-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdb.json
1 lines (1 loc) · 821 KB
/
db.json
1
{"meta":{"version":1,"warehouse":"1.0.3"},"models":{"Asset":[{"_id":"themes/jacman/source/js/totop.js","path":"js/totop.js","modified":0},{"_id":"themes/jacman/source/js/jquery.qrcode-0.12.0.min.js","path":"js/jquery.qrcode-0.12.0.min.js","modified":0},{"_id":"themes/jacman/source/js/jquery.imagesloaded.min.js","path":"js/jquery.imagesloaded.min.js","modified":0},{"_id":"themes/jacman/source/js/jquery-2.0.3.min.js","path":"js/jquery-2.0.3.min.js","modified":0},{"_id":"themes/jacman/source/js/gallery.js","path":"js/gallery.js","modified":0},{"_id":"themes/jacman/source/img/scrollup.png","path":"img/scrollup.png","modified":0},{"_id":"themes/jacman/source/img/logo.svg","path":"img/logo.svg","modified":0},{"_id":"themes/jacman/source/img/logo.png","path":"img/logo.png","modified":0},{"_id":"themes/jacman/source/img/jacman.jpg","path":"img/jacman.jpg","modified":0},{"_id":"themes/jacman/source/img/favicon.ico","path":"img/favicon.ico","modified":0},{"_id":"themes/jacman/source/img/cc-zero.svg","path":"img/cc-zero.svg","modified":0},{"_id":"themes/jacman/source/img/cc-by.svg","path":"img/cc-by.svg","modified":0},{"_id":"themes/jacman/source/img/cc-by-sa.svg","path":"img/cc-by-sa.svg","modified":0},{"_id":"themes/jacman/source/img/cc-by-nd.svg","path":"img/cc-by-nd.svg","modified":0},{"_id":"themes/jacman/source/img/cc-by-nc.svg","path":"img/cc-by-nc.svg","modified":0},{"_id":"themes/jacman/source/img/cc-by-nc-sa.svg","path":"img/cc-by-nc-sa.svg","modified":0},{"_id":"themes/jacman/source/img/cc-by-nc-nd.svg","path":"img/cc-by-nc-nd.svg","modified":0},{"_id":"themes/jacman/source/img/banner.jpg","path":"img/banner.jpg","modified":0},{"_id":"themes/jacman/source/img/author.jpg","path":"img/author.jpg","modified":0},{"_id":"themes/jacman/source/font/fontdiao.woff","path":"font/fontdiao.woff","modified":0},{"_id":"themes/jacman/source/font/fontdiao.ttf","path":"font/fontdiao.ttf","modified":0},{"_id":"themes/jacman/source/font/fontdiao.svg","path":"font/fontdiao.svg","modified":0},{"_id":"themes/jacman/source/font/fontdiao.eot","path":"font/fontdiao.eot","modified":0},{"_id":"themes/jacman/source/font/fontawesome-webfont.woff","path":"font/fontawesome-webfont.woff","modified":0},{"_id":"themes/jacman/source/font/fontawesome-webfont.ttf","path":"font/fontawesome-webfont.ttf","modified":0},{"_id":"themes/jacman/source/font/fontawesome-webfont.svg","path":"font/fontawesome-webfont.svg","modified":0},{"_id":"themes/jacman/source/font/fontawesome-webfont.eot","path":"font/fontawesome-webfont.eot","modified":0},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.woff","path":"font/coveredbyyourgrace-webfont.woff","modified":0},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.ttf","path":"font/coveredbyyourgrace-webfont.ttf","modified":0},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.svg","path":"font/coveredbyyourgrace-webfont.svg","modified":0},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.eot","path":"font/coveredbyyourgrace-webfont.eot","modified":0},{"_id":"themes/jacman/source/font/FontAwesome.otf","path":"font/FontAwesome.otf","modified":0},{"_id":"themes/jacman/source/fancybox/jquery.fancybox.pack.js","path":"fancybox/jquery.fancybox.pack.js","modified":0},{"_id":"themes/jacman/source/fancybox/jquery.fancybox.js","path":"fancybox/jquery.fancybox.js","modified":0},{"_id":"themes/jacman/source/fancybox/jquery.fancybox.css","path":"fancybox/jquery.fancybox.css","modified":0},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-thumbs.js","path":"fancybox/helpers/jquery.fancybox-thumbs.js","modified":0},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-thumbs.css","path":"fancybox/helpers/jquery.fancybox-thumbs.css","modified":0},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-media.js","path":"fancybox/helpers/jquery.fancybox-media.js","modified":0},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-buttons.js","path":"fancybox/helpers/jquery.fancybox-buttons.js","modified":0},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-buttons.css","path":"fancybox/helpers/jquery.fancybox-buttons.css","modified":0},{"_id":"themes/jacman/source/fancybox/helpers/fancybox_buttons.png","path":"fancybox/helpers/fancybox_buttons.png","modified":0},{"_id":"themes/jacman/source/fancybox/[email protected]","path":"fancybox/[email protected]","modified":0},{"_id":"themes/jacman/source/fancybox/fancybox_sprite.png","path":"fancybox/fancybox_sprite.png","modified":0},{"_id":"themes/jacman/source/fancybox/fancybox_overlay.png","path":"fancybox/fancybox_overlay.png","modified":0},{"_id":"themes/jacman/source/fancybox/[email protected]","path":"fancybox/[email protected]","modified":0},{"_id":"themes/jacman/source/fancybox/fancybox_loading.gif","path":"fancybox/fancybox_loading.gif","modified":0},{"_id":"themes/jacman/source/fancybox/blank.gif","path":"fancybox/blank.gif","modified":0},{"_id":"themes/jacman/source/css/style.styl","path":"css/style.styl","modified":0},{"_id":"source/CNAME","path":"CNAME","modified":0}],"Cache":[{"_id":"source/404.html","shasum":"9ae5313c4ee64f8060cd08bad8a6a8270e0bd02c","modified":1452479190239},{"_id":"source/CNAME","shasum":"5bea78925f82297f017d461af01317b7a493d489","modified":1452478547317},{"_id":"source/_posts/1-JavaScrpt简介-2016-01-05.md","shasum":"9521e1c2ac8f8aa0eccd17436d6ba58f2d7444c2","modified":1464748430407},{"_id":"source/_posts/2-在HTML中使用JavaScrpt-2016-01-05/deferAsync.png","shasum":"405aba297cb925c030bc11878e291edc350b3645","modified":1452171438360},{"_id":"source/_posts/2-在HTML中使用JavaScrpt-2016-01-05.md","shasum":"d77e07306640d0f9e47badadc248b289b5c4c4be","modified":1464762496610},{"_id":"source/_posts/3-第三章基本概念-2016-01-20.md","shasum":"68aad1b46e0a69b3b28e8c41978072194ea5fcab","modified":1467455654108},{"_id":"source/_posts/DOMContentLoaded-2016-01-08.md","shasum":"2a5ee0b40a0dfd790fd425e0142a5bffe65c21dc","modified":1464762310999},{"_id":"source/_posts/Array-2016-07-02.md","shasum":"160cb2340334b76e1119b540581826a4070c3a50","modified":1468845036856},{"_id":"source/_posts/DOMContentLoaded-2016-01-08/zongjie.png","shasum":"75b021d03703a62dffca85d21065daa074d2edc4","modified":1452226722787},{"_id":"source/_posts/JavaScript-code-style-2016-07-02.md","shasum":"0a61234be2ea0373c43a407337de4cecc799b55a","modified":1467597004505},{"_id":"source/_posts/JavaScript模块管理器-2016-07-10.md","shasum":"847cfc75a6eb220f8bfbbc019cd3e0ed15cd67f8","modified":1468144213913},{"_id":"source/_posts/function-2016-07-02.md","shasum":"f319f8accff9fcb78f56d92995c093462b308038","modified":1468332251457},{"_id":"source/_posts/javascript模块化-2016-07-10/guanxi.png","shasum":"2fa38c9c75091fba9a58a5ad40099659686d6c0b","modified":1468126403334},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image1.jpg","shasum":"2b16d4501fb5d96aa74bce522bf6619d271da92e","modified":1456108013569},{"_id":"source/_posts/javascript模块化-2016-07-10.md","shasum":"8525b653ed97b1677a6186ffbb32610dec6af097","modified":1468144306342},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image10.png","shasum":"38c20cf85e288d828a624ca4fd788fbb41039a38","modified":1456110979028},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image12.png","shasum":"c37c65adbceeb24bcc04f6aee94ad9c3257f07d3","modified":1456111158369},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image13.png","shasum":"84ccfcdeac04e66582513c359e214d1a3dc8b6fd","modified":1456111583844},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image14.png","shasum":"effec54c0893d59927ebac1f013f9bf014d9824f","modified":1456111618115},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image2.jpg","shasum":"4646bac94ab4efc141bcfc59fdb51d9cf066d53f","modified":1456108328728},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image3.jpg","shasum":"3477936fdd2cfa5015c19d59c3748d719f0ca7aa","modified":1456108521533},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image11.png","shasum":"5b867713120981fef57c0e51401e3523bb624e13","modified":1456111149351},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image5.png","shasum":"9395a1e25f7e8c1e5bc05b2a89e594ad05deb220","modified":1456110040030},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image4.jpg","shasum":"4646bac94ab4efc141bcfc59fdb51d9cf066d53f","modified":1456109175097},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image6.png","shasum":"effec54c0893d59927ebac1f013f9bf014d9824f","modified":1456110308131},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image7.png","shasum":"52f3f014bf0872c3d005b8fcf4773d0874bf716c","modified":1456110827026},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image9.png","shasum":"9cccf45e61ce8b329c14f97ae3579ddf5a282544","modified":1456110968947},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22.md","shasum":"d6c5bff06ffb94bc54ff00b075228029cd0a06ef","modified":1456112112354},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image8.png","shasum":"b93b45193c3ab0bf72788a61bb6c25ecd7cab2b0","modified":1456110952848},{"_id":"source/_posts/ECMAScrip中的对象存取器-getter和setter-2016-01-21.md","shasum":"53477985cd52e20b2a72c148a3cd7dc49fb96423","modified":1453369070135},{"_id":"source/_posts/numberInJavaScript-2016-06-19/11.png","shasum":"4d846b73a61ab526358e65b60e6cbd36a6315e81","modified":1466341543106},{"_id":"source/_posts/numberInJavaScript-2016-06-19/32.png","shasum":"f3f668acfda2b4d3a13904e306bbbf37250106ae","modified":1465540260422},{"_id":"source/_posts/numberInJavaScript-2016-06-19/123.png","shasum":"a2bedb9531ae71ecb62babb3353024ca1a235965","modified":1466337849837},{"_id":"source/_posts/numberInJavaScript-2016-06-19/456.jpg","shasum":"a288a59a9909a28c4315a3b1f0c5e78ff7d2c71f","modified":1466337862413},{"_id":"source/_posts/numberInJavaScript-2016-06-19/789.jpg","shasum":"a288a59a9909a28c4315a3b1f0c5e78ff7d2c71f","modified":1466337870533},{"_id":"source/_posts/numberInJavaScript-2016-06-19/10.png","shasum":"28c156f84f848b99836643da8c012569a5fe60b8","modified":1466341422555},{"_id":"source/_posts/numberInJavaScript-2016-06-19/64.png","shasum":"3be28f0630aaeca21f91282fe55a9d9e8b73335c","modified":1465540266804},{"_id":"source/_posts/error-2016-07-02.md","shasum":"a65568b9834add66eb66c60b32c51027b9501b7f","modified":1467596623409},{"_id":"source/_posts/严格模式-2016-01-21/stracit.png","shasum":"79f6c9b89a2d7551182e34520bd2b55d84d279dc","modified":1454471627270},{"_id":"source/_posts/void-0-2016-01-29.md","shasum":"1e741ccc4b7fd2bb5df3f38c5520707167644e9e","modified":1454053234743},{"_id":"source/_posts/严格模式-2016-01-21.md","shasum":"d4ac42fab875f539396abd6aa01ed54f1fed4bad","modified":1465310917016},{"_id":"source/_posts/好好学学Object-2016-02-03.md","shasum":"17d5b0c0941189ac0673f31ad63c702d7a927a4c","modified":1468335361938},{"_id":"source/_posts/webkit的预加载扫描器-2016-01-19.md","shasum":"c8b9ebe46cdb0553b9cba8e0a1accc4499da7911","modified":1453208403599},{"_id":"source/_posts/numberInJavaScript-2016-06-19.md","shasum":"480fe3d710580dffd878e4e8eebdd242d7684736","modified":1466344353493},{"_id":"source/_posts/如何学习javascript-转帖-2016-01-27.md","shasum":"86d88de583c4d5b64794f5feb02d861820a8a022","modified":1453862572902},{"_id":"source/_posts/理解react和redux-2016-07-31.md","shasum":"3587dc192507d1b7a4f218e16caafbed56149751","modified":1469945118590},{"_id":"source/_posts/好好学学number-2016-01-29.md","shasum":"28aadeb414a40f3b44be93b8d707d42721415bbe","modified":1465621061618},{"_id":"source/_posts/好好学学undefined!-2016-01-29.md","shasum":"c304d31e6e9a40b76029b7aeed201c3f6dab78ee","modified":1454392617673},{"_id":"source/_posts/类型检验-2016-02-19.md","shasum":"4479d37a543ba10a50d4e38a84e3e3e73db28463","modified":1465622802267},{"_id":"source/_posts/类型检验-2016-02-19/type.jpg","shasum":"2b4ee2768ce5350fc369e0e78ae5631804cdaf62","modified":1455854375113},{"_id":"source/_posts/好好学学String-2016-02-02.md","shasum":"843d01d0c0198acda3e6de551e1436db87462e72","modified":1454487014214},{"_id":"source/_posts/类型转换-2016-02-18.md","shasum":"e672d58fe9ead2e0c65a3dd0207e34fa71f8a543","modified":1465626489517},{"_id":"source/_posts/运算符-2016-02-25.md","shasum":"150888bf7f4183798cb153f1257fcf2df67c7332","modified":1465631311806},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/body.png","shasum":"22b09c36423075213428f54b894a33cfc23e8767","modified":1474602243759},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/cm-http.png","shasum":"a3233944381117e547e512812ad1aa59dcd4a01b","modified":1474601383317},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/first.png","shasum":"c025de8e5398d9013ea046514563aeb30d2b05bb","modified":1474599636792},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/userStatus.png","shasum":"f55ada6b417fe79cdf3b2a1452311cd72669309b","modified":1474603095749},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/getMessageResponse.png","shasum":"86c16471d4e484bdc2b48d3de254fce6873b5092","modified":1474603018365},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/message.png","shasum":"4e53efa1233fd5c74dd1f37a32698e998433b318","modified":1474602442461},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/结构图.png","shasum":"75a2a80991cfea9bce9c51a3ab60835c46801cb8","modified":1474603863434},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/架构.png","shasum":"68dc262f187f004a484b3a91d7e38152da4188e2","modified":1474604624323},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/具体作用.png","shasum":"22fbaccd2f51c860d6be938ed757df34b6c3df27","modified":1474601615894},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23.md","shasum":"92efbdacdb6fc0e6428ada9705878138e88229a7","modified":1474605103988},{"_id":"source/baidu_verify_i0wnrfk2ui.html","shasum":"48d9c82953f45c582fba83160d71d4df0ec7103f","modified":1452572516453},{"_id":"source/_posts/DOMContentLoaded-2016-01-08/onLoadVSDomContentLoaded.png","shasum":"597586264b44d04e58387eb02379818a338627b1","modified":1452220118067},{"_id":"source/_posts/javascript模块化-2016-07-10/process.png","shasum":"32264d53bd8700c6fe542004b3e53f43fe1ddb50","modified":1468130689091},{"_id":"source/_posts/javascript模块化-2016-07-10/webpack.png","shasum":"fa9b0395678a17aa21699b476675239f30755577","modified":1468141681326},{"_id":"source/_posts/好好学学number-2016-01-29/number.png","shasum":"6d0564a349b5e99daa5b5588314b3a4ce45f7fd1","modified":1453980343059},{"_id":"source/_posts/好好学学number-2016-01-29/number2.png","shasum":"2735883e61ae234615de6234b9280f12a4e4ba47","modified":1454043023911},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/getMessage.png","shasum":"5ccb53856aa701d4a7fcb2b7c7c1202a0c37c387","modified":1474602891075},{"_id":"themes/jacman/LICENSE","shasum":"931516aa36c53eb7843c83d82662eb50cc3c4367","modified":1450859342186},{"_id":"themes/jacman/README.md","shasum":"79be8a49927c8666f1804d7ccd08af8d3268062a","modified":1450859342186},{"_id":"themes/jacman/_config.yml","shasum":"ec3e1c2147b1223cf10a14ba768a931a2e13f31d","modified":1452228411063},{"_id":"themes/jacman/languages/default.yml","shasum":"eea72d6138497287c0b3f4bd93e4f6f62b7aff37","modified":1450859342189},{"_id":"themes/jacman/languages/zh-CN.yml","shasum":"1f3b9d00dd4322352b0c9c82a76dc9865a616d41","modified":1450859342190},{"_id":"themes/jacman/languages/zh-TW.yml","shasum":"61a02ba818d641579a86fcd7f5926ab1e6ab5f70","modified":1450859342191},{"_id":"themes/jacman/README_zh.md","shasum":"0854e4c96f53005f3a47e21af3f8aee361719ce4","modified":1450859342187},{"_id":"themes/jacman/layout/_partial/after_footer.ejs","shasum":"f0772c9de0431384d4e852358ee425a1cdf3ea6d","modified":1453707593001},{"_id":"themes/jacman/layout/_partial/archive.ejs","shasum":"2c7395e7563fe016521712a645c28a13f952d52a","modified":1450859342194},{"_id":"themes/jacman/layout/_partial/analytics.ejs","shasum":"697601996220fe0a0f9cd628be67dec3c86ae2aa","modified":1450859342193},{"_id":"themes/jacman/layout/_partial/article_row.ejs","shasum":"4cb855d91ece7f67b2ca0992fffa55472d0b9c93","modified":1450859342196},{"_id":"themes/jacman/layout/_partial/article.ejs","shasum":"261ecacb8456f4cb972632b6a9103860fa63b9a3","modified":1450859342195},{"_id":"themes/jacman/layout/_partial/categories.ejs","shasum":"8a52d0344d5bce1925cf586ed73c11192925209b","modified":1450859342197},{"_id":"themes/jacman/layout/_partial/footer.ejs","shasum":"32db7e7c8171530d29c3878f387c4438d6057508","modified":1450859342197},{"_id":"themes/jacman/layout/_partial/header.ejs","shasum":"18515612344ff048b9372b91b7eef6f3b143801f","modified":1450859342199},{"_id":"themes/jacman/layout/_partial/head.ejs","shasum":"761941be4922cd3c177c8130296b909bf7db5c09","modified":1450859342198},{"_id":"themes/jacman/layout/_partial/pagination.ejs","shasum":"6146ac37dfb4f8613090bc52b3fc8cfa911a186a","modified":1450859342200},{"_id":"themes/jacman/layout/_partial/mathjax.ejs","shasum":"d42994ac696f52ba99c1cbac382cd76d5b04a3e8","modified":1450859342200},{"_id":"themes/jacman/layout/_partial/post/article.ejs","shasum":"b09e3acea7076e1f01dfe0c2295e19951ea09437","modified":1450859342202},{"_id":"themes/jacman/layout/_partial/post/catetags.ejs","shasum":"0e37bababc8f4659f5b59a552a946b46d89e4158","modified":1450859342202},{"_id":"themes/jacman/layout/_partial/post/comment.ejs","shasum":"c88bc8f5805173920a5fdd7e9234a850e3d8e151","modified":1450859342203},{"_id":"themes/jacman/layout/_partial/post/footer.ejs","shasum":"b12ec08a5845a3d8c01257614f1dfead879c87d2","modified":1450859342204},{"_id":"themes/jacman/layout/_partial/post/gallery.ejs","shasum":"fafc2501d7e65983b0f5c2b58151ca12e57c0574","modified":1450859342205},{"_id":"themes/jacman/layout/_partial/post/header.ejs","shasum":"36a705942b691abe0d643ea8afa339981b32f6f2","modified":1450859342206},{"_id":"themes/jacman/layout/_partial/post/jiathis.ejs","shasum":"d7f5960039ac74924559ab6ba03c64457b8f0966","modified":1450859342207},{"_id":"themes/jacman/layout/_partial/post/pagination.ejs","shasum":"7de9c07a4c968429a8088c31a28b7f3a993ded1b","modified":1450859342207},{"_id":"themes/jacman/layout/_partial/sidebar.ejs","shasum":"c4f527fff0070fbe65919053a16224412317f40d","modified":1450859342209},{"_id":"themes/jacman/layout/_partial/search.ejs","shasum":"1083824a6c6c3df02767f2f3b727aee78ebb76ec","modified":1450859342208},{"_id":"themes/jacman/layout/_partial/tags.ejs","shasum":"b33b2b5d08f1d53a8de25a95f660f7f1cea7b3cb","modified":1450859342209},{"_id":"themes/jacman/layout/_partial/tinysou_search.ejs","shasum":"06ecddc8a9d40b480fe2e958af1dab857a9d5441","modified":1450859342210},{"_id":"themes/jacman/layout/_partial/totop.ejs","shasum":"bea5bb7cb9350b8af7d97a8d223af63a5b30ab78","modified":1450859342210},{"_id":"themes/jacman/layout/_widget/archive.ejs","shasum":"39ea6b7888406fbd1b4cf236ebd718e881493374","modified":1450859342211},{"_id":"themes/jacman/layout/_widget/category.ejs","shasum":"c1fae96b5053da021bcc04ab2ce5c2c8d30de8a2","modified":1450859342212},{"_id":"themes/jacman/layout/_widget/douban.ejs","shasum":"e3820c36169e88663e6c9177666b2904c1ce47e6","modified":1450859342212},{"_id":"themes/jacman/layout/_widget/github-card.ejs","shasum":"5c759b6ea214bac56a393247de27e67ce73fb33f","modified":1450859342213},{"_id":"themes/jacman/layout/_widget/rss.ejs","shasum":"0a4b5f2a2e36a1d504fe2e7c6c8372cbb4628aab","modified":1450859342214},{"_id":"themes/jacman/layout/_widget/tag.ejs","shasum":"7e82ad9c916b9ce871b2f65ce8f283c5ba47947b","modified":1450859342215},{"_id":"themes/jacman/layout/_widget/tagcloud.ejs","shasum":"10a1001189d5c28ce6d42494563b9637c302b454","modified":1450859342215},{"_id":"themes/jacman/layout/_widget/weibo.ejs","shasum":"a31c2b223d0feb2a227e203cac9e5d13b7d328a8","modified":1450859342216},{"_id":"themes/jacman/layout/archive.ejs","shasum":"a18842e3d719fe3ca9b977a6995f8facc75c8673","modified":1450859342216},{"_id":"themes/jacman/layout/category.ejs","shasum":"9b740fc33f6f028df60f0bc4312bf3ebd03aa8ea","modified":1450859342217},{"_id":"themes/jacman/layout/index.ejs","shasum":"75cef2172c286994af412e11ab7f4f5a0daaf1f5","modified":1450859342217},{"_id":"themes/jacman/layout/page.ejs","shasum":"bd6bbf2ea8e183bd835867ff617dc6366b56748c","modified":1450859342219},{"_id":"themes/jacman/layout/post.ejs","shasum":"3114134775bdde5a83cf14feb019606fa2b2b2be","modified":1450859342219},{"_id":"themes/jacman/layout/layout.ejs","shasum":"5b4289a4526899809b9c2facea535367ff51ba2b","modified":1450859342218},{"_id":"themes/jacman/layout/tag.ejs","shasum":"45150a2365768b6b67880193c9264ad2bb4814db","modified":1450859342220},{"_id":"themes/jacman/scripts/fancybox.js","shasum":"aa411cd072399df1ddc8e2181a3204678a5177d9","modified":1450859342221},{"_id":"themes/jacman/source/css/_base/highlight/highlight.styl","shasum":"91b62bfc58390b0d5db782a75be6965ee3665eb3","modified":1450859342224},{"_id":"themes/jacman/source/css/_base/highlight/theme.styl","shasum":"e3a59bd427ba37a54ead9eeba9a5356b3f720a48","modified":1450859342225},{"_id":"themes/jacman/source/css/_base/font.styl","shasum":"c8a0faf43b08e37ad07a5669db76d595da966159","modified":1450859342223},{"_id":"themes/jacman/source/css/_base/public.styl","shasum":"f016180726019927b9a835ed01e04d153f27a149","modified":1450859342225},{"_id":"themes/jacman/layout/_widget/links.ejs","shasum":"e49868063439c2092cdf9a8ec82cc295b0e42f66","modified":1450859342213},{"_id":"themes/jacman/source/css/_base/variable.styl","shasum":"cb652eb83c28a208743fabab92de896f8b7cbf7b","modified":1450859342226},{"_id":"themes/jacman/source/css/_partial/article.styl","shasum":"c69641b4a34a8c62986b335414413dbde26de25e","modified":1450859342227},{"_id":"themes/jacman/source/css/_partial/aside.styl","shasum":"506fde1d67ce750452cbe84bee01a19c7d027c5e","modified":1450859342228},{"_id":"themes/jacman/source/css/_partial/duoshuo.styl","shasum":"e85f1192283f043115c272a9deb3cb6ced793990","modified":1450859342229},{"_id":"themes/jacman/source/css/_partial/footer.styl","shasum":"1911613a19b605a58f801c21b03b5d4c83b90f9c","modified":1450859342229},{"_id":"themes/jacman/source/css/_partial/header.styl","shasum":"5121ceb712be3f2dde98b8b6e589b546e19eab8f","modified":1450859342231},{"_id":"themes/jacman/source/css/_partial/gallery.styl","shasum":"7246809f4ce3166ec1b259bf475cae1a48e29aad","modified":1450859342230},{"_id":"themes/jacman/source/css/_partial/index.styl","shasum":"a72ff14effd276015264f870f47ed8f8413bf5d3","modified":1450859342232},{"_id":"themes/jacman/source/css/_partial/totop.styl","shasum":"96363d7c5aaed5f649667fc0752a62620a67e872","modified":1450859342233},{"_id":"themes/jacman/source/css/_partial/helper.styl","shasum":"1136600932b97534b88465bf05ef313630b2de3d","modified":1450859342232},{"_id":"themes/jacman/source/css/style.styl","shasum":"a0a45af186a72ae68979bf26f2a5d0d2303189ca","modified":1450859342234},{"_id":"themes/jacman/source/fancybox/blank.gif","shasum":"2daeaa8b5f19f0bc209d976c02bd6acb51b00b0a","modified":1450859342235},{"_id":"themes/jacman/source/fancybox/fancybox_loading.gif","shasum":"1a755fb2599f3a313cc6cfdb14df043f8c14a99c","modified":1450859342236},{"_id":"themes/jacman/source/fancybox/[email protected]","shasum":"273b123496a42ba45c3416adb027cd99745058b0","modified":1450859342236},{"_id":"themes/jacman/source/fancybox/fancybox_overlay.png","shasum":"b3a4ee645ba494f52840ef8412015ba0f465dbe0","modified":1450859342237},{"_id":"themes/jacman/source/fancybox/fancybox_sprite.png","shasum":"17df19f97628e77be09c352bf27425faea248251","modified":1450859342238},{"_id":"themes/jacman/source/fancybox/helpers/fancybox_buttons.png","shasum":"e385b139516c6813dcd64b8fc431c364ceafe5f3","modified":1450859342240},{"_id":"themes/jacman/source/fancybox/[email protected]","shasum":"30c58913f327e28f466a00f4c1ac8001b560aed8","modified":1450859342239},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-media.js","shasum":"294420f9ff20f4e3584d212b0c262a00a96ecdb3","modified":1450859342242},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-buttons.css","shasum":"1a9d8e5c22b371fcc69d4dbbb823d9c39f04c0c8","modified":1450859342240},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-thumbs.css","shasum":"4ac329c16a5277592fc12a37cca3d72ca4ec292f","modified":1450859342242},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-buttons.js","shasum":"dc3645529a4bf72983a39fa34c1eb9146e082019","modified":1450859342241},{"_id":"themes/jacman/source/fancybox/helpers/jquery.fancybox-thumbs.js","shasum":"47da1ae5401c24b5c17cc18e2730780f5c1a7a0c","modified":1450859342243},{"_id":"themes/jacman/source/fancybox/jquery.fancybox.css","shasum":"aaa582fb9eb4b7092dc69fcb2d5b1c20cca58ab6","modified":1450859342243},{"_id":"themes/jacman/source/fancybox/jquery.fancybox.js","shasum":"d08b03a42d5c4ba456ef8ba33116fdbb7a9cabed","modified":1450859342244},{"_id":"themes/jacman/source/fancybox/jquery.fancybox.pack.js","shasum":"9e0d51ca1dbe66f6c0c7aefd552dc8122e694a6e","modified":1450859342245},{"_id":"themes/jacman/source/font/FontAwesome.otf","shasum":"b5b4f9be85f91f10799e87a083da1d050f842734","modified":1450859342247},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.ttf","shasum":"194ccb4acf77a03dc25bcc174edb266143704fec","modified":1450859342251},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.woff","shasum":"c6f8dc1a2f6ce914f120e80a876b8fd77b98888e","modified":1450859342252},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.eot","shasum":"a17d0f10534303e40f210c506ebb8703fa23b7de","modified":1450859342248},{"_id":"themes/jacman/source/font/fontawesome-webfont.eot","shasum":"7619748fe34c64fb157a57f6d4ef3678f63a8f5e","modified":1450859342253},{"_id":"themes/jacman/source/font/fontawesome-webfont.woff","shasum":"04c3bf56d87a0828935bd6b4aee859995f321693","modified":1450859342258},{"_id":"themes/jacman/source/font/fontdiao.ttf","shasum":"ee9fd7be2493c9bf6d2841044e69a0830d9d3fab","modified":1450859342262},{"_id":"themes/jacman/source/font/fontdiao.woff","shasum":"71f54eb6e98aa28cafeb04aab71c0e5b349ea89f","modified":1450859342263},{"_id":"themes/jacman/source/font/fontdiao.eot","shasum":"9544a0d7ba208989302bc4da5a184faeb0e883c9","modified":1450859342259},{"_id":"themes/jacman/source/img/author.jpg","shasum":"2a292e681b4c6c975eec9c8c356d99647a465542","modified":1450859342264},{"_id":"themes/jacman/source/img/cc-by-nc-nd.svg","shasum":"c6524ece3f8039a5f612feaf865d21ec8a794564","modified":1450859342268},{"_id":"themes/jacman/source/img/cc-by-nd.svg","shasum":"c563508ce9ced1e66948024ba1153400ac0e0621","modified":1450859342271},{"_id":"themes/jacman/source/img/cc-by-nc-sa.svg","shasum":"3031be41e8753c70508aa88e84ed8f4f653f157e","modified":1450859342269},{"_id":"themes/jacman/source/img/cc-by-nc.svg","shasum":"8d39b39d88f8501c0d27f8df9aae47136ebc59b7","modified":1450859342270},{"_id":"themes/jacman/source/img/cc-by-sa.svg","shasum":"aa4742d733c8af8d38d4c183b8adbdcab045872e","modified":1450859342272},{"_id":"themes/jacman/source/img/cc-zero.svg","shasum":"87669bf8ac268a91d027a0a4802c92a1473e9030","modified":1450859342273},{"_id":"themes/jacman/source/img/jacman.jpg","shasum":"0ba14a4a5e3be012826fc713c33479912126d34e","modified":1450859342275},{"_id":"themes/jacman/source/img/cc-by.svg","shasum":"28a0a4fe355a974a5e42f68031652b76798d4f7e","modified":1450859342272},{"_id":"themes/jacman/source/img/favicon.ico","shasum":"2d22a3e0c7905a894e832c831dd91c29c209c7a5","modified":1450859342274},{"_id":"themes/jacman/source/img/logo.svg","shasum":"9ae38f7225c38624faeb7b74996efa9de7bf065b","modified":1450859342277},{"_id":"themes/jacman/source/img/scrollup.png","shasum":"2137d4f1739aa8aa3fcb0348c3ddf1e41d62f2e3","modified":1450859342277},{"_id":"themes/jacman/source/js/gallery.js","shasum":"f8a4ba7fb8349cca374a3c69fff9b2bf21f742ed","modified":1450859342278},{"_id":"themes/jacman/source/js/jquery.imagesloaded.min.js","shasum":"4109837b1f6477bacc6b095a863b1b95b1b3693f","modified":1450859342280},{"_id":"themes/jacman/source/js/totop.js","shasum":"cad23c5ea7163d1e5c05a0fd3ef9233469da10cb","modified":1450859342282},{"_id":"themes/jacman/source/js/jquery.qrcode-0.12.0.min.js","shasum":"57c3987166a26415a71292162690e82c21e315ad","modified":1450859342281},{"_id":"themes/jacman/source/font/coveredbyyourgrace-webfont.svg","shasum":"eabdb262d8e246865dfb56031f01ff6e8d2f9d53","modified":1450859342249},{"_id":"themes/jacman/source/font/fontdiao.svg","shasum":"334a94e6a66a8b089be7315d876bec93efe38d2b","modified":1450859342261},{"_id":"themes/jacman/source/font/fontawesome-webfont.ttf","shasum":"7f09c97f333917034ad08fa7295e916c9f72fd3f","modified":1450859342257},{"_id":"themes/jacman/source/img/logo.png","shasum":"fd08d12d1fa147cf894e8f8327e38f1758de32ed","modified":1450859342276},{"_id":"themes/jacman/source/js/jquery-2.0.3.min.js","shasum":"a0ae3697b0ab8c0e8bd3186c80db42abd6d97a8d","modified":1450859342279},{"_id":"themes/jacman/source/font/fontawesome-webfont.svg","shasum":"46fcc0194d75a0ddac0a038aee41b23456784814","modified":1450859342255},{"_id":"themes/jacman/source/img/banner.jpg","shasum":"5104860c4f8b2e84ef734ba6c37fe7a288bf0d74","modified":1450859342267},{"_id":"public/2016/09/23/通过XMPP构建即时通信/body.png","modified":1474605176793,"shasum":"22b09c36423075213428f54b894a33cfc23e8767"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/cm-http.png","modified":1474605176811,"shasum":"a3233944381117e547e512812ad1aa59dcd4a01b"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/first.png","modified":1474605176823,"shasum":"c025de8e5398d9013ea046514563aeb30d2b05bb"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/getMessage.png","modified":1474605176839,"shasum":"5ccb53856aa701d4a7fcb2b7c7c1202a0c37c387"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/getMessageResponse.png","modified":1474605176842,"shasum":"86c16471d4e484bdc2b48d3de254fce6873b5092"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/message.png","modified":1474605176847,"shasum":"4e53efa1233fd5c74dd1f37a32698e998433b318"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/userStatus.png","modified":1474605176857,"shasum":"f55ada6b417fe79cdf3b2a1452311cd72669309b"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/具体作用.png","modified":1474605176865,"shasum":"22fbaccd2f51c860d6be938ed757df34b6c3df27"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/架构.png","modified":1474605176871,"shasum":"68dc262f187f004a484b3a91d7e38152da4188e2"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/结构图.png","modified":1474605176879,"shasum":"75a2a80991cfea9bce9c51a3ab60835c46801cb8"},{"_id":"public/2016/02/19/类型检验/type.jpg","modified":1474605176883,"shasum":"2b4ee2768ce5350fc369e0e78ae5631804cdaf62"},{"_id":"public/2016/01/29/好好学学number/number.png","modified":1474605176893,"shasum":"6d0564a349b5e99daa5b5588314b3a4ce45f7fd1"},{"_id":"public/2016/01/29/好好学学number/number2.png","modified":1474605176902,"shasum":"2735883e61ae234615de6234b9280f12a4e4ba47"},{"_id":"public/2016/01/21/严格模式/stracit.png","modified":1474605176906,"shasum":"79f6c9b89a2d7551182e34520bd2b55d84d279dc"},{"_id":"public/2016/06/19/numberInJavaScript/10.png","modified":1474605176908,"shasum":"28c156f84f848b99836643da8c012569a5fe60b8"},{"_id":"public/2016/06/19/numberInJavaScript/11.png","modified":1474605176912,"shasum":"4d846b73a61ab526358e65b60e6cbd36a6315e81"},{"_id":"public/2016/06/19/numberInJavaScript/123.png","modified":1474605176915,"shasum":"a2bedb9531ae71ecb62babb3353024ca1a235965"},{"_id":"public/2016/06/19/numberInJavaScript/32.png","modified":1474605176921,"shasum":"f3f668acfda2b4d3a13904e306bbbf37250106ae"},{"_id":"public/2016/06/19/numberInJavaScript/456.jpg","modified":1474605176924,"shasum":"a288a59a9909a28c4315a3b1f0c5e78ff7d2c71f"},{"_id":"public/2016/06/19/numberInJavaScript/64.png","modified":1474605176927,"shasum":"3be28f0630aaeca21f91282fe55a9d9e8b73335c"},{"_id":"public/2016/06/19/numberInJavaScript/789.jpg","modified":1474605176930,"shasum":"a288a59a9909a28c4315a3b1f0c5e78ff7d2c71f"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image1.jpg","modified":1474605176935,"shasum":"2b16d4501fb5d96aa74bce522bf6619d271da92e"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image10.png","modified":1474605176938,"shasum":"38c20cf85e288d828a624ca4fd788fbb41039a38"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image11.png","modified":1474605176941,"shasum":"5b867713120981fef57c0e51401e3523bb624e13"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image12.png","modified":1474605176945,"shasum":"c37c65adbceeb24bcc04f6aee94ad9c3257f07d3"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image13.png","modified":1474605176951,"shasum":"84ccfcdeac04e66582513c359e214d1a3dc8b6fd"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image14.png","modified":1474605176955,"shasum":"effec54c0893d59927ebac1f013f9bf014d9824f"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image2.jpg","modified":1474605176957,"shasum":"4646bac94ab4efc141bcfc59fdb51d9cf066d53f"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image3.jpg","modified":1474605176960,"shasum":"3477936fdd2cfa5015c19d59c3748d719f0ca7aa"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image4.jpg","modified":1474605176964,"shasum":"4646bac94ab4efc141bcfc59fdb51d9cf066d53f"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image5.png","modified":1474605176966,"shasum":"9395a1e25f7e8c1e5bc05b2a89e594ad05deb220"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image6.png","modified":1474605176970,"shasum":"effec54c0893d59927ebac1f013f9bf014d9824f"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image7.png","modified":1474605176981,"shasum":"52f3f014bf0872c3d005b8fcf4773d0874bf716c"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image8.png","modified":1474605176984,"shasum":"b93b45193c3ab0bf72788a61bb6c25ecd7cab2b0"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/image9.png","modified":1474605176986,"shasum":"9cccf45e61ce8b329c14f97ae3579ddf5a282544"},{"_id":"public/2016/07/10/javascript模块化/guanxi.png","modified":1474605176989,"shasum":"2fa38c9c75091fba9a58a5ad40099659686d6c0b"},{"_id":"public/2016/07/10/javascript模块化/process.png","modified":1474605176995,"shasum":"32264d53bd8700c6fe542004b3e53f43fe1ddb50"},{"_id":"public/2016/07/10/javascript模块化/webpack.png","modified":1474605177000,"shasum":"fa9b0395678a17aa21699b476675239f30755577"},{"_id":"public/2016/01/08/DOMContentLoaded/onLoadVSDomContentLoaded.png","modified":1474605177004,"shasum":"597586264b44d04e58387eb02379818a338627b1"},{"_id":"public/2016/01/08/DOMContentLoaded/zongjie.png","modified":1474605177008,"shasum":"75b021d03703a62dffca85d21065daa074d2edc4"},{"_id":"public/2016/01/05/2-在HTML中使用JavaScrpt/deferAsync.png","modified":1474605177017,"shasum":"405aba297cb925c030bc11878e291edc350b3645"},{"_id":"public/404.html","modified":1474605177107,"shasum":"a21b5406ef4378f234ad7f1875c047dd21a86501"},{"_id":"public/baidu_verify_i0wnrfk2ui.html","modified":1474605177117,"shasum":"b3f41baff9e890ecfccc0a8d360fd0919a39b450"},{"_id":"public/2016/09/23/通过XMPP构建即时通信/index.html","modified":1474605177191,"shasum":"9e4d85a79f8d7e2155aa9a581feaf3afaebaa8fb"},{"_id":"public/2016/07/31/理解react和redux/index.html","modified":1474605177209,"shasum":"c6bd1c878654ea8625de09cb932195c24438c7de"},{"_id":"public/2016/07/10/JavaScript模块管理器/index.html","modified":1474605177254,"shasum":"cb2d3163d30ed37ed46e94c983f4c9c7792a2976"},{"_id":"public/2016/07/10/javascript模块化/index.html","modified":1474605177316,"shasum":"139aafa518ff920e2abcf9a66c5e9a7f4cf17e11"},{"_id":"public/2016/07/02/JavaScript-code-style/index.html","modified":1474605177375,"shasum":"80dbb7c2ab3f6a6f098df47ff1d69af66a110956"},{"_id":"public/2016/07/02/error/index.html","modified":1474605177442,"shasum":"3a5009aa057dd2f0de2e85a86377751a3284b370"},{"_id":"public/2016/07/02/function/index.html","modified":1474605177560,"shasum":"1794531a6da8b93fde17c3e775a96dd2afb851e7"},{"_id":"public/2016/07/02/Array/index.html","modified":1474605177645,"shasum":"a585f905bed4f576e7484881b6b27d53ee61fa8e"},{"_id":"public/2016/06/19/numberInJavaScript/index.html","modified":1474605177695,"shasum":"b3ec9905f176b36681c74f45cac4618d4cee9841"},{"_id":"public/2016/02/25/运算符/index.html","modified":1474605177807,"shasum":"e04fdc36cd03740fd4aafbd0c930d2355279b901"},{"_id":"public/2016/02/22/js中函数参数都是按值传递的/index.html","modified":1474605177829,"shasum":"97e1fba1c2b8b3d8920f08c4a088299a5c14f2d2"},{"_id":"public/2016/02/19/类型检验/index.html","modified":1474605177899,"shasum":"6a93e98ff80ba8053ecdd05ed1ecad64b91e3bcd"},{"_id":"public/2016/02/18/类型转换/index.html","modified":1474605177941,"shasum":"82986a2e2ea1630ce9398fda3ddfd452a7c0a2e0"},{"_id":"public/2016/02/03/好好学学Object/index.html","modified":1474605178057,"shasum":"8d42b60b4f64983031f2d0c3bf7b969a883e5332"},{"_id":"public/2016/02/02/好好学学String/index.html","modified":1474605178115,"shasum":"425d899fd03fc497899cc4b2f5758b8092a32815"},{"_id":"public/2016/01/29/void-0/index.html","modified":1474605178135,"shasum":"e6684b92a2a40ea182586939335062a4e648b2fc"},{"_id":"public/2016/01/29/好好学学number/index.html","modified":1474605178217,"shasum":"8272fb9222111ac6aa15fe68a1239ef6ce3245cb"},{"_id":"public/2016/01/29/好好学学undefined!/index.html","modified":1474605178250,"shasum":"ed02b641cb62539ace6a6e70f4323291a56c5daf"},{"_id":"public/2016/01/27/如何学习javascript-转帖/index.html","modified":1474605178266,"shasum":"681953cf88521554930038e650136fd5f46c7ba0"},{"_id":"public/2016/01/21/ECMAScrip中的对象存取器-getter和setter/index.html","modified":1474605178281,"shasum":"6e62a6dcdcbb14cb20f12a088553f6e1dfc27250"},{"_id":"public/2016/01/21/严格模式/index.html","modified":1474605178311,"shasum":"f47dcb4b3e7c3cfbcb56b84e6ccacaaf51415e8e"},{"_id":"public/2016/01/20/3-第三章基本概念/index.html","modified":1474605178348,"shasum":"7b329dbaa74f7adad8e96bb8ccc754a13e3e1e3e"},{"_id":"public/2016/01/19/webkit的预加载扫描器/index.html","modified":1474605178360,"shasum":"71a871dd9115f5cbe98dfc91b9f1f933d7558c2b"},{"_id":"public/2016/01/08/DOMContentLoaded/index.html","modified":1474605178407,"shasum":"749df7a10cbbf2b58d0d3a97cf1fa72467407c1d"},{"_id":"public/2016/01/05/2-在HTML中使用JavaScrpt/index.html","modified":1474605178432,"shasum":"e105c9ef7dc7df94da09633175874ff02d2a0852"},{"_id":"public/2016/01/05/1-JavaScrpt简介/index.html","modified":1474605178448,"shasum":"6c8ace95ab7c36416b3829d3fc4d82848acf421f"},{"_id":"public/categories/《JS高程3-笔记》/index.html","modified":1474605178461,"shasum":"3f63e883e5c3e3c530b03c13ceeda8794a2ae0fa"},{"_id":"public/categories/JavaScript/index.html","modified":1474605178469,"shasum":"252981ba36cfc5ab9354b5a17cd10b3bb51df731"},{"_id":"public/categories/JavaScript/page/2/index.html","modified":1474605178480,"shasum":"6ffb6b1a2969be93d1dc79f2dc3c42c42693b75a"},{"_id":"public/categories/模块化/index.html","modified":1474605178486,"shasum":"eaee8c8c0c058a795d39a0018e412fde42bd8ce7"},{"_id":"public/archives/index.html","modified":1474605178501,"shasum":"67ad777d35841c2675a7e41909e685d2eae826cd"},{"_id":"public/archives/page/2/index.html","modified":1474605178517,"shasum":"2a19202c3802214183ef6045e3c817cf0b39286d"},{"_id":"public/archives/page/3/index.html","modified":1474605178530,"shasum":"3013053e297cced47fb3b6b26ecbe5c35e5a3022"},{"_id":"public/archives/2016/index.html","modified":1474605178547,"shasum":"6318070b9c0bd962e5852da0e83fe16d64466f60"},{"_id":"public/archives/2016/page/2/index.html","modified":1474605178558,"shasum":"dd1fcc66925adc49ae02d2a622c5c0c693680f4b"},{"_id":"public/archives/2016/page/3/index.html","modified":1474605178577,"shasum":"fc92174f4ce46b6978f8ee5b77f7ac0f2cb0e14c"},{"_id":"public/archives/2016/01/index.html","modified":1474605178589,"shasum":"af2d2b2f208b35224c8979e5aab0e5fb3b9105ec"},{"_id":"public/archives/2016/01/page/2/index.html","modified":1474605178597,"shasum":"a7e1a1d6e61f5d7b6545f79bd6d36c116bb695ab"},{"_id":"public/archives/2016/02/index.html","modified":1474605178605,"shasum":"255351ea0f2366214df339338cddb37f8ec38768"},{"_id":"public/archives/2016/06/index.html","modified":1474605178612,"shasum":"13fc80ae4512325370b5af9b87f299b2c4aae92a"},{"_id":"public/archives/2016/07/index.html","modified":1474605178624,"shasum":"df3d22a6ff211e96a71552a5837a0efa844f62bb"},{"_id":"public/archives/2016/09/index.html","modified":1474605178634,"shasum":"d60e54167f07ade7e95c879df0c1a0321e799f5e"},{"_id":"public/index.html","modified":1474605178656,"shasum":"4b102ff66d1db402321841ca5350d57f5c068289"},{"_id":"public/page/2/index.html","modified":1474605178681,"shasum":"fef31a3f9f1ff765bc8cdd6be9444580a52548ab"},{"_id":"public/page/3/index.html","modified":1474605178697,"shasum":"62a404f453931a5e622f1cb3167a72e50d8dbc84"},{"_id":"public/tags/HTTP/index.html","modified":1474605178702,"shasum":"d8130a6c2eacdc0dfb96327c6587e3462be0ed60"},{"_id":"public/tags/JavaScript/index.html","modified":1474605178713,"shasum":"b720f98bf4c7e6542639b43b86884f25dbaa3c3c"},{"_id":"public/tags/JavaScript/page/2/index.html","modified":1474605178723,"shasum":"0340fb63aa0d849d88428c877377a35e6b84112b"},{"_id":"public/tags/JavaScript/page/3/index.html","modified":1474605178736,"shasum":"0d535497e42cdfce1a1bbe97ceab67ae1c7de97c"},{"_id":"public/tags/redux/index.html","modified":1474605178742,"shasum":"366b66c853f29b6eb8f31b08d2e464363559f39b"},{"_id":"public/tags/react/index.html","modified":1474605178747,"shasum":"6f9e26c61cf869d6e66f8d238ddd8f1ecf59256d"},{"_id":"public/tags/jQuery/index.html","modified":1474605178755,"shasum":"55a7b7c0b42c9e0ebe0d30ebabe8572bb1b4cb83"}],"Category":[{"name":"《JS高程3-笔记》","_id":"citf9olwn0006skv7412h6v76"},{"name":"JavaScript","_id":"citf9oly1000mskv79c3ipwal"},{"name":"模块化","_id":"citf9om86001kskv7l6s91d6b"}],"Data":[],"Page":[{"layout":"default","_content":"<script type=\"text/javascript\" src=\"http://www.qq.com/404/search_children.js\" charset=\"utf-8\" homePageUrl=\"www.yangshengdonghome.com\" homePageName=\"前端之路\"></script>","source":"404.html","raw":"---\nlayout: default\n---\n<script type=\"text/javascript\" src=\"http://www.qq.com/404/search_children.js\" charset=\"utf-8\" homePageUrl=\"www.yangshengdonghome.com\" homePageName=\"前端之路\"></script>","date":"2016-06-22T07:47:16.251Z","updated":"2016-01-11T02:26:30.239Z","path":"404.html","title":"","comments":1,"_id":"citf9olqi0000skv7xq8bgp7t"},{"_content":"i0wnrfk2ui","source":"baidu_verify_i0wnrfk2ui.html","raw":"i0wnrfk2ui","date":"2016-06-22T07:47:16.257Z","updated":"2016-01-12T04:21:56.453Z","path":"baidu_verify_i0wnrfk2ui.html","title":"","comments":1,"layout":"page","_id":"citf9ols10001skv7c02hv07k"}],"Post":[{"title":"通过XMPP构建即时通信","date":"2016-09-23T02:59:24.000Z","_content":"\n# 概述\n{% asset_img first.png %}\n上图是WebChat的连接示意图,图中的CM是指连接管理器(Connection Manager),服务器端与CM之间是XMPP协议的连接,CM与客户端之间是Http协议的连接。\n<!--more-->\n\n# 为什么要有CM\n因为WebChat要部署在浏览器中,浏览器与服务器之间的通信我们最常用的就是Http的请求和响应,浏览器不支持XMPP协议的连接,JS又不能操作TCP建立一个持久连接,所以采用了一个折衷的方案,就是在浏览器与客户端之间建立一个CM服务器,它的作用就是用Http协议模拟与客户端之间的双向的持久化连接,与服务器之间直接用XMPP协议通信,XMPP的规范化组织叫这技术为BOSH。我先介绍前端最关注的BOSH技术,引出它们的实现[strophe.js](http://strophe.im/)与[converse.js](https://conversejs.org/),再介绍一下服务器与CM之间的通信协议XMPP,主要包括怎样找到消息的接收者,怎样传输消息等。\n\n# BOSH实现长连接\n\n## BOSH概述\nBOSH是一种传输协议,它可以利用HTTP协议模拟客户端与服务器之间的双向流传输,它比普通的轮询技术节省了系统开销与网络资源,它主要用于Jabber/XMPP客户端-服务器之间的数据传输,然而BOSH并非为XMPP定制,它也可以用于别的传输。\n\n## 原理\n\n大部分BOSH技术的实现都是在服务器与客户端之间架设一个连接管理器(CM)来处理HTTP连接。\n\nCM有以下两个作用:\n\n1. 模拟CM与客户端之间的双向通信。\n2. 将消息体用<body/>包裹起来。\n\n{% asset_img cm-http.png %}\n\n客户端与CM之间的通信遵守如下规则:\n\n1. 当客户端发送一个request给CM,CM不会立刻返回一个response给客户端,相反它会保持这个request的open状态,直到CM从服务器接收到一个新的数据它才会将数据response给客户端。收到response后,客户端会立即发送一个新的request给CM,保持客户端与CM总是处于连接状态。\n2. 当在一定时间之后(通常为几秒钟),CM如果始终没有新的数据response给客户端,CM会发送一个空的response给客户端,这样做的目的是检测CM与客户端是否处于连接状态,因为诸如防火墙之类的程序会断开很久保持静默的连接。\n3. 在HTTP1.1协议中默认开启了持久化连接(就是Connection: keep-alive),CM与客户端一个request和response之后仍然保持连接状态,等待客户端发起发起下一个请求,这样做减小了套接字创建的开销。\n4. 当客户端有数据要发送给服务器的时候,客户端先发送请求给CM,这时CM会先立即响应先前的request,发送response给客户端,也就是说它会先立即处理完已经存在的连接,再腾出手来处理另一个套接字发来的请求。这样CM会遵守规则1,收到request后不立即返回response给客户端,等到有真正的数据从服务器传来的时候CM再response给客户端。\n\n连接示意图如下图所示:\n\n{% asset_img 具体作用.png %}\n\n如上图所示,当浏览器发送一个request给CM后,CM不立即返回response,当浏览器又发送一个request后,CM先立即返回上一个response,当CM有消息要发送给浏览器时,再response给浏览器,浏览器收到一个response之后,又发送一个新的request给CM,保持连接状态。\n\n## <body\\/>\n\n{% asset_img body.png %}\n\n每一个request或者response的消息体(body)都会被一个命名空间为[httpbind](http://jabber.org/protocol/httpbind)的`<body>`元素包裹起来。具体的作用参考:[xep-0206](http://xmpp.org/extensions/xep-0206.html)。\n\n## 传输信息的格式\n\n信息传递的格式都是XML,之所以用XML是因为XMPP本身就是一种XML流技术。这里有三种传输信息的格式,分别代表不同的用途:\n\n1. `<message>`\n\n\n{% asset_img message.png %}\n\n聊天信息的发送和接收是通过message节来实现。例如xxg1@host发送一条信息”你好“给xxg2@host,xxg1@host客户端会将下面的这段XML发送到XMPP服务器,服务器再推送给xxg2@host客户端。其中`<message>`的from属性是发送者,to属性是接收者,`<body>`子元素的内容就是聊天信息。\n\n关于type的种类:\n\n+ normal:单个消息,回应可能会或者可能不会很快到来。\n+ chat:代表这类消息是私聊,两个用户间的对话。\n+ groupchat:代表这类消息是多用户聊天室中的消息。\n+ headline:发送的警告或通告,并不期望有回应\n\n每个用户都需要一个地址,以便服务器可以在网络上定位我们,就相当于IP地址,称为JID。\n\n2. `<iq>`\n\niq即info/Query,发送的iq节消息必须总是收到一个回复,其采用“请求-响应”机制,相当于一个Ajax请求。下面的例子是客户端通过`<iq>`请求获取联系人,XMPP服务器将结果返回:\n\n客户端请求获取联系人:\n{% asset_img getMessage.png %}\n服务器返回结果:\n{% asset_img getMessageResponse.png %}\n\n3. <presence>\n\n可用于表明用户的状态,例如用户状态改变成“Do not disturb”(“请勿打扰”),会向服务器发送:\n{% asset_img userStatus.png %}\n\n## strophe.js\n\nstrophe.js是一个XMPP javascript库,作用\n\n1. 操作XML节点\n2. 管理连接\nAPI Document:[strophe-js](http://strophe.im/strophejs/doc/1.2.3/files/strophe-js.html)\n\n### 结构图:\n{% asset_img 结构图.png %}\n\n+ Builder用于操作XML节点,Handler相当于我们常用的event.js,管理回调方法;Connection是最重要的类,用于管理向XMPP服务器发送的连接。\n+ 在strophe.js中持久化的连接有两种实现方法,一种是Bosh一种是websocket,request用于新建xmlHttprequest请求。\n\n### 例子\n\n以下是一个利用strophe.js实现Web私聊的例子:\n\n```\n// XMPP服务器BOSH地址\nvar BOSH_SERVICE = 'http://host:5280';\n\n// XMPP连接\nvar connection = null;\n\n// 当前状态是否连接\nvar connected = false;\n\n// 当前登录的JID\nvar jid = \"\";\n\n// 连接状态改变的事件\nfunction onConnect(status) {\n console.log(status)\n //Strophe.Status查询连接状态\n if (status == Strophe.Status.CONNFAIL) {\n alert(\"连接失败!\");\n } else if (status == Strophe.Status.AUTHFAIL) {\n alert(\"登录失败!\");\n } else if (status == Strophe.Status.DISCONNECTED) {\n alert(\"连接断开!\");\n connected = false;\n } else if (status == Strophe.Status.CONNECTED) {\n alert(\"连接成功,可以开始聊天了!\");\n connected = true;\n\n // 当接收到<message>节点,调用onMessage回调函数\n connection.addHandler(onMessage, null, 'message', null, null, null);\n\n }\n}\n\n// 接收到<message>\nfunction onMessage(msg) {\n\n // 解析出<message>的from、type属性,以及body子元素\n var from = msg.getAttribute('from');\n var type = msg.getAttribute('type');\n var elems = msg.getElementsByTagName('body');\n\n if (type == \"chat\" && elems.length > 0) {\n var body = elems[0];\n $(\"#msg\").append(from + \":<br>\" + Strophe.getText(body) + \"<br>\");//Strophe.getText得到body中的text节点\n }\n return true;\n}\n\n$(document).ready(function() {\n\n // 通过BOSH连接XMPP服务器\n $('#btn-login').click(function() {\n if(!connected) {\n connection = new Strophe.Connection(BOSH_SERVICE);\n connection.connect($(\"#input-jid\").val(), $(\"#input-pwd\").val(), onConnect);\n jid = $(\"#input-jid\").val();\n }\n });\n\n // 发送消息\n $(\"#btn-send\").click(function() {\n if(connected) {\n if($(\"#input-contacts\").val() == '') {\n alert(\"请输入联系人!\");\n return;\n }\n\n // 创建一个<message>元素并发送\n //c方法是增加一个子节点给当前的元素\n var msg = $msg({\n to: $(\"#input-contacts\").val(),\n from: jid,\n type: 'chat'\n }).c(\"body\", null, $(\"#input-msg\").val());\n connection.send(msg.tree());\n\n $(\"#msg\").append(jid + \":<br>\" + $(\"#input-msg\").val() + \"<br>\");\n $(\"#input-msg\").val('');\n } else {\n alert(\"请先登录!\");\n }\n });\n});\n```\n\n### 查看源码\n\n* strophe.builder 1649行\n* 建立Connection 2171行\n* 建立连接 4302行\n* 发送请求 4747行\n\n## converse.js\nconverse.js是一个开源的XMPP聊天客户端,它使用strophe.js作为它实现XMPP通信功能的库。\n\n# XMPP\n\n## JID\n\nXMPP是一种基于XML流技术的通信协议,像Email地址一样,XMPP实体也有一个网络中唯一的地址,用user@domain/resource表示,例如WebChat中我的地址就是iyis2569@ejabhost2/web-58531850,User为用户名,Domain表示服务器地址,WebChat可以在不同的地方同时登录,resource就可以区分同一账号在多处登陆,QQ、MSN都不能实现这种功能。\n\n## 架构\n{% asset_img 架构.png %}\n\n在实践中,XMPP是一个包含了很多互相通信的客户端和服务器的网络,因此如图客户端1与客户端2可以通过服务器1与服务器2交互信息,这种分布式的网络很常见,例如Email网络。\n在XMPP端到端的通信中逻辑上是点到点,物理则是客户端到服务器再到服务器再到客户端。\n\n客户端与服务器、服务器与服务器连接的流程如下:\n\n1. 确定要连接的服务器的IP地址和端口号,也就是域名解析,类DNS解析\n2. 打开一个TCP连接\n3. 通过TCP打开一个XML流\n4. 传输XML节点\n5. 关闭XML流\n6. 关闭TCP连接\n\n## 绑定到TCP的持久化连接\nXMPP协议的重点就是将一个JID的消息发送到另一个JID去,这很像Email协议,但XMPP更强调实时性。之所以XMPP能做到即时通信就是因为它是一种持久化的连接。每个点对点都建立了基于TCP长连接的持久XML流来保持可通讯状态,这样可以双方进行实时的信息交互。\n\n## XML流\n\n定义:XML流是一个容器,用于任何两个实体间通过网络进行XML元素的交换。XML流以`<stream>`标签开始,以`</stream>`标签结束。在XML流处于活动状态期间,可以通过这个流发送不限数量的XML元素。\nXML节:\n打开流:\n\n上面说的打开一个TCP连接后,客户端向服务器发送一段XML流:\n\n```\n<?xml version='1.0'?>\n<stream:stream\n from='[email protected]'\n to='im.example.com'\n version='1.0'\n xml:lang='en'\n xmlns='jabber:client'\n xmlns:stream='http://etherx.jabber.org/streams'>\n```\n这时服务器会发送一段stream来回应客户端\n```\n<?xml version='1.0'?>\n<stream:stream\n from='im.example.com'\n id='++TR84Sm6A3hnt3Q065SnAbbk3Y='\n to='[email protected]'\n version='1.0'\n xml:lang='en'\n xmlns='jabber:client'\n xmlns:stream='http://etherx.jabber.org/streams'>\n```\n这时就打开了一段流,服务器与客户端就可以交互XML节了。\n\n# Websocket\n\n# 参考文献\n1、BOSH:http://xmpp.org/extensions/xep-0124.html\n\n2、XMPP OVER BOSH:http://xmpp.org/extensions/xep-0206.html\n\n3、百度百科\n\n4、strophe.js:http://strophe.im/strophejs/doc/1.2.3/files/strophe-js.html#Strophe.Connection.Strophe.Connection\n\n5、converse.js:https://conversejs.org/\n\n6、xmpp博客:http://www.jianshu.com/p/a94749385755\n\n7、xmpp Core:http://xmpp.org/rfcs/rfc6120.html","source":"_posts/通过XMPP构建即时通信-2016-09-23.md","raw":"title: 通过XMPP构建即时通信\ndate: 2016-09-23 10:59:24\ntags:\n- HTTP\n---\n\n# 概述\n{% asset_img first.png %}\n上图是WebChat的连接示意图,图中的CM是指连接管理器(Connection Manager),服务器端与CM之间是XMPP协议的连接,CM与客户端之间是Http协议的连接。\n<!--more-->\n\n# 为什么要有CM\n因为WebChat要部署在浏览器中,浏览器与服务器之间的通信我们最常用的就是Http的请求和响应,浏览器不支持XMPP协议的连接,JS又不能操作TCP建立一个持久连接,所以采用了一个折衷的方案,就是在浏览器与客户端之间建立一个CM服务器,它的作用就是用Http协议模拟与客户端之间的双向的持久化连接,与服务器之间直接用XMPP协议通信,XMPP的规范化组织叫这技术为BOSH。我先介绍前端最关注的BOSH技术,引出它们的实现[strophe.js](http://strophe.im/)与[converse.js](https://conversejs.org/),再介绍一下服务器与CM之间的通信协议XMPP,主要包括怎样找到消息的接收者,怎样传输消息等。\n\n# BOSH实现长连接\n\n## BOSH概述\nBOSH是一种传输协议,它可以利用HTTP协议模拟客户端与服务器之间的双向流传输,它比普通的轮询技术节省了系统开销与网络资源,它主要用于Jabber/XMPP客户端-服务器之间的数据传输,然而BOSH并非为XMPP定制,它也可以用于别的传输。\n\n## 原理\n\n大部分BOSH技术的实现都是在服务器与客户端之间架设一个连接管理器(CM)来处理HTTP连接。\n\nCM有以下两个作用:\n\n1. 模拟CM与客户端之间的双向通信。\n2. 将消息体用<body/>包裹起来。\n\n{% asset_img cm-http.png %}\n\n客户端与CM之间的通信遵守如下规则:\n\n1. 当客户端发送一个request给CM,CM不会立刻返回一个response给客户端,相反它会保持这个request的open状态,直到CM从服务器接收到一个新的数据它才会将数据response给客户端。收到response后,客户端会立即发送一个新的request给CM,保持客户端与CM总是处于连接状态。\n2. 当在一定时间之后(通常为几秒钟),CM如果始终没有新的数据response给客户端,CM会发送一个空的response给客户端,这样做的目的是检测CM与客户端是否处于连接状态,因为诸如防火墙之类的程序会断开很久保持静默的连接。\n3. 在HTTP1.1协议中默认开启了持久化连接(就是Connection: keep-alive),CM与客户端一个request和response之后仍然保持连接状态,等待客户端发起发起下一个请求,这样做减小了套接字创建的开销。\n4. 当客户端有数据要发送给服务器的时候,客户端先发送请求给CM,这时CM会先立即响应先前的request,发送response给客户端,也就是说它会先立即处理完已经存在的连接,再腾出手来处理另一个套接字发来的请求。这样CM会遵守规则1,收到request后不立即返回response给客户端,等到有真正的数据从服务器传来的时候CM再response给客户端。\n\n连接示意图如下图所示:\n\n{% asset_img 具体作用.png %}\n\n如上图所示,当浏览器发送一个request给CM后,CM不立即返回response,当浏览器又发送一个request后,CM先立即返回上一个response,当CM有消息要发送给浏览器时,再response给浏览器,浏览器收到一个response之后,又发送一个新的request给CM,保持连接状态。\n\n## <body\\/>\n\n{% asset_img body.png %}\n\n每一个request或者response的消息体(body)都会被一个命名空间为[httpbind](http://jabber.org/protocol/httpbind)的`<body>`元素包裹起来。具体的作用参考:[xep-0206](http://xmpp.org/extensions/xep-0206.html)。\n\n## 传输信息的格式\n\n信息传递的格式都是XML,之所以用XML是因为XMPP本身就是一种XML流技术。这里有三种传输信息的格式,分别代表不同的用途:\n\n1. `<message>`\n\n\n{% asset_img message.png %}\n\n聊天信息的发送和接收是通过message节来实现。例如xxg1@host发送一条信息”你好“给xxg2@host,xxg1@host客户端会将下面的这段XML发送到XMPP服务器,服务器再推送给xxg2@host客户端。其中`<message>`的from属性是发送者,to属性是接收者,`<body>`子元素的内容就是聊天信息。\n\n关于type的种类:\n\n+ normal:单个消息,回应可能会或者可能不会很快到来。\n+ chat:代表这类消息是私聊,两个用户间的对话。\n+ groupchat:代表这类消息是多用户聊天室中的消息。\n+ headline:发送的警告或通告,并不期望有回应\n\n每个用户都需要一个地址,以便服务器可以在网络上定位我们,就相当于IP地址,称为JID。\n\n2. `<iq>`\n\niq即info/Query,发送的iq节消息必须总是收到一个回复,其采用“请求-响应”机制,相当于一个Ajax请求。下面的例子是客户端通过`<iq>`请求获取联系人,XMPP服务器将结果返回:\n\n客户端请求获取联系人:\n{% asset_img getMessage.png %}\n服务器返回结果:\n{% asset_img getMessageResponse.png %}\n\n3. <presence>\n\n可用于表明用户的状态,例如用户状态改变成“Do not disturb”(“请勿打扰”),会向服务器发送:\n{% asset_img userStatus.png %}\n\n## strophe.js\n\nstrophe.js是一个XMPP javascript库,作用\n\n1. 操作XML节点\n2. 管理连接\nAPI Document:[strophe-js](http://strophe.im/strophejs/doc/1.2.3/files/strophe-js.html)\n\n### 结构图:\n{% asset_img 结构图.png %}\n\n+ Builder用于操作XML节点,Handler相当于我们常用的event.js,管理回调方法;Connection是最重要的类,用于管理向XMPP服务器发送的连接。\n+ 在strophe.js中持久化的连接有两种实现方法,一种是Bosh一种是websocket,request用于新建xmlHttprequest请求。\n\n### 例子\n\n以下是一个利用strophe.js实现Web私聊的例子:\n\n```\n// XMPP服务器BOSH地址\nvar BOSH_SERVICE = 'http://host:5280';\n\n// XMPP连接\nvar connection = null;\n\n// 当前状态是否连接\nvar connected = false;\n\n// 当前登录的JID\nvar jid = \"\";\n\n// 连接状态改变的事件\nfunction onConnect(status) {\n console.log(status)\n //Strophe.Status查询连接状态\n if (status == Strophe.Status.CONNFAIL) {\n alert(\"连接失败!\");\n } else if (status == Strophe.Status.AUTHFAIL) {\n alert(\"登录失败!\");\n } else if (status == Strophe.Status.DISCONNECTED) {\n alert(\"连接断开!\");\n connected = false;\n } else if (status == Strophe.Status.CONNECTED) {\n alert(\"连接成功,可以开始聊天了!\");\n connected = true;\n\n // 当接收到<message>节点,调用onMessage回调函数\n connection.addHandler(onMessage, null, 'message', null, null, null);\n\n }\n}\n\n// 接收到<message>\nfunction onMessage(msg) {\n\n // 解析出<message>的from、type属性,以及body子元素\n var from = msg.getAttribute('from');\n var type = msg.getAttribute('type');\n var elems = msg.getElementsByTagName('body');\n\n if (type == \"chat\" && elems.length > 0) {\n var body = elems[0];\n $(\"#msg\").append(from + \":<br>\" + Strophe.getText(body) + \"<br>\");//Strophe.getText得到body中的text节点\n }\n return true;\n}\n\n$(document).ready(function() {\n\n // 通过BOSH连接XMPP服务器\n $('#btn-login').click(function() {\n if(!connected) {\n connection = new Strophe.Connection(BOSH_SERVICE);\n connection.connect($(\"#input-jid\").val(), $(\"#input-pwd\").val(), onConnect);\n jid = $(\"#input-jid\").val();\n }\n });\n\n // 发送消息\n $(\"#btn-send\").click(function() {\n if(connected) {\n if($(\"#input-contacts\").val() == '') {\n alert(\"请输入联系人!\");\n return;\n }\n\n // 创建一个<message>元素并发送\n //c方法是增加一个子节点给当前的元素\n var msg = $msg({\n to: $(\"#input-contacts\").val(),\n from: jid,\n type: 'chat'\n }).c(\"body\", null, $(\"#input-msg\").val());\n connection.send(msg.tree());\n\n $(\"#msg\").append(jid + \":<br>\" + $(\"#input-msg\").val() + \"<br>\");\n $(\"#input-msg\").val('');\n } else {\n alert(\"请先登录!\");\n }\n });\n});\n```\n\n### 查看源码\n\n* strophe.builder 1649行\n* 建立Connection 2171行\n* 建立连接 4302行\n* 发送请求 4747行\n\n## converse.js\nconverse.js是一个开源的XMPP聊天客户端,它使用strophe.js作为它实现XMPP通信功能的库。\n\n# XMPP\n\n## JID\n\nXMPP是一种基于XML流技术的通信协议,像Email地址一样,XMPP实体也有一个网络中唯一的地址,用user@domain/resource表示,例如WebChat中我的地址就是iyis2569@ejabhost2/web-58531850,User为用户名,Domain表示服务器地址,WebChat可以在不同的地方同时登录,resource就可以区分同一账号在多处登陆,QQ、MSN都不能实现这种功能。\n\n## 架构\n{% asset_img 架构.png %}\n\n在实践中,XMPP是一个包含了很多互相通信的客户端和服务器的网络,因此如图客户端1与客户端2可以通过服务器1与服务器2交互信息,这种分布式的网络很常见,例如Email网络。\n在XMPP端到端的通信中逻辑上是点到点,物理则是客户端到服务器再到服务器再到客户端。\n\n客户端与服务器、服务器与服务器连接的流程如下:\n\n1. 确定要连接的服务器的IP地址和端口号,也就是域名解析,类DNS解析\n2. 打开一个TCP连接\n3. 通过TCP打开一个XML流\n4. 传输XML节点\n5. 关闭XML流\n6. 关闭TCP连接\n\n## 绑定到TCP的持久化连接\nXMPP协议的重点就是将一个JID的消息发送到另一个JID去,这很像Email协议,但XMPP更强调实时性。之所以XMPP能做到即时通信就是因为它是一种持久化的连接。每个点对点都建立了基于TCP长连接的持久XML流来保持可通讯状态,这样可以双方进行实时的信息交互。\n\n## XML流\n\n定义:XML流是一个容器,用于任何两个实体间通过网络进行XML元素的交换。XML流以`<stream>`标签开始,以`</stream>`标签结束。在XML流处于活动状态期间,可以通过这个流发送不限数量的XML元素。\nXML节:\n打开流:\n\n上面说的打开一个TCP连接后,客户端向服务器发送一段XML流:\n\n```\n<?xml version='1.0'?>\n<stream:stream\n from='[email protected]'\n to='im.example.com'\n version='1.0'\n xml:lang='en'\n xmlns='jabber:client'\n xmlns:stream='http://etherx.jabber.org/streams'>\n```\n这时服务器会发送一段stream来回应客户端\n```\n<?xml version='1.0'?>\n<stream:stream\n from='im.example.com'\n id='++TR84Sm6A3hnt3Q065SnAbbk3Y='\n to='[email protected]'\n version='1.0'\n xml:lang='en'\n xmlns='jabber:client'\n xmlns:stream='http://etherx.jabber.org/streams'>\n```\n这时就打开了一段流,服务器与客户端就可以交互XML节了。\n\n# Websocket\n\n# 参考文献\n1、BOSH:http://xmpp.org/extensions/xep-0124.html\n\n2、XMPP OVER BOSH:http://xmpp.org/extensions/xep-0206.html\n\n3、百度百科\n\n4、strophe.js:http://strophe.im/strophejs/doc/1.2.3/files/strophe-js.html#Strophe.Connection.Strophe.Connection\n\n5、converse.js:https://conversejs.org/\n\n6、xmpp博客:http://www.jianshu.com/p/a94749385755\n\n7、xmpp Core:http://xmpp.org/rfcs/rfc6120.html","slug":"通过XMPP构建即时通信","published":1,"updated":"2016-09-23T04:31:43.988Z","_id":"citf9olta0002skv7qjlpe2cb","comments":1,"layout":"post","photos":[],"link":""},{"title":"运算符","date":"2016-02-25T06:19:16.000Z","comments":1,"_content":"运算符是处理数据的基本方法,用来从现有数据得到新的数据。JavaScript与其他编程语言一样,提供了多种运算符。\n<!--more-->\n## 算术运算符\n\nJavaScript提供9个算术运算符。\n\n- **加法运算符**(Addition):`x + y`\n- **减法运算符**(Subtraction): `x - y`\n- **乘法运算符**(Multiplication): `x * y`\n- **除法运算符**(Division):`x / y`\n- **余数运算符**(Remainder):`x % y`\n- **自增运算符**(Increment):`++x` 或者 `x++`\n- **自减运算符**(Decrement):`--x` 或者 `x--`\n- **数值运算符**(Convert to number): `+x`\n- **负数值运算符**(Negate):`-x`\n\n减法、乘法、除法运算法比较单纯,就是执行相应的数学运算。下面介绍其他几个算术运算符。\n\n### 加法运算符\n\n加法运算符(`+`)需要注意的地方是,它除了用于数值的相加,还能用于字符串的连接。\n\n```javascript\n1 + 1 // 2\n'1' + '1' // \"11\"\n'1.1' + '1.1' // \"1.11.1\"\n```\n\n上面代码中,如果两个运算子都是数值,加号运算符就执行数值的加法运算;如果两个运算子都是字符串,加号运算符就执行字符串的连接运算,变成字符串连接运算符。这种由于参数不同,而改变自身行为的现象,叫做“重载”(overload)。\n\n如果两个运算子之中,一个是字符串,另一个是数值,加法运算符执行字符串连接运算。\n\n```javascript\n1 + '1' // \"11\"\n```\n\n上面代码表示,两个运算子之中有一个是字符串,另一个运算子就会被自动转为字符串。\n\n```javascript\n'3' + 4 + 5 // \"345\"\n3 + 4 + '5' // \"75\"\n```\n\n上面代码中,由于加法运算符遇到字符串,会发生重载,导致运算结果的不同。\n\n由于这个特性,下面的写法有时用于将一个值转为字符串。\n\n```javascript\nx + ''\n```\n\n上面代码表示,一个值加上空字符串,会使得该值转为字符串形式。\n\n布尔值和复合类型的值,也可以使用加法运算符,但是会导致数据类型的自动转换,关于这方面的详细讨论,参见[《数据类型转换》](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n**除加法运算符以外的其他算术运算符,都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。**\n\n```javascript\n1 - '1' // 0\n+'3' // 3\n-true // -1\n```\n\n上面代码表示,减法运算符将字符串“1”自动转为数值1,数值运算符(+)将字符串“3”转为数值3,求负运算符(-)将布尔值true转为-1。\n\n由于加法运算符与其他算术运算符的这种差异,会导致一些意想不到的结果,计算时要小心。\n\n```javascript\nvar now = new Date();\ntypeof (now + 1) // \"string\"\ntypeof (now - 1) // \"number\"\n```\n\n上面代码中,`now`是一个Date对象的实例。加法运算时,`now`转为字符串,加一个数字,得到还是字符串;减法运算时,now转为数值,减一个数字,得到的是数字。\n<span style=\"color:red;\">Date对象比较特殊在和一个任意类型的数据相加的时候,会优先调用toString()方法,其他对象都是优先valueOf()</span>\n\n### 余数运算符\n\n余数运算符(`%`)返回前一个运算子被后一个运算子除,所得的余数。\n\n```javascript\n12 % 5 // 2\n```\n\n需要注意的是,运算结果的正负号由第一个运算子的正负号决定。\n\n```javascript\n-1 % 2 // -1\n1 % -2 // 1\n```\n\n为了得到正确的负数的余数值,需要先使用绝对值函数。\n\n```javascript\n// 错误的写法\nfunction isOdd(n) {\n return n % 2 === 1;\n}\nisOdd(-5) // false\nisOdd(-4) // false\n\n// 正确的写法\nfunction isOdd(n) {\n return Math.abs(n % 2) === 1;\n}\nisOdd(-5) // true\nisOdd(-4) // false\n```\n\n余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。\n\n```javascript\n6.5 % 2.1\n// 0.19999999999999973\n```\n\n### 自增和自减运算符\n\n自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量。\n\n```javascript\nvar x = 1;\n++x // 2\nx // 2\n\n--x // 1\nx // 1\n```\n\n上面代码的变量x自增后,返回2,再进行自减,返回1。这两种情况都会使得,原始变量`x`的值发生改变。\n\n自增和自减运算符有一个需要注意的地方,就是放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。\n\n```javascript\nvar x = 1;\nvar y = 1;\n\nx++ // 1\n++y // 2\n```\n\n上面代码中,`x`是先返回当前值,然后自增,所以得到1;`y`是先自增,然后返回新的值,所以得到2。\n\n### 数值运算符,负数值运算符\n\n数值运算符(`+`)同样使用加号,但是加法运算符是二元运算符(需要两个操作数),它是一元运算符(只需要一个操作数)。\n\n数值运算符的作用在于可以将任何值转为数值(与`Number`函数的作用相同)。\n\n```javascript\n+true // 1\n+[] // 0\n+{} // NaN\n```\n\n上面代码表示,非数值类型的值经过数值运算符以后,都变成了数值(最后一行`NaN`也是数值)。具体的类型转换规则,参见[《数据类型转换》](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n求负运算符(`-`),也同样具有将一个值转为数值的功能,所以下面的写法等同于数值运算符。\n\n```javascript\nvar x = 1;\n-x // -1\n-(-x) // 1\n\nvar y = \"111\";\n-y // -111\n```\n\n上面代码最后一行的圆括号不可少,否则会变成递减运算符。从中可以看出,数值运算符和负数值运算符,不会改变原始变量的值。\n\n## 赋值运算符\n\n赋值运算符(Assignment Operators)用于给变量赋值。\n\n### 何为\"左值\"?\n形如`A=B`的表达式称为赋值表达式。其中A和B又分别可以是表达式。B可以是任意表达式,但是A必须是一个左值。所谓左值,就是可以被赋值的表达式,在ES规范中是用内部类型引用(Reference)描述的。例如:\n * 表达式`foo.bar`可以作为一个左值,表示对`foo`这个对象中`bar`这个名称的引用;\n * 变量`email`可以作为一个左值,表示对当前执行环境中的环境记录项`envRec`中`email`这个名称的引用;\n * 同样地,函数名`func`可以做左值,然而函数调用表达式`func(a, b)`不可以。\n\n那么JS引擎是怎样计算一般的赋值表达式 A = B的呢?简单地说,按如下步骤:\n 1. 计算表达式A,得到一个引用refA;\n 2. 计算表达式B,得到一个值valueB;\n 3. 将valueB赋给refA指向的名称绑定;\n 4. 返回valueB。\n\n### 结合性\n赋值表达式是右结合的。这意味着:\n\n A1 = A2 = A3 = A4\n等价于\n\n A1 = (A2 = (A3 = A4))\n### 连等的解析\n好了,有了上面两部分的知识。下面来看一下JS引擎是怎样运算连等赋值表达式的。\n\n Exp1 = Exp2 = Exp3 = Exp4\n首先根据右结合性,可以转换成\n\n Exp1 = (Exp2 = (Exp3 = Exp4))\n\n然后,我们已经知道对于单个赋值运算,JS引擎总是先计算左边的操作数,再计算右边的操作数。所以接下来的步骤就是:\n\n 1. 计算Exp1,得到Ref1\n 2. 计算Exp2,得到Ref2\n 3. 计算Exp3,得到Ref3\n 4. 计算Exp4,得到Value4。\n\n现在变成了这样的:\n\n Ref1 = (Ref2 = (Ref3 = Value4))\n\n接下来的步骤是:\n\n 1. 将Value4赋给Exp3;\n 2. 将Value4赋给Exp2;\n 3. 将Value4赋给Exp1;\n 4. 返回表达式最终的结果Value4。\n\n总结一下就是:\n> 先从左到右解析各个引用,然后计算最右侧的表达式的值,最后把值从右到左赋给各个引用。\n\n最常见的赋值运算符,当然就是等号(`=`),表达式`x = y`表示将`y`的值赋给`x`。除此之外,JavaScript还提供其他11个赋值运算符。\n\n```javascript\nx += y // 等同于 x = x + y\nx -= y // 等同于 x = x - y\nx *= y // 等同于 x = x * y\nx /= y // 等同于 x = x / y\nx %= y // 等同于 x = x % y\nx >>= y // 等同于 x = x >> y\nx <<= y // 等同于 x = x << y\nx >>>= y // 等同于 x = x >>> y\nx &= y // 等同于 x = x & y\nx |= y // 等同于 x = x | y\nx ^= y // 等同于 x = x ^ y\n```\n\n上面11个赋值运算符,都是先进行某种运算,然后将得到值返回给左边的变量。\n\n### 小问题\n\n```javascript\nvar a = {n: 1};\nvar b = a;\na.x = a = {n: 2};\nalert(a.x); // --> undefined\nalert(b.x); // --> {n: 2}\n```\n首先前两个var语句执行完后,a和b都指向同一个对象`{n: 1}`(为方便描述,下面称为对象`N1`)。然后来看\n\n a.x = a = {n: 2};\n根据前面的知识,首先依次计算表达式`a.x`和`a`,得到两个引用。其中`a.x`表示对象`N1`中的`x`,而`a`相当于`envRec.a`,即当前环境记录项中的`a`。所以此时可以写出如下的形式:\n\n [[N1]].x = [[encRec]].a = {n: 2};//其中,[[]]表示引用指向的对象。\n接下来,将`{n: 2}`赋值给`[[encRec]].a`,即将`{n: 2}`绑定到当前上下文中的名称`a`。\n接下来,将同一个`{n: 2}`赋值给`[[N1]].x`,即将`{n: 2}`绑定到`N1`中的名称`x`。\n由于`b`仍然指向`N1`,所以此时有\n\n b <=> N1 <=> {n: 1, x: {n: 2}}\n而a被重新赋值了,所以\n\n a <=> {n: 2}\n并且\n\n a === b.x\n\n如果你明白了上面所有的内容,应该会明白`a.x = a = {n:2};`与`b.x = a = {n:2};`是完全等价的。因为在解析`a.x`或`b.x`的那个时间点。`a`和`b`这两个名称指向同一个对象,就像C++中同一个对象可以有多个引用一样。而在这个时间点之后,不论是`a.x`还是`b.x`,其实早就不存在了,它已经变成了那个`内存中的对象.x`了。\n\n## 比较运算符\n\n比较运算符比较两个值,然后返回一个布尔值,表示是否满足比较条件。JavaScript提供了8个比较运算符。\n\n- `==` 相等\n- `===` 严格相等\n- `!=` 不相等\n- `!==` 严格不相等\n- `<` 小于\n- `<=` 小于或等于\n- `\\>` 大于\n- `\\>=` 大于或等于\n\n其中,比较两个值是否相等的运算符有两个:一个是相等运算符(`==`),另一个是严格相等运算符(`===`)。\n\n相等运算符(`==`)比较两个值是否相等,严格相等运算符(`===`)比较它们是否为“同一个值”。两者的一个重要区别是,如果两个值不是同一类型,严格相等运算符(`===`)直接返回false,而相等运算符(`==`)会将它们转化成同一个类型,再用严格相等运算符进行比较。\n\n### 严格相等运算符\n\n严格相等运算符的运算规则如下。\n\n**(1)不同类型的值**\n\n如果两个值的类型不同,直接返回`false`。\n\n```javascript\n1 === \"1\" // false\ntrue === \"true\" // false\n```\n\n上面代码比较数值的`1`与字符串的“1”、布尔值的`true`与字符串“true”,因为类型不同,结果都是`false`。\n\n**(2)同一类的原始类型值**\n\n同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false。\n\n```javascript\n1 === 0x1 // true\n```\n\n上面代码比较十进制的1与十六进制的1,因为类型和值都相同,返回`true`。\n\n需要注意的是,`NaN`与任何值都不相等(包括自身)。另外,正0等于负0。\n\n```javascript\nNaN === NaN // false\n+0 === -0 // true\n```\n\n**(3)同一类的复合类型值**\n\n两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象。\n\n```javascript\n({}) === {} // false\n[] === [] // false\n(function (){}) === function (){} // false\n```\n\n上面代码分别比较两个空对象、两个空数组、两个空函数,结果都是不相等。原因是对于复合类型的值,严格相等运算比较的是它们的内存地址是否一样,而上面代码中空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是`false`。另外,之所以要把第一个空对象放在括号内,是为了避免JavaScript引擎把这一行解释成代码块,从而报错;把第一个空函数放在括号内,是为了避免这一行被解释成函数的定义。\n\n如果两个变量指向同一个复合类型的数据,则它们相等。\n\n```javascript\nvar v1 = {};\nvar v2 = v1;\nv1 === v2 // true\n```\n\n**(4)undefined和null**\n\n`undefined`和`null`与自身严格相等。\n\n```javascript\nundefined === undefined // true\nnull === null // true\n```\n\n由于变量声明后默认值是`undefined`,因此两个只声明未赋值的变量是相等的。\n\n```javascript\nvar v1;\nvar v2;\nv1 === v2 // true\n```\n\n**(5)严格不相等运算符**\n\n严格相等运算符有一个对应的“严格不相等运算符”(`!==`),两者的运算结果正好相反。\n\n```javascript\n1 !== '1' // true\n```\n\n### 相等运算符\n\n相等运算符在比较相同类型的数据时,与严格相等运算符完全一样。\n\n在比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。类型转换规则如下:\n\n**(1)原始类型的值**\n\n原始类型的数据会转换成数值类型再进行比较。\n\n```javascript\n1 == true // true\n0 == false // true\n\n\"true\" == true // false\n\n'' == 0 // true\n\n'' == false // true\n'1' == true // true\n\n'2' == true // false\n2 == true // false\n2 == false // false\n\n'\\n 123 \\t' == 123 // true\n// 因为字符串转为数字时,省略前置和后置的空格\n```\n\n上面代码将字符串和布尔值都转为数值,然后再进行比较。字符串与布尔值的类型转换规则,参见[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n**(2)对象与原始类型值比较**\n\n对象(这里指广义的对象,包括数值和函数)与原始类型的值比较时,对象转化成原始类型的值,再进行比较。\n\n```javascript\n[1] == 1 // true\n[1] == '1' // true\n[1] == true // true\n```\n\n上面代码将只含有数值1的数组与原始类型的值进行比较,数组`[1]`会被自动转换成数值`1`,因此结果都是`true`。数组的类型转换规则,参见[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n**(3)undefined和null**\n\nundefined和null与其他类型的值比较时,结果都为false,它们互相比较时结果为true。\n\n```javascript\n\nfalse == null // false\n0 == null // false\n\nundefined == null // true\n\n```\n\n**(4)相等运算符的缺点**\n\n相等运算符隐藏的类型转换,会带来一些违反直觉的结果。\n\n```javascript\n\n'' == '0' // false\n0 == '' // true\n0 == '0' // true\n\nfalse == 'false' // false\nfalse == '0' // true\n\nfalse == undefined // false\nfalse == null // false\nnull == undefined // true\n\n' \\t\\r\\n ' == 0 // true\n\n```\n\n上面这些表达式都很容易出错,因此不要使用相等运算符(==),最好只使用严格相等运算符(===)。\n\n**(5)不相等运算符**\n\n相等运算符有一个对应的“不相等运算符”(!=),两者的运算结果正好相反。\n\n```javascript\n\n1 != \"1\" // false\n\n```\n**(6)详细比较过程**\n摘录[ES5标准](https://www.w3.org/html/ig/zh/wiki/ES5/expressions#.E7.AD.89.E4.BA.8E.E8.BF.90.E7.AE.97.E7.AC.A6.EF.BC.88.3D.3D.EF.BC.89):以 x 和 y 为值进行 x == y 比较会产生的结果可为 true 或 false。比较的执行步骤如下:\n1. 若 Type(x) 与 Type(y) 相同, 则\n 1. 若 Type(x) 为 Undefined, 返回 true。\n 2. 若 Type(x)为 Null, 返回 true。\n 3. 若 Type(x)为 Number,则\n 4. 若 x 为 NaN,返回 false。\n 5. 若 y 为 NaN,返回 false。\n 6. 若 x 与 y 为相等数值,返回 true。\n 7. 若 x 为 +0 且 y 为 −0,返回 true。\n 8. 若 x 为 −0 且 y 为 +0,返回 true。\n 9. 返回 false。\n2. 若 Type(x) 为 String,则当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。否则,返回 false。\n3. 若 Type(x) 为 Boolean,当 x 和 y 为同为 true 或者同为 false 时返回 true。否则,返回 false。\n4. 当 x 和 y 为引用同一对象时返回 true。否则,返回 false。\n5. 若 x 为 null 且 y 为 undefined,返回 true。\n6. 若 x 为 undefined 且 y 为 null,返回 true。\n7. 若 Type(x) 为 Number 且 Type(y) 为 String,返回 x == ToNumber(y) 的结果。\n8. 若 Type(x) 为 String 且 Type(y) 为 Number,返回比较 ToNumber(x) == y 的结果。\n9. 若 Type(x) 为 Boolean,返回比较 ToNumber(x) == y 的结果。\n10. 若 Type(y) 为 Boolean,返回比较 x == ToNumber(y) 的结果。\n11. 若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果。\n12. 若 Type(x) 为 Object 且 Type(y) 为 String 或 Number,返回比较 ToPrimitive(x) == y 的结果。\n13. 返回 false。\n注:等于运算符不总是可传递。举例来说,两个代表相同 String 值但是不同的 String 对象会分别与 String 值 ==,但是两个对象间不相等。比如下例:\n\n```javascript\nvar a = 2; var b = 2;\nvar a1 = new Number(2); var b1 = new Number(2);\nconsole.log(a == a1) //true\nconsole.log(b == b1) //true\nconsole.log(a == b) //true\nconsole.log(a1 == b1) //false\nvar a = \"123\"; var b = \"123\";\nvar a1 = new String(\"123\"); var b1 = new String(\"123\");\nconsole.log(a == a1) //true\nconsole.log(b == b1) //true\nconsole.log(a == b) //true\nconsole.log(a1 == b1) //false\n```\n注:<span style=\"color: red;\">不能将null 和undefined 转换成其他任何值,**可以见得: `null`、`undefined`只和`null`、`undefined`相等,和`false`和0比都是`false`。**</span>\n```javascript\nundefined == 0; //false\nnull == 0; //false\nfalse == null; //false\nfalse == undefined; //flase\n```\n### 小问题\n```javascript\nvar a; // undefined\n!a // true\na == false // false\n\na = null;\n!a // true\na == false // false\n```\n上面的代码中,为什么!a中a能转换成false, 而a == false 中a就不能转换成false呢?\n\n## 布尔运算符\n\n布尔运算符用于将表达式转为布尔值。\n\n### 取反运算符(!)\n\n取反运算符形式上是一个感叹号,用于将布尔值变为相反值,即true变成false,false变成true。\n\n```javascript\n\n!true // false\n!false // true\n\n```\n\n对于非布尔值的数据,取反运算符会自动将其转为布尔值。规则是,以下六个值取反后为`true`,其他值取反后都为`false`。\n\n- undefined\n- null\n- false\n- 0(包括+0和-0)\n- NaN\n- 空字符串(\"\")\n\n这意味着,取反运算符有转换数据类型的作用。\n\n```javascript\n!undefined // true\n!null // true\n!0 // true\n!NaN // true\n!\"\" // true\n\n!54 // false\n!'hello' // false\n![] // false\n!{} // false\n```\n\n上面代码中,不管什么类型的值,经过取反运算后,都变成了布尔值。\n\n如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,<span style=\"color:red;\">与Boolean函数的作用相同</span>。这是一种常用的类型转换的写法。\n\n```javascript\n\n!!x\n\n// 等同于\n\nBoolean(x)\n\n```\n\n上面代码中,不管x是什么类型的值,经过两次取反运算后,变成了与Boolean函数结果相同的布尔值。所以,两次取反就是将一个值转为布尔值的简便写法。\n\n取反运算符的这种将任意数据自动转为布尔值的功能,对下面三种布尔运算符(且运算符、或运算符、三元条件运算符)都成立。\n\n### 且运算符(&&)\n\n且运算符的运算规则是:如果第一个运算子的布尔值为`true`,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为`false`,则直接返回第一个运算子的值,且不再对第二个运算子求值。\n\n```javascript\n\n\"t\" && \"\" // \"\"\n\"t\" && \"f\" // \"f\"\n\"t\" && (1+2) // 3\n\"\" && \"f\" // \"\"\n\"\" && \"\" // \"\"\n\nvar x = 1;\n(1-1) && (x+=1) // 0\nx // 1\n\n```\n\n上面代码的最后一部分表示,由于且运算符的第一个运算子的布尔值为`false`,则直接返回它的值`0`,而不再对第二个运算子求值,所以变量`x`的值没变。\n\n这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代if结构,比如下面是一段`if`结构的代码,就可以用且运算符改写。\n\n```javascript\n\nif (i !== 0 ){\n\tdoSomething();\n}\n\n// 等价于\n\ni && doSomething();\n\n```\n\n上面代码的两种写法是等价的,但是后一种不容易看出目的,也不容易除错,建议谨慎使用。\n\n且运算符可以多个连用,这时返回第一个布尔值为`false`的表达式的值。\n\n```javascript\n\ntrue && 'foo' && '' && 4 && 'foo' && true // ''\n\n```\n\n上面代码中第一个布尔值为false的表达式为第三个表达式,所以得到一个空字符串。\n\n### 或运算符(||)\n\n或运算符的运算规则是:如果第一个运算子的布尔值为`true`,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为`false`,则返回第二个运算子的值。\n\n```javascript\n\n\"t\" || \"\" // \"t\"\n\"t\" || \"f\" // \"t\"\n\"\" || \"f\" // \"f\"\n\"\" || \"\" // \"\"\n\n```\n\n短路规则对这个运算符也适用。\n\n或运算符可以多个连用,这时返回第一个布尔值为`true`的表达式的值。\n\n```javascript\n\nfalse || 0 || '' || 4 || 'foo' || true // 4\n\n```\n\n上面代码中第一个布尔值为`true`的表达式是第四个表达式,所以得到数值4。\n\n或运算符常用于为一个变量设置默认值。\n\n```javascript\n\nfunction saveText(text) {\n text = text || ''; // ...\n}\n\n// 或者写成\n\nsaveText(this.text || '')\n\n```\n\n上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。\n\n### 三元条件运算符( ? :)\n\n三元条件运算符用问号(?)和冒号(:),分隔三个表达式。如果第一个表达式的布尔值为`true`,则返回第二个表达式的值,否则返回第三个表达式的值。\n\n```javascript\n\n\"t\" ? true : false // true\n\n0 ? true : false // false\n\n```\n\n上面代码的“t”和0的布尔值分别为`true`和`false`,所以分别返回第二个和第三个表达式的值。\n\n通常来说,三元条件表达式与`if...else`语句具有同样表达效果,前者可以表达的,后者也能表达。但是两者具有一个重大差别,`if...else`是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用`if..else`。\n\n```javascript\n\nvar check = true ? console.log('T') : console.log('F');\n\nconsole.log(true ? 'T' : 'F');\n\n```\n\n上面代码是赋值语句和`console.log`方法的例子,它们都需要使用表达式,这时三元条件表达式就能满足需要。如果要用`if...else`语句,就必须改变整个代码写法了。\n\n## 位运算符\n\n### 简介\nECMAScript 中的所有数值都以IEEE-754 64 位格式存储,但位操作符并不直接操作64 位的值。而是先将64 位的值转换成32 位的整数,然后执行操作,最后再将结果转换回64 位,位运算符用于直接对二进制位进行计算,一共有7个。\n如果对非数值应用位操作符,会先使用Number()函数将该值转换为一个数值(自动完成),然后再应用位操作。\n\n- **或运算**(or):符号为|,表示两个二进制位中有一个为1,则结果为1,否则为0。\n\n- **与运算**(and):符号为&,表示两个二进制位都为1,则结果为1,否则为0。\n\n- **否运算**(not):符号为~,表示将一个二进制位变成相反值。\n\n- **异或运算**(xor):符号为ˆ,表示两个二进制位中有且仅有一个为1时,结果为1,否则为0。\n\n- **左移运算**(left shift):符号为<<,详见下文解释。\n\n- **右移运算**(right shift):符号为>>,详见下文解释。\n\n- **带符号位的右移运算**(zero filled right shift):符号为>>>,详见下文解释。\n\n这些位运算符直接处理每一个比特位,所以是非常底层的运算,好处是速度极快,缺点是很不直观,许多场合不能使用它们,否则会带来过度的复杂性。\n\n有一点需要特别注意,位运算符只对整数起作用,**如果一个运算子不是整数,会自动将小数部分舍去后再运行。另外,虽然在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。**\n\n在ECMAScript 中,当对数值应用位操作符时,后台会发生如下转换过程:64 位的数值被转换成32位数值,然后执行位操作,最后再将32 位的结果转换回64 位数值。这样,表面上看起来就好像是在操作32 位数值,就跟在其他语言中以类似方式执行二进制操作一样。但这个转换过程也导致了一个严重的副效应,**即在对特殊的NaN 和Infinity 值应用位操作时,这两个值都会被当成0 来处理**。\n\n```javascript\ni = i|0;\n```\n\n上面这行代码的意思,就是将`i`转为32位整数。\n\n### “或运算”与“与运算”\n\n这两种运算比较容易理解,就是逐位比较两个运算子。\n“或运算”的规则是,如果两个二进制位之中至少有一个位为1,则返回1,否则返回0。\n“与运算”的规则是,如果两个二进制位之中至少有一个位为0,则返回0,否则返回1。\n\n```javascript\n0 | 3 // 3\n0 & 3 // 0\n```\n\n上面两个表达式,0和3的二进制形式分别是00和11,所以进行“或运算”会得到11(即3),进行”与运算“会得到00(即0)。\n\n**位运算只对整数有效,遇到小数时,会将小数部分舍去,只保留整数部分。**所以,将一个小数与0进行或运算,等同于对该数去除小数部分,即取整数位。\n\n```javascript\n2.9 | 0\n// 2\n\n-2.9 | 0\n// -2\n\n```\n\n需要注意的是,这种取整方法不适用超过32位整数最大值2147483647的数。\n\n```javascript\n2147483649.4 | 0;\n// -2147483647\n\n```\n\n### 否运算\n\n“否运算”将每个二进制位都变为相反值(0变为1,1变为0)。它的返回结果有时比较难理解,因为涉及到计算机内部的数值表示机制。\n\n```javascript\n~ 3 // -4\n```\n\n上面表达式对`3`进行`“否运算”`,得到`-4`。之所以会有这样的结果,是因为位运算时,JavaScirpt内部将所有的运算子都转为32位的二进制整数再进行运算。`3`在JavaScript内部是`00000000000000000000000000000011`,否运算以后得到`11111111111111111111111111111100`,由于第一位是`1`,所以这个数是一个负数。JavaScript内部采用`2`的补码形式表示负数,即需要将这个数减去`1`,再取一次反,然后加上负号,才能得到这个负数对应的10进制值。这个数减去`1`等于`11111111111111111111111111111011`,再取一次反得到`00000000000000000000000000000100`,再加上负号就是`-4`。考虑到这样的过程比较麻烦,可以简单记忆成,一个数与自身的取反值相加,等于`-1`。\n\n```javascript\n~ -3 // 2\n```\n\n上面表达式可以这样算,`-3`的取反值等于`-1`减去`-3`,结果为`2`。\n\n对一个整数连续两次“否运算”,得到它自身。\n\n```javascript\n~~3 // 3\n```\n\n<span style=\"color:red;\">所有的位运算都只对整数有效</span>。否运算遇到小数时,也会将小数部分舍去,只保留整数部分。所以,**对一个小数连续进行两次否运算,能达到取整效果。**\n\n```javascript\n~~2.9 // 2\n~~47.11 // 47\n~~1.9999 // 1\n~~3 // 3\n~~-2.9 // -2\n```\n\n使用否运算取整,是所有取整方法中最快的一种。\n\n对字符串进行否运算,JavaScript引擎会先调用Number函数,将字符串转为数值。\n\n```javascript\n// 以下例子相当于~Number('011')\n~'011' // -12\n~'42 cats' // -1\n~'0xcafebabe' // 889275713\n~'deadbeef' // -1\n\n// 以下例子相当于~~Number('011')\n~~'011'; // 11\n~~'42 cats'; // 0\n~~'0xcafebabe'; // -889275714\n~~'deadbeef'; // 0\n```\n\nNumber函数将字符串转为数值的规则,参见[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n<span style=\"color:red;\">否运算对特殊数值的处理是:超出32位的整数将会被截去超出的位数,NaN和Infinity转为0。</span>\n\n对于其他类型的参数,否运算也是先用`Number`转为数值,然后再进行处理。\n\n```javascript\n~~[] // 0\n~~NaN // 0\n~~null // 0\n```\n\n### 异或运算\n\n“异或运算”在两个二进制位不同时返回1,相同时返回0。\n\n```javascript\n0^3 // 3\n```\n\n上面表达式中,0的二进制形式是`00`,`3`的二进制形式是`11`,它们每一个二进制位都不同,所以得到`11(即3)`。\n\n“异或运算”有一个特殊运用,连续对两个数a和b进行三次异或运算,`a^=b`, `b^=a`, `a^=b`,可以互换它们的值(详见[维基百科](http://en.wikipedia.org/wiki/XOR_swap_algorithm))。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值。\n\n```javascript\nvar a = 10;\nvar b = 99;\n\na^=b, b^=a, a^=b;\n\na // 99\nb // 10\n\n```\n\n这是互换两个变量的值的最快方法。\n\n异或运算也可以用来取整。\n\n```javascript\n12.9^0 // 12\n\n```\n\n### 左移运算符(<<)\n\n左移运算符表示将一个数的二进制形式向前移动,尾部补0。\n\n```javascript\n4 << 1\n// 8\n// 因为4的二进制形式为100,左移一位为1000(即十进制的8)\n\n-4 << 1\n// -8\n\n```\n\n上面代码中,`-4`左移一位之所以得到`-8`,是因为-4的二进制形式是`11111111111111111111111111111100`,左移一位后得到`11111111111111111111111111111000`,该数转为十进制(减去`1`后取反,再加上负号)即为`-8`。\n\n如果左移`0`位,就相当于取整,对于正数和负数都有效。\n\n```javascript\n13.5 << 0\n// 13\n\n-13.5 << 0\n// -13\n\n```\n\n左移运算符用于二进制数值非常方便。\n\n```javascript\n\nvar color = {r: 186, g: 218, b: 85};\n\n// RGB to HEX\nvar rgb2hex = function(r, g, b) {\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).substr(1);\n}\n\nrgb2hex(color.r,color.g,color.b)\n// \"#bada55\"\n\n```\n\n上面代码使用左移运算符,将颜色的RGB值转为HEX值。\n\n### 右移运算符(>>)\n\n右移运算符表示将一个数的二进制形式向右移动,头部补上最左位的值,即正数补0,负数补1。\n\n```javascript\n4 >> 1\n// 2\n/*\n// 因为4的二进制形式为00000000000000000000000000000100,\n// 右移一位得到00000000000000000000000000000010,\n// 即为十进制的2\n*/\n\n-4 >> 1\n// -2\n/*\n// 因为-4的二进制形式为11111111111111111111111111111100,\n// 右移一位,头部补1,得到11111111111111111111111111111110,\n// 即为十进制的-2\n*/\n```\n\n右移运算可以模拟2的整除运算。\n\n```javascript\n5 >> 1\n// 相当于 5 / 2 = 2\n\n21 >> 2\n// 相当于 21 / 4 = 5\n\n21 >> 3\n// 相当于 21 / 8 = 2\n\n21 >> 4\n// 相当于 21 / 16 = 1\n```\n\n### 小数取整的方法\n* 通过`Math`中自带的方法\n * `Math.floor()`、`Math.round()`、`Math.ceil()`、`parseInt()`(这个会舍弃小数点`.`)\n* 位运算 所有位运算会把`NaN`、`Infinity`、`null`、`undefined` 当作0来处理\n * `-4.1 << 0 // 4` 左移`0`位\n * `12.9^0 // 12`\n * `~~2.9 // 2`\n * `-2.9 | 0 // 2`\n\n### 带符号位的右移运算符(>>>)\n\n该运算符表示将一个数的二进制形式向右移动,不管正数或负数,头部一律补`0`。所以,该运算总是得到正值,这就是它的名称`“带符号位的右移”`的涵义。对于正数,该运算的结果与右移运算符(>>)完全一致,区别主要在于负数。\n\n```javascript\n4 >>> 1\n// 2\n\n-4 >>> 1\n// 2147483646\n/*\n// 因为-4的二进制形式为11111111111111111111111111111100,\n// 带符号位的右移一位,得到01111111111111111111111111111110,\n// 即为十进制的2147483646。\n*/\n```\n\n### 开关作用\n\n位运算符可以用作设置对象属性的开关。\n假定某个对象有四个开关,每个开关都是一个变量,取值为2的整数次幂。\n```javascript\nvar FLAG_A = 1; // 0001\nvar FLAG_B = 2; // 0010\nvar FLAG_C = 4; // 0100\nvar FLAG_D = 8; // 1000\n\n```\n\n上面代码设置A、B、C、D四个开关,每个开关分别占有1个二进制位。\n现在假设需要打开ABD三个开关,我们可以构造一个掩码变量。\n```javascript\nvar mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011\n\n```\n\n上面代码对ABD三个变量进行“或运算”,得到掩码值为二进制的`1011`。\n有了掩码,就可以用“与运算”检验当前设置是否与开关设置一致。\n\n```javascript\nif (flags & FLAG_C) { // 0101 & 0100 => 0100 => true\n // ...\n}\n\n```\n\n上面代码表示,如果当前设置与掩码一致,则返回`true`,否则返回`false`。\n`“或运算”`可以将当前设置改成开关设置。\n\n```javascript\nflags |= mask; \n\n```\n\n“与运算”可以将当前设置中凡是与开关设置不一样的项,全部关闭。\n\n```javascript\nflags &= mask; \n\n```\n\n“异或运算”可以切换(toggle)当前设置。\n\n```javascript\nflags = flags ^ mask; \n\n```\n\n“否运算”可以翻转当前设置。\n\n```javascript\nflags = ~flags;\n\n```\n\n## 其他运算符\n\n### 圆括号运算符\n\n在JavaScript中,圆括号是一种运算符,它有两种用法:如果把表达式放在圆括号之中,作用是求值;如果跟在函数的后面,作用是调用函数。\n\n把表达式放在圆括号之中,将返回表达式的值。\n\n```javascript\n(1) // 1\n('a') // a\n(1+2) // 3\n\n```\n\n把对象放在圆括号之中,则会返回对象的值,即对象本身。\n\n```javascript\nvar o = {p:1};\n\n(o)\n// Object {p: 1}\n\n```\n\n将函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数,即对函数求值。\n\n```javascript\nfunction f(){return 1;}\n\n(f) // function f(){return 1;}\nf() // 1\n\n```\n\n上面的代码先定义了一个函数,然后依次将函数放在圆括号之中、将圆括号跟在函数后面,得到的结果是不一样的。\n\n由于圆括号的作用是求值,如果将语句放在圆括号之中,就会报错,因为语句没有返回值。\n\n```javascript\n(var a =1)\n// SyntaxError: Unexpected token var\n\n```\n\n### void运算符\n\n`void`运算符的作用是执行一个表达式,然后不返回任何值,或者说返回`undefined`。\n\n```javascript\nvoid 0 // undefined\nvoid(0) // undefined\n```\n\n上面是`void`运算符的两种写法,都正确。建议采用后一种形式,即总是使用括号。因为`void`运算符的优先性很高,如果不使用括号,容易造成错误的结果。比如,`void 4 + 7`实际上等同于`(void 4) + 7`。\n\n下面是`void`运算符的一个例子。\n\n```javascript\nvar x = 3;\nvoid (x = 5) //undefined\nx // 5\n```\n\n这个运算符主要是用于书签工具(bookmarklet),以及用于在超级链接中插入代码,目的是返回`undefined`可以防止网页跳转。\n\n```javascript\n<a href=\"javascript:void window.open('http://example.com/')\">\n 点击打开新窗口\n</a>\n```\n\n上面代码用于在网页中创建一个链接,点击后会打开一个新窗口。如果没有`void`,点击后就会在当前窗口打开链接。\n\n下面是常见的网页中触发鼠标点击事件的写法。\n\n```javascript\n<a href=\"http://example.com\" onclick=\"f();\">文字</a>\n```\n\n上面代码有一个问题,函数`f`必须返回`false`,或者说`onclick`事件必须返回`false`,否则会引起浏览器跳转到`example.com`。\n\n```javascript\nfunction f() {\n // some code\n return false;\n}\n```\n\n或者写成\n\n```javascript\n<a href=\"http://example.com\" onclick=\"f();return false;\">文字</a>\n```\n\n`void`运算符可以取代上面两种写法。\n\n```javascript\n<a href=\"javascript:void(f())\">文字</a>\n```\n\n下面的代码会提交表单,但是不会产生页面跳转。\n\n```javascript\n<a href=\"javascript:void(document.form.submit())\">\n文字</a>\n```\n\n### 逗号运算符\n\n逗号运算符用于对两个表达式求值,并返回后一个表达式的值。\n\n```javascript\n\n\"a\", \"b\" // \"b\"\n\nvar x = 0;\nvar y = (x++, 10);\nx // 1\ny // 10\n\n```\n\n## 运算顺序\n\n**(1)运算符的优先级**\n\nJavaScript各种运算符的优先级别(Operator Precedence)是不一样的。优先级高的运算符先执行,优先级低的运算符后执行。\n\n```javascript\n4 + 5 * 6 // 34\n```\n\n上面的代码中,乘法运算符(`*`)的优先性高于加法运算符(`+`),所以先执行乘法,再执行加法,相当于下面这样。\n\n```javascript\n4 + (5 * 6) // 34\n```\n\n如果多个运算符混写在一起,常常会导致令人困惑的代码。\n\n```javascript\nvar x = 1;\nvar arr = [];\n\nvar y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];\n```\n\n上面代码中,变量`y`的值就很难看出来,因为这个表达式涉及5个运算符,到底谁的优先级最高,实在不容易记住。\n\n根据语言规格,这五个运算符的优先级从高到低依次为:小于等于(<=)、严格相等(===)、或(||)、三元(?:)、等号(=)。因此上面的表达式,实际的运算顺序如下。\n\n```javascript\nvar y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];\n```\n\n记住所有运算符的优先级,几乎是不可能的,也是没有必要的。\n\n**(2)圆括号的作用**\n\n圆括号可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的运算符会第一个运算。\n\n```javascript\n(4 + 5) * 6 // 54\n```\n\n上面代码中,由于使用了圆括号,加法会先于乘法执行。\n\n由于运算符的优先级别十分繁杂,且都是来自硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要。\n\n**(3)左结合与右结合**\n\n对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的“左结合”(left-to-right associativity),即从左边开始计算。\n\n```javascript\nx + y + z\n```\n\n上面代码先计算最左边的`x`与`y`的和,然后再计算与`z`的和。\n\n但是少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的“右结合”(right-to-left associativity)。其中,最主要的是赋值运算符(`=`)和三元条件运算符(`?:`)。\n\n```javascript\nw = x = y = z;\nq = a ? b : c ? d : e ? f : g;\n```\n\n上面代码的运算结果,相当于下面的样子。\n\n```javascript\nw = (x = (y = z));\nq = a ? b : (c ? d : (e ? f : g));\n``` \n## 全部运算符优先级 汇总表\n下面的表将所有运算符按照优先级的不同从高到低排列。\n<table style=\"width:100%;margin-top:-8145px;\">\n <tr>\n <th>优先级</th>\n <th>运算类型</th>\n <th>关联性</th>\n <th>运算符</th>\n </tr>\n <tr>\n <td>19</td>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Grouping\" title=\"圆括号运算符( ) 用来控制表达式中的运算优先级.\"><code>圆括号</code></a></td>\n <td>n/a</td>\n <td><code>( … )</code></td>\n </tr>\n <tr>\n <td rowspan=\"3\">18</td>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Property_Accessors#点符号表示法\" title=\"属性访问器提供了两种方式用于访问一个对象的属性,它们分别是点符号和括号。\"><code>成员访问</code></a></td>\n <td>从左到右</td>\n <td><code>… . …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Property_Accessors#括号表示法\" title=\"属性访问器提供了两种方式用于访问一个对象的属性,它们分别是点符号和括号。\"><code>需计算的成员访问</code></a></td>\n <td>从左到右</td>\n <td><code>… [ … ]</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new\" title=\"new运算符的作用是创建一个对象实例。这个对象可以是用户自定义的,也可以是一些系统自带的带构造函数的对象。\"><code>new</code></a> (带参数列表)</td>\n <td>n/a</td>\n <td><code>new … ( … )</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\">17</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions\" title=\"JavaScript/Reference/Operators/Special_Operators/function_call\">函数调用</a></td>\n <td>从左到右</td>\n <td><code>… ( <var>… </var>)</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new\" title=\"JavaScript/Reference/Operators/Special_Operators/new_Operator\">new</a> (无参数列表)</td>\n <td>从右到左</td>\n <td><code>new …</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\">16</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">后置递增</a>(运算符在后)</td>\n <td>n/a</td>\n <td><code>… ++</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">后置递减</a>(运算符在后)</td>\n <td>n/a</td>\n <td><code>… --</code></td>\n </tr>\n <tr>\n <td rowspan=\"9\">15</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_NOT\">逻辑非</a></td>\n <td>从右到左</td>\n <td><code>! …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位非</a></td>\n <td>从右到左</td>\n <td><code>~ …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">一元加法</a></td>\n <td>从右到左</td>\n <td><code>+ …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_negation\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">一元减法</a></td>\n <td>从右到左</td>\n <td><code>- …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">前置递增</a></td>\n <td>从右到左</td>\n <td><code>++ …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">前置递减</a></td>\n <td>从右到左</td>\n <td><code>-- …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof\" title=\"JavaScript/Reference/Operators/Special_Operators/typeof_Operator\">typeof</a></td>\n <td>从右到左</td>\n <td><code>typeof …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void\" title=\"JavaScript/Reference/Operators/Special_Operators/void_Operator\">void</a></td>\n <td>从右到左</td>\n <td><code>void …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete\" title=\"JavaScript/Reference/Operators/Special_Operators/delete_Operator\">delete</a></td>\n <td>从右到左</td>\n <td><code>delete …</code></td>\n </tr>\n <tr>\n <td rowspan=\"3\">14</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Multiplication\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">乘法</a></td>\n <td>从左到右</td>\n <td><code>… * …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Division\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">除法</a></td>\n <td>从左到右</td>\n <td><code>… / …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">取模</a></td>\n <td>从左到右</td>\n <td><code>… % …</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\">13</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Addition\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">加法</a></td>\n <td>从左到右</td>\n <td><code>… + …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Subtraction\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">减法</a></td>\n <td>从左到右</td>\n <td><code>… - …</code></td>\n </tr>\n <tr>\n <td rowspan=\"3\">12</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位左移</a></td>\n <td>从左到右</td>\n <td><code>… << …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位右移</a></td>\n <td>从左到右</td>\n <td><code>… >> …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">无符号右移</a></td>\n <td>从左到右</td>\n <td><code>… >>> …</code></td>\n </tr>\n <tr>\n <td rowspan=\"6\">11</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">小于</a></td>\n <td>从左到右</td>\n <td><code>… < …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than__or_equal_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">小于等于</a></td>\n <td>从左到右</td>\n <td><code>… <= …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">大于</a></td>\n <td>从左到右</td>\n <td><code>… > …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_or_equal_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">大于等于</a></td>\n <td>从左到右</td>\n <td><code>… >= …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in\" title=\"JavaScript/Reference/Operators/Special_Operators/in_Operator\">in</a></td>\n <td>从左到右</td>\n <td><code>… in …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof\" title=\"JavaScript/Reference/Operators/Special_Operators/instanceof_Operator\">instanceof</a></td>\n <td>从左到右</td>\n <td><code>… instanceof …</code></td>\n </tr>\n <tr>\n <td rowspan=\"4\">10</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Equality\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">等号</a></td>\n <td>从左到右</td>\n <td><code>… == …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Inequality\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">非等号</a></td>\n <td>从左到右</td>\n <td><code>… != …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Identity\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">全等号</a></td>\n <td>从左到右</td>\n <td><code>… === …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Nonidentity\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">非全等号</a></td>\n <td>从左到右</td>\n <td><code>… !== …</code></td>\n </tr>\n <tr>\n <td>9</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_AND\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位与</a></td>\n <td>从左到右</td>\n <td><code>… & …</code></td>\n </tr>\n <tr>\n <td>8</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_XOR\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位异或</a></td>\n <td>从左到右</td>\n <td><code>… ^ …</code></td>\n </tr>\n <tr>\n <td>7</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_OR\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位或</a></td>\n <td>从左到右</td>\n <td><code>… | …</code></td>\n </tr>\n <tr>\n <td>6</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_AND\" title=\"JavaScript/Reference/Operators/Logical_Operators\">逻辑与</a></td>\n <td>从左到右</td>\n <td><code>… && …</code></td>\n </tr>\n <tr>\n <td>5</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR\" title=\"JavaScript/Reference/Operators/Logical_Operators\">逻辑或</a></td>\n <td>从左到右</td>\n <td><code>… || …</code></td>\n </tr>\n <tr>\n <td>4</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator\" title=\"JavaScript/Reference/Operators/Special_Operators/Conditional_Operator\">条件运算符</a></td>\n <td>从右到左</td>\n <td><code>… ? … : …</code></td>\n </tr>\n <tr>\n <td rowspan=\"12\">3</td>\n <td rowspan=\"12\"><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators\" title=\"JavaScript/Reference/Operators/Assignment_Operators\">赋值</a></td>\n <td rowspan=\"12\">从右到左</td>\n <td><code>… = …</code></td>\n </tr>\n <tr>\n <td><code>… += …</code></td>\n </tr>\n <tr>\n <td><code>… -= …</code></td>\n </tr>\n <tr>\n <td><code>… *= …</code></td>\n </tr>\n <tr>\n <td><code>… /= …</code></td>\n </tr>\n <tr>\n <td><code>… %= …</code></td>\n </tr>\n <tr>\n <td><code>… <<= …</code></td>\n </tr>\n <tr>\n <td><code>… >>= …</code></td>\n </tr>\n <tr>\n <td><code>… >>>= …</code></td>\n </tr>\n <tr>\n <td><code>… &= …</code></td>\n </tr>\n <tr>\n <td><code>… ^= …</code></td>\n </tr>\n <tr>\n <td><code>… |= …</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\" colspan=\"1\">2</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield\" title=\"JavaScript/Reference/Operators/yield\">yield</a></td>\n <td>从右到左</td>\n <td><code>yield …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*\" title=\"JavaScript/Reference/Operators/yield\">yield*</a></td>\n <td>从右到左</td>\n <td>yield* …</td>\n </tr>\n <tr>\n <td>1</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator\" title=\"JavaScript/Reference/Operators/Spread_operator\">Spread</a></td>\n <td>n/a</td>\n <td><code>...</code> …</td>\n </tr>\n <tr>\n <td>0</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator\" title=\"JavaScript/Reference/Operators/Comma_Operator\">逗号</a></td>\n <td>从左到右</td>\n <td><code>… , …</code></td>\n </tr>\n\n</table>","source":"_posts/运算符-2016-02-25.md","raw":"title: 运算符\ndate: 2016-02-25 14:19:16\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\n运算符是处理数据的基本方法,用来从现有数据得到新的数据。JavaScript与其他编程语言一样,提供了多种运算符。\n<!--more-->\n## 算术运算符\n\nJavaScript提供9个算术运算符。\n\n- **加法运算符**(Addition):`x + y`\n- **减法运算符**(Subtraction): `x - y`\n- **乘法运算符**(Multiplication): `x * y`\n- **除法运算符**(Division):`x / y`\n- **余数运算符**(Remainder):`x % y`\n- **自增运算符**(Increment):`++x` 或者 `x++`\n- **自减运算符**(Decrement):`--x` 或者 `x--`\n- **数值运算符**(Convert to number): `+x`\n- **负数值运算符**(Negate):`-x`\n\n减法、乘法、除法运算法比较单纯,就是执行相应的数学运算。下面介绍其他几个算术运算符。\n\n### 加法运算符\n\n加法运算符(`+`)需要注意的地方是,它除了用于数值的相加,还能用于字符串的连接。\n\n```javascript\n1 + 1 // 2\n'1' + '1' // \"11\"\n'1.1' + '1.1' // \"1.11.1\"\n```\n\n上面代码中,如果两个运算子都是数值,加号运算符就执行数值的加法运算;如果两个运算子都是字符串,加号运算符就执行字符串的连接运算,变成字符串连接运算符。这种由于参数不同,而改变自身行为的现象,叫做“重载”(overload)。\n\n如果两个运算子之中,一个是字符串,另一个是数值,加法运算符执行字符串连接运算。\n\n```javascript\n1 + '1' // \"11\"\n```\n\n上面代码表示,两个运算子之中有一个是字符串,另一个运算子就会被自动转为字符串。\n\n```javascript\n'3' + 4 + 5 // \"345\"\n3 + 4 + '5' // \"75\"\n```\n\n上面代码中,由于加法运算符遇到字符串,会发生重载,导致运算结果的不同。\n\n由于这个特性,下面的写法有时用于将一个值转为字符串。\n\n```javascript\nx + ''\n```\n\n上面代码表示,一个值加上空字符串,会使得该值转为字符串形式。\n\n布尔值和复合类型的值,也可以使用加法运算符,但是会导致数据类型的自动转换,关于这方面的详细讨论,参见[《数据类型转换》](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n**除加法运算符以外的其他算术运算符,都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。**\n\n```javascript\n1 - '1' // 0\n+'3' // 3\n-true // -1\n```\n\n上面代码表示,减法运算符将字符串“1”自动转为数值1,数值运算符(+)将字符串“3”转为数值3,求负运算符(-)将布尔值true转为-1。\n\n由于加法运算符与其他算术运算符的这种差异,会导致一些意想不到的结果,计算时要小心。\n\n```javascript\nvar now = new Date();\ntypeof (now + 1) // \"string\"\ntypeof (now - 1) // \"number\"\n```\n\n上面代码中,`now`是一个Date对象的实例。加法运算时,`now`转为字符串,加一个数字,得到还是字符串;减法运算时,now转为数值,减一个数字,得到的是数字。\n<span style=\"color:red;\">Date对象比较特殊在和一个任意类型的数据相加的时候,会优先调用toString()方法,其他对象都是优先valueOf()</span>\n\n### 余数运算符\n\n余数运算符(`%`)返回前一个运算子被后一个运算子除,所得的余数。\n\n```javascript\n12 % 5 // 2\n```\n\n需要注意的是,运算结果的正负号由第一个运算子的正负号决定。\n\n```javascript\n-1 % 2 // -1\n1 % -2 // 1\n```\n\n为了得到正确的负数的余数值,需要先使用绝对值函数。\n\n```javascript\n// 错误的写法\nfunction isOdd(n) {\n return n % 2 === 1;\n}\nisOdd(-5) // false\nisOdd(-4) // false\n\n// 正确的写法\nfunction isOdd(n) {\n return Math.abs(n % 2) === 1;\n}\nisOdd(-5) // true\nisOdd(-4) // false\n```\n\n余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。\n\n```javascript\n6.5 % 2.1\n// 0.19999999999999973\n```\n\n### 自增和自减运算符\n\n自增和自减运算符,是一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量。\n\n```javascript\nvar x = 1;\n++x // 2\nx // 2\n\n--x // 1\nx // 1\n```\n\n上面代码的变量x自增后,返回2,再进行自减,返回1。这两种情况都会使得,原始变量`x`的值发生改变。\n\n自增和自减运算符有一个需要注意的地方,就是放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。\n\n```javascript\nvar x = 1;\nvar y = 1;\n\nx++ // 1\n++y // 2\n```\n\n上面代码中,`x`是先返回当前值,然后自增,所以得到1;`y`是先自增,然后返回新的值,所以得到2。\n\n### 数值运算符,负数值运算符\n\n数值运算符(`+`)同样使用加号,但是加法运算符是二元运算符(需要两个操作数),它是一元运算符(只需要一个操作数)。\n\n数值运算符的作用在于可以将任何值转为数值(与`Number`函数的作用相同)。\n\n```javascript\n+true // 1\n+[] // 0\n+{} // NaN\n```\n\n上面代码表示,非数值类型的值经过数值运算符以后,都变成了数值(最后一行`NaN`也是数值)。具体的类型转换规则,参见[《数据类型转换》](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n求负运算符(`-`),也同样具有将一个值转为数值的功能,所以下面的写法等同于数值运算符。\n\n```javascript\nvar x = 1;\n-x // -1\n-(-x) // 1\n\nvar y = \"111\";\n-y // -111\n```\n\n上面代码最后一行的圆括号不可少,否则会变成递减运算符。从中可以看出,数值运算符和负数值运算符,不会改变原始变量的值。\n\n## 赋值运算符\n\n赋值运算符(Assignment Operators)用于给变量赋值。\n\n### 何为\"左值\"?\n形如`A=B`的表达式称为赋值表达式。其中A和B又分别可以是表达式。B可以是任意表达式,但是A必须是一个左值。所谓左值,就是可以被赋值的表达式,在ES规范中是用内部类型引用(Reference)描述的。例如:\n * 表达式`foo.bar`可以作为一个左值,表示对`foo`这个对象中`bar`这个名称的引用;\n * 变量`email`可以作为一个左值,表示对当前执行环境中的环境记录项`envRec`中`email`这个名称的引用;\n * 同样地,函数名`func`可以做左值,然而函数调用表达式`func(a, b)`不可以。\n\n那么JS引擎是怎样计算一般的赋值表达式 A = B的呢?简单地说,按如下步骤:\n 1. 计算表达式A,得到一个引用refA;\n 2. 计算表达式B,得到一个值valueB;\n 3. 将valueB赋给refA指向的名称绑定;\n 4. 返回valueB。\n\n### 结合性\n赋值表达式是右结合的。这意味着:\n\n A1 = A2 = A3 = A4\n等价于\n\n A1 = (A2 = (A3 = A4))\n### 连等的解析\n好了,有了上面两部分的知识。下面来看一下JS引擎是怎样运算连等赋值表达式的。\n\n Exp1 = Exp2 = Exp3 = Exp4\n首先根据右结合性,可以转换成\n\n Exp1 = (Exp2 = (Exp3 = Exp4))\n\n然后,我们已经知道对于单个赋值运算,JS引擎总是先计算左边的操作数,再计算右边的操作数。所以接下来的步骤就是:\n\n 1. 计算Exp1,得到Ref1\n 2. 计算Exp2,得到Ref2\n 3. 计算Exp3,得到Ref3\n 4. 计算Exp4,得到Value4。\n\n现在变成了这样的:\n\n Ref1 = (Ref2 = (Ref3 = Value4))\n\n接下来的步骤是:\n\n 1. 将Value4赋给Exp3;\n 2. 将Value4赋给Exp2;\n 3. 将Value4赋给Exp1;\n 4. 返回表达式最终的结果Value4。\n\n总结一下就是:\n> 先从左到右解析各个引用,然后计算最右侧的表达式的值,最后把值从右到左赋给各个引用。\n\n最常见的赋值运算符,当然就是等号(`=`),表达式`x = y`表示将`y`的值赋给`x`。除此之外,JavaScript还提供其他11个赋值运算符。\n\n```javascript\nx += y // 等同于 x = x + y\nx -= y // 等同于 x = x - y\nx *= y // 等同于 x = x * y\nx /= y // 等同于 x = x / y\nx %= y // 等同于 x = x % y\nx >>= y // 等同于 x = x >> y\nx <<= y // 等同于 x = x << y\nx >>>= y // 等同于 x = x >>> y\nx &= y // 等同于 x = x & y\nx |= y // 等同于 x = x | y\nx ^= y // 等同于 x = x ^ y\n```\n\n上面11个赋值运算符,都是先进行某种运算,然后将得到值返回给左边的变量。\n\n### 小问题\n\n```javascript\nvar a = {n: 1};\nvar b = a;\na.x = a = {n: 2};\nalert(a.x); // --> undefined\nalert(b.x); // --> {n: 2}\n```\n首先前两个var语句执行完后,a和b都指向同一个对象`{n: 1}`(为方便描述,下面称为对象`N1`)。然后来看\n\n a.x = a = {n: 2};\n根据前面的知识,首先依次计算表达式`a.x`和`a`,得到两个引用。其中`a.x`表示对象`N1`中的`x`,而`a`相当于`envRec.a`,即当前环境记录项中的`a`。所以此时可以写出如下的形式:\n\n [[N1]].x = [[encRec]].a = {n: 2};//其中,[[]]表示引用指向的对象。\n接下来,将`{n: 2}`赋值给`[[encRec]].a`,即将`{n: 2}`绑定到当前上下文中的名称`a`。\n接下来,将同一个`{n: 2}`赋值给`[[N1]].x`,即将`{n: 2}`绑定到`N1`中的名称`x`。\n由于`b`仍然指向`N1`,所以此时有\n\n b <=> N1 <=> {n: 1, x: {n: 2}}\n而a被重新赋值了,所以\n\n a <=> {n: 2}\n并且\n\n a === b.x\n\n如果你明白了上面所有的内容,应该会明白`a.x = a = {n:2};`与`b.x = a = {n:2};`是完全等价的。因为在解析`a.x`或`b.x`的那个时间点。`a`和`b`这两个名称指向同一个对象,就像C++中同一个对象可以有多个引用一样。而在这个时间点之后,不论是`a.x`还是`b.x`,其实早就不存在了,它已经变成了那个`内存中的对象.x`了。\n\n## 比较运算符\n\n比较运算符比较两个值,然后返回一个布尔值,表示是否满足比较条件。JavaScript提供了8个比较运算符。\n\n- `==` 相等\n- `===` 严格相等\n- `!=` 不相等\n- `!==` 严格不相等\n- `<` 小于\n- `<=` 小于或等于\n- `\\>` 大于\n- `\\>=` 大于或等于\n\n其中,比较两个值是否相等的运算符有两个:一个是相等运算符(`==`),另一个是严格相等运算符(`===`)。\n\n相等运算符(`==`)比较两个值是否相等,严格相等运算符(`===`)比较它们是否为“同一个值”。两者的一个重要区别是,如果两个值不是同一类型,严格相等运算符(`===`)直接返回false,而相等运算符(`==`)会将它们转化成同一个类型,再用严格相等运算符进行比较。\n\n### 严格相等运算符\n\n严格相等运算符的运算规则如下。\n\n**(1)不同类型的值**\n\n如果两个值的类型不同,直接返回`false`。\n\n```javascript\n1 === \"1\" // false\ntrue === \"true\" // false\n```\n\n上面代码比较数值的`1`与字符串的“1”、布尔值的`true`与字符串“true”,因为类型不同,结果都是`false`。\n\n**(2)同一类的原始类型值**\n\n同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false。\n\n```javascript\n1 === 0x1 // true\n```\n\n上面代码比较十进制的1与十六进制的1,因为类型和值都相同,返回`true`。\n\n需要注意的是,`NaN`与任何值都不相等(包括自身)。另外,正0等于负0。\n\n```javascript\nNaN === NaN // false\n+0 === -0 // true\n```\n\n**(3)同一类的复合类型值**\n\n两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象。\n\n```javascript\n({}) === {} // false\n[] === [] // false\n(function (){}) === function (){} // false\n```\n\n上面代码分别比较两个空对象、两个空数组、两个空函数,结果都是不相等。原因是对于复合类型的值,严格相等运算比较的是它们的内存地址是否一样,而上面代码中空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是`false`。另外,之所以要把第一个空对象放在括号内,是为了避免JavaScript引擎把这一行解释成代码块,从而报错;把第一个空函数放在括号内,是为了避免这一行被解释成函数的定义。\n\n如果两个变量指向同一个复合类型的数据,则它们相等。\n\n```javascript\nvar v1 = {};\nvar v2 = v1;\nv1 === v2 // true\n```\n\n**(4)undefined和null**\n\n`undefined`和`null`与自身严格相等。\n\n```javascript\nundefined === undefined // true\nnull === null // true\n```\n\n由于变量声明后默认值是`undefined`,因此两个只声明未赋值的变量是相等的。\n\n```javascript\nvar v1;\nvar v2;\nv1 === v2 // true\n```\n\n**(5)严格不相等运算符**\n\n严格相等运算符有一个对应的“严格不相等运算符”(`!==`),两者的运算结果正好相反。\n\n```javascript\n1 !== '1' // true\n```\n\n### 相等运算符\n\n相等运算符在比较相同类型的数据时,与严格相等运算符完全一样。\n\n在比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。类型转换规则如下:\n\n**(1)原始类型的值**\n\n原始类型的数据会转换成数值类型再进行比较。\n\n```javascript\n1 == true // true\n0 == false // true\n\n\"true\" == true // false\n\n'' == 0 // true\n\n'' == false // true\n'1' == true // true\n\n'2' == true // false\n2 == true // false\n2 == false // false\n\n'\\n 123 \\t' == 123 // true\n// 因为字符串转为数字时,省略前置和后置的空格\n```\n\n上面代码将字符串和布尔值都转为数值,然后再进行比较。字符串与布尔值的类型转换规则,参见[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n**(2)对象与原始类型值比较**\n\n对象(这里指广义的对象,包括数值和函数)与原始类型的值比较时,对象转化成原始类型的值,再进行比较。\n\n```javascript\n[1] == 1 // true\n[1] == '1' // true\n[1] == true // true\n```\n\n上面代码将只含有数值1的数组与原始类型的值进行比较,数组`[1]`会被自动转换成数值`1`,因此结果都是`true`。数组的类型转换规则,参见[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n\n**(3)undefined和null**\n\nundefined和null与其他类型的值比较时,结果都为false,它们互相比较时结果为true。\n\n```javascript\n\nfalse == null // false\n0 == null // false\n\nundefined == null // true\n\n```\n\n**(4)相等运算符的缺点**\n\n相等运算符隐藏的类型转换,会带来一些违反直觉的结果。\n\n```javascript\n\n'' == '0' // false\n0 == '' // true\n0 == '0' // true\n\nfalse == 'false' // false\nfalse == '0' // true\n\nfalse == undefined // false\nfalse == null // false\nnull == undefined // true\n\n' \\t\\r\\n ' == 0 // true\n\n```\n\n上面这些表达式都很容易出错,因此不要使用相等运算符(==),最好只使用严格相等运算符(===)。\n\n**(5)不相等运算符**\n\n相等运算符有一个对应的“不相等运算符”(!=),两者的运算结果正好相反。\n\n```javascript\n\n1 != \"1\" // false\n\n```\n**(6)详细比较过程**\n摘录[ES5标准](https://www.w3.org/html/ig/zh/wiki/ES5/expressions#.E7.AD.89.E4.BA.8E.E8.BF.90.E7.AE.97.E7.AC.A6.EF.BC.88.3D.3D.EF.BC.89):以 x 和 y 为值进行 x == y 比较会产生的结果可为 true 或 false。比较的执行步骤如下:\n1. 若 Type(x) 与 Type(y) 相同, 则\n 1. 若 Type(x) 为 Undefined, 返回 true。\n 2. 若 Type(x)为 Null, 返回 true。\n 3. 若 Type(x)为 Number,则\n 4. 若 x 为 NaN,返回 false。\n 5. 若 y 为 NaN,返回 false。\n 6. 若 x 与 y 为相等数值,返回 true。\n 7. 若 x 为 +0 且 y 为 −0,返回 true。\n 8. 若 x 为 −0 且 y 为 +0,返回 true。\n 9. 返回 false。\n2. 若 Type(x) 为 String,则当 x 和 y 为完全相同的字符序列(长度相等且相同字符在相同位置)时返回 true。否则,返回 false。\n3. 若 Type(x) 为 Boolean,当 x 和 y 为同为 true 或者同为 false 时返回 true。否则,返回 false。\n4. 当 x 和 y 为引用同一对象时返回 true。否则,返回 false。\n5. 若 x 为 null 且 y 为 undefined,返回 true。\n6. 若 x 为 undefined 且 y 为 null,返回 true。\n7. 若 Type(x) 为 Number 且 Type(y) 为 String,返回 x == ToNumber(y) 的结果。\n8. 若 Type(x) 为 String 且 Type(y) 为 Number,返回比较 ToNumber(x) == y 的结果。\n9. 若 Type(x) 为 Boolean,返回比较 ToNumber(x) == y 的结果。\n10. 若 Type(y) 为 Boolean,返回比较 x == ToNumber(y) 的结果。\n11. 若 Type(x) 为 String 或 Number,且 Type(y) 为 Object,返回比较 x == ToPrimitive(y) 的结果。\n12. 若 Type(x) 为 Object 且 Type(y) 为 String 或 Number,返回比较 ToPrimitive(x) == y 的结果。\n13. 返回 false。\n注:等于运算符不总是可传递。举例来说,两个代表相同 String 值但是不同的 String 对象会分别与 String 值 ==,但是两个对象间不相等。比如下例:\n\n```javascript\nvar a = 2; var b = 2;\nvar a1 = new Number(2); var b1 = new Number(2);\nconsole.log(a == a1) //true\nconsole.log(b == b1) //true\nconsole.log(a == b) //true\nconsole.log(a1 == b1) //false\nvar a = \"123\"; var b = \"123\";\nvar a1 = new String(\"123\"); var b1 = new String(\"123\");\nconsole.log(a == a1) //true\nconsole.log(b == b1) //true\nconsole.log(a == b) //true\nconsole.log(a1 == b1) //false\n```\n注:<span style=\"color: red;\">不能将null 和undefined 转换成其他任何值,**可以见得: `null`、`undefined`只和`null`、`undefined`相等,和`false`和0比都是`false`。**</span>\n```javascript\nundefined == 0; //false\nnull == 0; //false\nfalse == null; //false\nfalse == undefined; //flase\n```\n### 小问题\n```javascript\nvar a; // undefined\n!a // true\na == false // false\n\na = null;\n!a // true\na == false // false\n```\n上面的代码中,为什么!a中a能转换成false, 而a == false 中a就不能转换成false呢?\n\n## 布尔运算符\n\n布尔运算符用于将表达式转为布尔值。\n\n### 取反运算符(!)\n\n取反运算符形式上是一个感叹号,用于将布尔值变为相反值,即true变成false,false变成true。\n\n```javascript\n\n!true // false\n!false // true\n\n```\n\n对于非布尔值的数据,取反运算符会自动将其转为布尔值。规则是,以下六个值取反后为`true`,其他值取反后都为`false`。\n\n- undefined\n- null\n- false\n- 0(包括+0和-0)\n- NaN\n- 空字符串(\"\")\n\n这意味着,取反运算符有转换数据类型的作用。\n\n```javascript\n!undefined // true\n!null // true\n!0 // true\n!NaN // true\n!\"\" // true\n\n!54 // false\n!'hello' // false\n![] // false\n!{} // false\n```\n\n上面代码中,不管什么类型的值,经过取反运算后,都变成了布尔值。\n\n如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,<span style=\"color:red;\">与Boolean函数的作用相同</span>。这是一种常用的类型转换的写法。\n\n```javascript\n\n!!x\n\n// 等同于\n\nBoolean(x)\n\n```\n\n上面代码中,不管x是什么类型的值,经过两次取反运算后,变成了与Boolean函数结果相同的布尔值。所以,两次取反就是将一个值转为布尔值的简便写法。\n\n取反运算符的这种将任意数据自动转为布尔值的功能,对下面三种布尔运算符(且运算符、或运算符、三元条件运算符)都成立。\n\n### 且运算符(&&)\n\n且运算符的运算规则是:如果第一个运算子的布尔值为`true`,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为`false`,则直接返回第一个运算子的值,且不再对第二个运算子求值。\n\n```javascript\n\n\"t\" && \"\" // \"\"\n\"t\" && \"f\" // \"f\"\n\"t\" && (1+2) // 3\n\"\" && \"f\" // \"\"\n\"\" && \"\" // \"\"\n\nvar x = 1;\n(1-1) && (x+=1) // 0\nx // 1\n\n```\n\n上面代码的最后一部分表示,由于且运算符的第一个运算子的布尔值为`false`,则直接返回它的值`0`,而不再对第二个运算子求值,所以变量`x`的值没变。\n\n这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代if结构,比如下面是一段`if`结构的代码,就可以用且运算符改写。\n\n```javascript\n\nif (i !== 0 ){\n\tdoSomething();\n}\n\n// 等价于\n\ni && doSomething();\n\n```\n\n上面代码的两种写法是等价的,但是后一种不容易看出目的,也不容易除错,建议谨慎使用。\n\n且运算符可以多个连用,这时返回第一个布尔值为`false`的表达式的值。\n\n```javascript\n\ntrue && 'foo' && '' && 4 && 'foo' && true // ''\n\n```\n\n上面代码中第一个布尔值为false的表达式为第三个表达式,所以得到一个空字符串。\n\n### 或运算符(||)\n\n或运算符的运算规则是:如果第一个运算子的布尔值为`true`,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为`false`,则返回第二个运算子的值。\n\n```javascript\n\n\"t\" || \"\" // \"t\"\n\"t\" || \"f\" // \"t\"\n\"\" || \"f\" // \"f\"\n\"\" || \"\" // \"\"\n\n```\n\n短路规则对这个运算符也适用。\n\n或运算符可以多个连用,这时返回第一个布尔值为`true`的表达式的值。\n\n```javascript\n\nfalse || 0 || '' || 4 || 'foo' || true // 4\n\n```\n\n上面代码中第一个布尔值为`true`的表达式是第四个表达式,所以得到数值4。\n\n或运算符常用于为一个变量设置默认值。\n\n```javascript\n\nfunction saveText(text) {\n text = text || ''; // ...\n}\n\n// 或者写成\n\nsaveText(this.text || '')\n\n```\n\n上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。\n\n### 三元条件运算符( ? :)\n\n三元条件运算符用问号(?)和冒号(:),分隔三个表达式。如果第一个表达式的布尔值为`true`,则返回第二个表达式的值,否则返回第三个表达式的值。\n\n```javascript\n\n\"t\" ? true : false // true\n\n0 ? true : false // false\n\n```\n\n上面代码的“t”和0的布尔值分别为`true`和`false`,所以分别返回第二个和第三个表达式的值。\n\n通常来说,三元条件表达式与`if...else`语句具有同样表达效果,前者可以表达的,后者也能表达。但是两者具有一个重大差别,`if...else`是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用`if..else`。\n\n```javascript\n\nvar check = true ? console.log('T') : console.log('F');\n\nconsole.log(true ? 'T' : 'F');\n\n```\n\n上面代码是赋值语句和`console.log`方法的例子,它们都需要使用表达式,这时三元条件表达式就能满足需要。如果要用`if...else`语句,就必须改变整个代码写法了。\n\n## 位运算符\n\n### 简介\nECMAScript 中的所有数值都以IEEE-754 64 位格式存储,但位操作符并不直接操作64 位的值。而是先将64 位的值转换成32 位的整数,然后执行操作,最后再将结果转换回64 位,位运算符用于直接对二进制位进行计算,一共有7个。\n如果对非数值应用位操作符,会先使用Number()函数将该值转换为一个数值(自动完成),然后再应用位操作。\n\n- **或运算**(or):符号为|,表示两个二进制位中有一个为1,则结果为1,否则为0。\n\n- **与运算**(and):符号为&,表示两个二进制位都为1,则结果为1,否则为0。\n\n- **否运算**(not):符号为~,表示将一个二进制位变成相反值。\n\n- **异或运算**(xor):符号为ˆ,表示两个二进制位中有且仅有一个为1时,结果为1,否则为0。\n\n- **左移运算**(left shift):符号为<<,详见下文解释。\n\n- **右移运算**(right shift):符号为>>,详见下文解释。\n\n- **带符号位的右移运算**(zero filled right shift):符号为>>>,详见下文解释。\n\n这些位运算符直接处理每一个比特位,所以是非常底层的运算,好处是速度极快,缺点是很不直观,许多场合不能使用它们,否则会带来过度的复杂性。\n\n有一点需要特别注意,位运算符只对整数起作用,**如果一个运算子不是整数,会自动将小数部分舍去后再运行。另外,虽然在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。**\n\n在ECMAScript 中,当对数值应用位操作符时,后台会发生如下转换过程:64 位的数值被转换成32位数值,然后执行位操作,最后再将32 位的结果转换回64 位数值。这样,表面上看起来就好像是在操作32 位数值,就跟在其他语言中以类似方式执行二进制操作一样。但这个转换过程也导致了一个严重的副效应,**即在对特殊的NaN 和Infinity 值应用位操作时,这两个值都会被当成0 来处理**。\n\n```javascript\ni = i|0;\n```\n\n上面这行代码的意思,就是将`i`转为32位整数。\n\n### “或运算”与“与运算”\n\n这两种运算比较容易理解,就是逐位比较两个运算子。\n“或运算”的规则是,如果两个二进制位之中至少有一个位为1,则返回1,否则返回0。\n“与运算”的规则是,如果两个二进制位之中至少有一个位为0,则返回0,否则返回1。\n\n```javascript\n0 | 3 // 3\n0 & 3 // 0\n```\n\n上面两个表达式,0和3的二进制形式分别是00和11,所以进行“或运算”会得到11(即3),进行”与运算“会得到00(即0)。\n\n**位运算只对整数有效,遇到小数时,会将小数部分舍去,只保留整数部分。**所以,将一个小数与0进行或运算,等同于对该数去除小数部分,即取整数位。\n\n```javascript\n2.9 | 0\n// 2\n\n-2.9 | 0\n// -2\n\n```\n\n需要注意的是,这种取整方法不适用超过32位整数最大值2147483647的数。\n\n```javascript\n2147483649.4 | 0;\n// -2147483647\n\n```\n\n### 否运算\n\n“否运算”将每个二进制位都变为相反值(0变为1,1变为0)。它的返回结果有时比较难理解,因为涉及到计算机内部的数值表示机制。\n\n```javascript\n~ 3 // -4\n```\n\n上面表达式对`3`进行`“否运算”`,得到`-4`。之所以会有这样的结果,是因为位运算时,JavaScirpt内部将所有的运算子都转为32位的二进制整数再进行运算。`3`在JavaScript内部是`00000000000000000000000000000011`,否运算以后得到`11111111111111111111111111111100`,由于第一位是`1`,所以这个数是一个负数。JavaScript内部采用`2`的补码形式表示负数,即需要将这个数减去`1`,再取一次反,然后加上负号,才能得到这个负数对应的10进制值。这个数减去`1`等于`11111111111111111111111111111011`,再取一次反得到`00000000000000000000000000000100`,再加上负号就是`-4`。考虑到这样的过程比较麻烦,可以简单记忆成,一个数与自身的取反值相加,等于`-1`。\n\n```javascript\n~ -3 // 2\n```\n\n上面表达式可以这样算,`-3`的取反值等于`-1`减去`-3`,结果为`2`。\n\n对一个整数连续两次“否运算”,得到它自身。\n\n```javascript\n~~3 // 3\n```\n\n<span style=\"color:red;\">所有的位运算都只对整数有效</span>。否运算遇到小数时,也会将小数部分舍去,只保留整数部分。所以,**对一个小数连续进行两次否运算,能达到取整效果。**\n\n```javascript\n~~2.9 // 2\n~~47.11 // 47\n~~1.9999 // 1\n~~3 // 3\n~~-2.9 // -2\n```\n\n使用否运算取整,是所有取整方法中最快的一种。\n\n对字符串进行否运算,JavaScript引擎会先调用Number函数,将字符串转为数值。\n\n```javascript\n// 以下例子相当于~Number('011')\n~'011' // -12\n~'42 cats' // -1\n~'0xcafebabe' // 889275713\n~'deadbeef' // -1\n\n// 以下例子相当于~~Number('011')\n~~'011'; // 11\n~~'42 cats'; // 0\n~~'0xcafebabe'; // -889275714\n~~'deadbeef'; // 0\n```\n\nNumber函数将字符串转为数值的规则,参见[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)一节。\n<span style=\"color:red;\">否运算对特殊数值的处理是:超出32位的整数将会被截去超出的位数,NaN和Infinity转为0。</span>\n\n对于其他类型的参数,否运算也是先用`Number`转为数值,然后再进行处理。\n\n```javascript\n~~[] // 0\n~~NaN // 0\n~~null // 0\n```\n\n### 异或运算\n\n“异或运算”在两个二进制位不同时返回1,相同时返回0。\n\n```javascript\n0^3 // 3\n```\n\n上面表达式中,0的二进制形式是`00`,`3`的二进制形式是`11`,它们每一个二进制位都不同,所以得到`11(即3)`。\n\n“异或运算”有一个特殊运用,连续对两个数a和b进行三次异或运算,`a^=b`, `b^=a`, `a^=b`,可以互换它们的值(详见[维基百科](http://en.wikipedia.org/wiki/XOR_swap_algorithm))。这意味着,使用“异或运算”可以在不引入临时变量的前提下,互换两个变量的值。\n\n```javascript\nvar a = 10;\nvar b = 99;\n\na^=b, b^=a, a^=b;\n\na // 99\nb // 10\n\n```\n\n这是互换两个变量的值的最快方法。\n\n异或运算也可以用来取整。\n\n```javascript\n12.9^0 // 12\n\n```\n\n### 左移运算符(<<)\n\n左移运算符表示将一个数的二进制形式向前移动,尾部补0。\n\n```javascript\n4 << 1\n// 8\n// 因为4的二进制形式为100,左移一位为1000(即十进制的8)\n\n-4 << 1\n// -8\n\n```\n\n上面代码中,`-4`左移一位之所以得到`-8`,是因为-4的二进制形式是`11111111111111111111111111111100`,左移一位后得到`11111111111111111111111111111000`,该数转为十进制(减去`1`后取反,再加上负号)即为`-8`。\n\n如果左移`0`位,就相当于取整,对于正数和负数都有效。\n\n```javascript\n13.5 << 0\n// 13\n\n-13.5 << 0\n// -13\n\n```\n\n左移运算符用于二进制数值非常方便。\n\n```javascript\n\nvar color = {r: 186, g: 218, b: 85};\n\n// RGB to HEX\nvar rgb2hex = function(r, g, b) {\n return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).substr(1);\n}\n\nrgb2hex(color.r,color.g,color.b)\n// \"#bada55\"\n\n```\n\n上面代码使用左移运算符,将颜色的RGB值转为HEX值。\n\n### 右移运算符(>>)\n\n右移运算符表示将一个数的二进制形式向右移动,头部补上最左位的值,即正数补0,负数补1。\n\n```javascript\n4 >> 1\n// 2\n/*\n// 因为4的二进制形式为00000000000000000000000000000100,\n// 右移一位得到00000000000000000000000000000010,\n// 即为十进制的2\n*/\n\n-4 >> 1\n// -2\n/*\n// 因为-4的二进制形式为11111111111111111111111111111100,\n// 右移一位,头部补1,得到11111111111111111111111111111110,\n// 即为十进制的-2\n*/\n```\n\n右移运算可以模拟2的整除运算。\n\n```javascript\n5 >> 1\n// 相当于 5 / 2 = 2\n\n21 >> 2\n// 相当于 21 / 4 = 5\n\n21 >> 3\n// 相当于 21 / 8 = 2\n\n21 >> 4\n// 相当于 21 / 16 = 1\n```\n\n### 小数取整的方法\n* 通过`Math`中自带的方法\n * `Math.floor()`、`Math.round()`、`Math.ceil()`、`parseInt()`(这个会舍弃小数点`.`)\n* 位运算 所有位运算会把`NaN`、`Infinity`、`null`、`undefined` 当作0来处理\n * `-4.1 << 0 // 4` 左移`0`位\n * `12.9^0 // 12`\n * `~~2.9 // 2`\n * `-2.9 | 0 // 2`\n\n### 带符号位的右移运算符(>>>)\n\n该运算符表示将一个数的二进制形式向右移动,不管正数或负数,头部一律补`0`。所以,该运算总是得到正值,这就是它的名称`“带符号位的右移”`的涵义。对于正数,该运算的结果与右移运算符(>>)完全一致,区别主要在于负数。\n\n```javascript\n4 >>> 1\n// 2\n\n-4 >>> 1\n// 2147483646\n/*\n// 因为-4的二进制形式为11111111111111111111111111111100,\n// 带符号位的右移一位,得到01111111111111111111111111111110,\n// 即为十进制的2147483646。\n*/\n```\n\n### 开关作用\n\n位运算符可以用作设置对象属性的开关。\n假定某个对象有四个开关,每个开关都是一个变量,取值为2的整数次幂。\n```javascript\nvar FLAG_A = 1; // 0001\nvar FLAG_B = 2; // 0010\nvar FLAG_C = 4; // 0100\nvar FLAG_D = 8; // 1000\n\n```\n\n上面代码设置A、B、C、D四个开关,每个开关分别占有1个二进制位。\n现在假设需要打开ABD三个开关,我们可以构造一个掩码变量。\n```javascript\nvar mask = FLAG_A | FLAG_B | FLAG_D; // 0001 | 0010 | 1000 => 1011\n\n```\n\n上面代码对ABD三个变量进行“或运算”,得到掩码值为二进制的`1011`。\n有了掩码,就可以用“与运算”检验当前设置是否与开关设置一致。\n\n```javascript\nif (flags & FLAG_C) { // 0101 & 0100 => 0100 => true\n // ...\n}\n\n```\n\n上面代码表示,如果当前设置与掩码一致,则返回`true`,否则返回`false`。\n`“或运算”`可以将当前设置改成开关设置。\n\n```javascript\nflags |= mask; \n\n```\n\n“与运算”可以将当前设置中凡是与开关设置不一样的项,全部关闭。\n\n```javascript\nflags &= mask; \n\n```\n\n“异或运算”可以切换(toggle)当前设置。\n\n```javascript\nflags = flags ^ mask; \n\n```\n\n“否运算”可以翻转当前设置。\n\n```javascript\nflags = ~flags;\n\n```\n\n## 其他运算符\n\n### 圆括号运算符\n\n在JavaScript中,圆括号是一种运算符,它有两种用法:如果把表达式放在圆括号之中,作用是求值;如果跟在函数的后面,作用是调用函数。\n\n把表达式放在圆括号之中,将返回表达式的值。\n\n```javascript\n(1) // 1\n('a') // a\n(1+2) // 3\n\n```\n\n把对象放在圆括号之中,则会返回对象的值,即对象本身。\n\n```javascript\nvar o = {p:1};\n\n(o)\n// Object {p: 1}\n\n```\n\n将函数放在圆括号中,会返回函数本身。如果圆括号紧跟在函数的后面,就表示调用函数,即对函数求值。\n\n```javascript\nfunction f(){return 1;}\n\n(f) // function f(){return 1;}\nf() // 1\n\n```\n\n上面的代码先定义了一个函数,然后依次将函数放在圆括号之中、将圆括号跟在函数后面,得到的结果是不一样的。\n\n由于圆括号的作用是求值,如果将语句放在圆括号之中,就会报错,因为语句没有返回值。\n\n```javascript\n(var a =1)\n// SyntaxError: Unexpected token var\n\n```\n\n### void运算符\n\n`void`运算符的作用是执行一个表达式,然后不返回任何值,或者说返回`undefined`。\n\n```javascript\nvoid 0 // undefined\nvoid(0) // undefined\n```\n\n上面是`void`运算符的两种写法,都正确。建议采用后一种形式,即总是使用括号。因为`void`运算符的优先性很高,如果不使用括号,容易造成错误的结果。比如,`void 4 + 7`实际上等同于`(void 4) + 7`。\n\n下面是`void`运算符的一个例子。\n\n```javascript\nvar x = 3;\nvoid (x = 5) //undefined\nx // 5\n```\n\n这个运算符主要是用于书签工具(bookmarklet),以及用于在超级链接中插入代码,目的是返回`undefined`可以防止网页跳转。\n\n```javascript\n<a href=\"javascript:void window.open('http://example.com/')\">\n 点击打开新窗口\n</a>\n```\n\n上面代码用于在网页中创建一个链接,点击后会打开一个新窗口。如果没有`void`,点击后就会在当前窗口打开链接。\n\n下面是常见的网页中触发鼠标点击事件的写法。\n\n```javascript\n<a href=\"http://example.com\" onclick=\"f();\">文字</a>\n```\n\n上面代码有一个问题,函数`f`必须返回`false`,或者说`onclick`事件必须返回`false`,否则会引起浏览器跳转到`example.com`。\n\n```javascript\nfunction f() {\n // some code\n return false;\n}\n```\n\n或者写成\n\n```javascript\n<a href=\"http://example.com\" onclick=\"f();return false;\">文字</a>\n```\n\n`void`运算符可以取代上面两种写法。\n\n```javascript\n<a href=\"javascript:void(f())\">文字</a>\n```\n\n下面的代码会提交表单,但是不会产生页面跳转。\n\n```javascript\n<a href=\"javascript:void(document.form.submit())\">\n文字</a>\n```\n\n### 逗号运算符\n\n逗号运算符用于对两个表达式求值,并返回后一个表达式的值。\n\n```javascript\n\n\"a\", \"b\" // \"b\"\n\nvar x = 0;\nvar y = (x++, 10);\nx // 1\ny // 10\n\n```\n\n## 运算顺序\n\n**(1)运算符的优先级**\n\nJavaScript各种运算符的优先级别(Operator Precedence)是不一样的。优先级高的运算符先执行,优先级低的运算符后执行。\n\n```javascript\n4 + 5 * 6 // 34\n```\n\n上面的代码中,乘法运算符(`*`)的优先性高于加法运算符(`+`),所以先执行乘法,再执行加法,相当于下面这样。\n\n```javascript\n4 + (5 * 6) // 34\n```\n\n如果多个运算符混写在一起,常常会导致令人困惑的代码。\n\n```javascript\nvar x = 1;\nvar arr = [];\n\nvar y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];\n```\n\n上面代码中,变量`y`的值就很难看出来,因为这个表达式涉及5个运算符,到底谁的优先级最高,实在不容易记住。\n\n根据语言规格,这五个运算符的优先级从高到低依次为:小于等于(<=)、严格相等(===)、或(||)、三元(?:)、等号(=)。因此上面的表达式,实际的运算顺序如下。\n\n```javascript\nvar y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];\n```\n\n记住所有运算符的优先级,几乎是不可能的,也是没有必要的。\n\n**(2)圆括号的作用**\n\n圆括号可以用来提高运算的优先级,因为它的优先级是最高的,即圆括号中的运算符会第一个运算。\n\n```javascript\n(4 + 5) * 6 // 54\n```\n\n上面代码中,由于使用了圆括号,加法会先于乘法执行。\n\n由于运算符的优先级别十分繁杂,且都是来自硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要。\n\n**(3)左结合与右结合**\n\n对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的“左结合”(left-to-right associativity),即从左边开始计算。\n\n```javascript\nx + y + z\n```\n\n上面代码先计算最左边的`x`与`y`的和,然后再计算与`z`的和。\n\n但是少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的“右结合”(right-to-left associativity)。其中,最主要的是赋值运算符(`=`)和三元条件运算符(`?:`)。\n\n```javascript\nw = x = y = z;\nq = a ? b : c ? d : e ? f : g;\n```\n\n上面代码的运算结果,相当于下面的样子。\n\n```javascript\nw = (x = (y = z));\nq = a ? b : (c ? d : (e ? f : g));\n``` \n## 全部运算符优先级 汇总表\n下面的表将所有运算符按照优先级的不同从高到低排列。\n<table style=\"width:100%;margin-top:-8145px;\">\n <tr>\n <th>优先级</th>\n <th>运算类型</th>\n <th>关联性</th>\n <th>运算符</th>\n </tr>\n <tr>\n <td>19</td>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Grouping\" title=\"圆括号运算符( ) 用来控制表达式中的运算优先级.\"><code>圆括号</code></a></td>\n <td>n/a</td>\n <td><code>( … )</code></td>\n </tr>\n <tr>\n <td rowspan=\"3\">18</td>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Property_Accessors#点符号表示法\" title=\"属性访问器提供了两种方式用于访问一个对象的属性,它们分别是点符号和括号。\"><code>成员访问</code></a></td>\n <td>从左到右</td>\n <td><code>… . …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Property_Accessors#括号表示法\" title=\"属性访问器提供了两种方式用于访问一个对象的属性,它们分别是点符号和括号。\"><code>需计算的成员访问</code></a></td>\n <td>从左到右</td>\n <td><code>… [ … ]</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new\" title=\"new运算符的作用是创建一个对象实例。这个对象可以是用户自定义的,也可以是一些系统自带的带构造函数的对象。\"><code>new</code></a> (带参数列表)</td>\n <td>n/a</td>\n <td><code>new … ( … )</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\">17</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions\" title=\"JavaScript/Reference/Operators/Special_Operators/function_call\">函数调用</a></td>\n <td>从左到右</td>\n <td><code>… ( <var>… </var>)</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new\" title=\"JavaScript/Reference/Operators/Special_Operators/new_Operator\">new</a> (无参数列表)</td>\n <td>从右到左</td>\n <td><code>new …</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\">16</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">后置递增</a>(运算符在后)</td>\n <td>n/a</td>\n <td><code>… ++</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">后置递减</a>(运算符在后)</td>\n <td>n/a</td>\n <td><code>… --</code></td>\n </tr>\n <tr>\n <td rowspan=\"9\">15</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_NOT\">逻辑非</a></td>\n <td>从右到左</td>\n <td><code>! …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位非</a></td>\n <td>从右到左</td>\n <td><code>~ …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">一元加法</a></td>\n <td>从右到左</td>\n <td><code>+ …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_negation\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">一元减法</a></td>\n <td>从右到左</td>\n <td><code>- …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">前置递增</a></td>\n <td>从右到左</td>\n <td><code>++ …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">前置递减</a></td>\n <td>从右到左</td>\n <td><code>-- …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof\" title=\"JavaScript/Reference/Operators/Special_Operators/typeof_Operator\">typeof</a></td>\n <td>从右到左</td>\n <td><code>typeof …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void\" title=\"JavaScript/Reference/Operators/Special_Operators/void_Operator\">void</a></td>\n <td>从右到左</td>\n <td><code>void …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete\" title=\"JavaScript/Reference/Operators/Special_Operators/delete_Operator\">delete</a></td>\n <td>从右到左</td>\n <td><code>delete …</code></td>\n </tr>\n <tr>\n <td rowspan=\"3\">14</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Multiplication\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">乘法</a></td>\n <td>从左到右</td>\n <td><code>… * …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Division\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">除法</a></td>\n <td>从左到右</td>\n <td><code>… / …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">取模</a></td>\n <td>从左到右</td>\n <td><code>… % …</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\">13</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Addition\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">加法</a></td>\n <td>从左到右</td>\n <td><code>… + …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Subtraction\" title=\"JavaScript/Reference/Operators/Arithmetic_Operators\">减法</a></td>\n <td>从左到右</td>\n <td><code>… - …</code></td>\n </tr>\n <tr>\n <td rowspan=\"3\">12</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位左移</a></td>\n <td>从左到右</td>\n <td><code>… << …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位右移</a></td>\n <td>从左到右</td>\n <td><code>… >> …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">无符号右移</a></td>\n <td>从左到右</td>\n <td><code>… >>> …</code></td>\n </tr>\n <tr>\n <td rowspan=\"6\">11</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">小于</a></td>\n <td>从左到右</td>\n <td><code>… < …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than__or_equal_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">小于等于</a></td>\n <td>从左到右</td>\n <td><code>… <= …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">大于</a></td>\n <td>从左到右</td>\n <td><code>… > …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_or_equal_operator\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">大于等于</a></td>\n <td>从左到右</td>\n <td><code>… >= …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in\" title=\"JavaScript/Reference/Operators/Special_Operators/in_Operator\">in</a></td>\n <td>从左到右</td>\n <td><code>… in …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof\" title=\"JavaScript/Reference/Operators/Special_Operators/instanceof_Operator\">instanceof</a></td>\n <td>从左到右</td>\n <td><code>… instanceof …</code></td>\n </tr>\n <tr>\n <td rowspan=\"4\">10</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Equality\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">等号</a></td>\n <td>从左到右</td>\n <td><code>… == …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Inequality\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">非等号</a></td>\n <td>从左到右</td>\n <td><code>… != …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Identity\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">全等号</a></td>\n <td>从左到右</td>\n <td><code>… === …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Nonidentity\" title=\"JavaScript/Reference/Operators/Comparison_Operators\">非全等号</a></td>\n <td>从左到右</td>\n <td><code>… !== …</code></td>\n </tr>\n <tr>\n <td>9</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_AND\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位与</a></td>\n <td>从左到右</td>\n <td><code>… & …</code></td>\n </tr>\n <tr>\n <td>8</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_XOR\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位异或</a></td>\n <td>从左到右</td>\n <td><code>… ^ …</code></td>\n </tr>\n <tr>\n <td>7</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_OR\" title=\"JavaScript/Reference/Operators/Bitwise_Operators\">按位或</a></td>\n <td>从左到右</td>\n <td><code>… | …</code></td>\n </tr>\n <tr>\n <td>6</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_AND\" title=\"JavaScript/Reference/Operators/Logical_Operators\">逻辑与</a></td>\n <td>从左到右</td>\n <td><code>… && …</code></td>\n </tr>\n <tr>\n <td>5</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR\" title=\"JavaScript/Reference/Operators/Logical_Operators\">逻辑或</a></td>\n <td>从左到右</td>\n <td><code>… || …</code></td>\n </tr>\n <tr>\n <td>4</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator\" title=\"JavaScript/Reference/Operators/Special_Operators/Conditional_Operator\">条件运算符</a></td>\n <td>从右到左</td>\n <td><code>… ? … : …</code></td>\n </tr>\n <tr>\n <td rowspan=\"12\">3</td>\n <td rowspan=\"12\"><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators\" title=\"JavaScript/Reference/Operators/Assignment_Operators\">赋值</a></td>\n <td rowspan=\"12\">从右到左</td>\n <td><code>… = …</code></td>\n </tr>\n <tr>\n <td><code>… += …</code></td>\n </tr>\n <tr>\n <td><code>… -= …</code></td>\n </tr>\n <tr>\n <td><code>… *= …</code></td>\n </tr>\n <tr>\n <td><code>… /= …</code></td>\n </tr>\n <tr>\n <td><code>… %= …</code></td>\n </tr>\n <tr>\n <td><code>… <<= …</code></td>\n </tr>\n <tr>\n <td><code>… >>= …</code></td>\n </tr>\n <tr>\n <td><code>… >>>= …</code></td>\n </tr>\n <tr>\n <td><code>… &= …</code></td>\n </tr>\n <tr>\n <td><code>… ^= …</code></td>\n </tr>\n <tr>\n <td><code>… |= …</code></td>\n </tr>\n <tr>\n <td rowspan=\"2\" colspan=\"1\">2</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield\" title=\"JavaScript/Reference/Operators/yield\">yield</a></td>\n <td>从右到左</td>\n <td><code>yield …</code></td>\n </tr>\n <tr>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*\" title=\"JavaScript/Reference/Operators/yield\">yield*</a></td>\n <td>从右到左</td>\n <td>yield* …</td>\n </tr>\n <tr>\n <td>1</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator\" title=\"JavaScript/Reference/Operators/Spread_operator\">Spread</a></td>\n <td>n/a</td>\n <td><code>...</code> …</td>\n </tr>\n <tr>\n <td>0</td>\n <td><a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator\" title=\"JavaScript/Reference/Operators/Comma_Operator\">逗号</a></td>\n <td>从左到右</td>\n <td><code>… , …</code></td>\n </tr>\n\n</table>","slug":"运算符","published":1,"updated":"2016-06-11T07:48:31.806Z","layout":"post","photos":[],"link":"","_id":"citf9olwl0005skv792lxs5e0"},{"title":"类型转换","date":"2016-02-18T08:08:26.000Z","comments":1,"_content":"JavaScript是一种动态类型语言,变量是没有类型的,可以随时赋予任意值。但是,数据本身和各种运算是有类型的,因此运算时变量需要转换类型。大多数情况下,这种数据类型转换是自动的,但是有时也需要手动强制转换。\n<!--more-->\n## 强制转换\n强制转换主要指使用Number、String和Boolean三个构造函数,手动将各种类型的值,转换成数字、字符串或者布尔值。\n详细了解看这里:[好好学学Number!](http://www.yangshengdonghome.com/2016/01/29/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6number/)\n## 自动转换\n\n当遇到以下几种情况,JavaScript会自动转换数据类型:\n\n- 不同类型的数据进行互相运算;\n- 对非布尔值类型的数据求布尔值;\n- 对非数值类型的数据使用一元运算符(即“+”和“-”)。\n\n### 自动转换为布尔值\n\n当JavaScript遇到预期为布尔值的地方(比如if语句的条件部分),就会将非布尔值的参数自动转换为布尔值。它的转换规则与上面的“强制转换成布尔值”的规则相同,也就是说,在预期为布尔值的地方,系统内部会自动调用Boolean方法。\n\n因此除了以下六个值,其他都是自动转为true:\n\n- undefined\n- null\n- -0\n- +0\n- NaN\n- ''(空字符串)\n\n### 自动转换为字符串\n\n当JavaScript遇到预期为字符串的地方,就会将非字符串的数据自动转为字符串,转换规则与“强制转换为字符串”相同。\n\n字符串的自动转换,主要发生在加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。\n\n```javascript\n'5' + 1 // '51'\n'5' + true // \"5true\"\n'5' + false // \"5false\"\n'5' + {} // \"5[object Object]\"\n'5' + [] // \"5\"\n'5' + function (){} // \"5function (){}\"\n'5' + undefined // \"5undefined\"\n'5' + null // \"5null\"\n```\n\n### 自动转换为数值\n\n当JavaScript遇到预期为数值的地方,就会将参数值自动转换为数值,转换规则与“强制转换为数值”相同。\n\n除了加法运算符有可能把运算子转为字符串,其他运算符都会把两侧的运算子自动转成数值。\n\n```javascript\n'5' - '2' // 3\n'5' * '2' // 10\ntrue - 1 // 0\nfalse - 1 // -1\n'1' - 1 // 0\n'5'*[] // 0\nfalse/'5' // 0\n'abc'-1 // NaN\n```\n上面都是二元算术运算符的例子,JavaScript的两个一元算术运算符——正号和负号——也会把运算子自动转为数值。\n```javascript\n+'abc' // NaN\n-'abc' // NaN\n+true // 1\n-false // 0\n```\n\n### 小结\n\n由于自动转换有很大的不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean、Number和String方法进行显式转换。\n\n## 加法运算符的类型转化\n\n加法运算符(+)需要特别讨论,因为它可以完成两种运算(加法和字符连接),所以不仅涉及到数据类型的转换,还涉及到确定运算类型。\n\n### 三种抽象操作\n加法运算符会触发三种类型转换(不包括一元`+`): 将值转换为原始值,转换为数字,转换为字符串,这刚好对应了JavaScript引擎内部的三种抽象操作: `ToPrimitive()`,`ToNumber()`,`ToString()`。\n\n#### 通过ToPrimitive()将值转换为原始值\n\nJavaScript引擎内部的抽象操作`ToPrimitive()`有着这样的签名:\n```javascript\n ToPrimitive(input, PreferredType?)\n```\n可选参数PreferredType可以是Number或者String,它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值。\n如果PreferredType被标志为Number,则会进行下面的操作来转换输入的值:\n\n1. 如果输入的值已经是个原始值,则直接返回它。\n2. 否则,如果输入的值是一个对象。则调用该对象的valueOf()方法。如果valueOf()方法的返回值是一个原始值,则返回这个原始值。\n3. 否则,调用这个对象的toString()方法。如果toString()方法的返回值是一个原始值,则返回这个原始值。\n4. 否则,抛出TypeError异常。\n\n如果PreferredType被标志为String,则转换操作的第二步和第三步的顺序会调换。\n如果没有PreferredType这个参数,则PreferredType的值会按照这样的规则来自动设置:<span style=\"color:red;\">Date类型的对象会被设置为String,其它类型的值会被设置为Number。</span>\n如:\n```javascript\nnew Date() + 1 // \"Sat Jun 11 2016 12:32:55 GMT+0800 1\" 调用了 toString()\n\nvar a = {\n valueOf:function(){\n return 222\n \n },\n toString:function(){\n return \"aaa\"\n \n }};\n \"1\" + a//\"1222\"\n```\n#### 通过ToNumber()将值转换为数字\n下面的表格解释了ToNumber()是如何将原始值转换成数字的。\n\n| 参数 | 结果 |\n|:-----|:---:|\n|undefined| **`NaN`** |\n|null| **+0** |\n|布尔值| **true被转换为1,false转换为+0** |\n|数字| **无需转换** |\n|字符串| **由字符串解析为数字。例如,\"324\"被转换为324** |\n\n如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, Number)将该对象转换为原始值,然后在调用ToNumber()将这个原始值转换为数字。\n\n#### 通过ToString()将值转换为字符串\n下面的表格解释了ToString()是如何将原始值转换成字符串的。\n\n| 参数 | 结果 |\n|:-----|:---:|\n|undefined| **\"undefined\"** |\n|null| **\"null\"** |\n|布尔值| **\"true\" 或者 \"false\"** |\n|数字| **数字作为字符串,比如。\"1.765\"** |\n|字符串| **无需转换** |\n如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, String)将该对象转换为原始值,然后再调用ToString()将这个原始值转换为字符串。\n\n### 三种情况\n\n加法运算符的类型转换,可以分成三种情况讨论。\n\n**(1)运算子之中存在字符串**\n\n两个运算子之中,只要有一个是字符串,则另一个不管是什么类型,都会被自动转为字符串,然后执行字符串连接运算。前面的《自动转换为字符串》一节,已经举了很多例子。\n\n**(2)两个运算子都为数值或布尔值**\n\n这种情况下,执行加法运算,布尔值转为数值(`true`为1,`false`为0)。\n\n```javascript\ntrue + 5 // 6\n\ntrue + true // 2\n```\n\n**(3)运算子之中存在对象**\n\n运算子之中存在对象(或者准确地说,存在非简单类型的值),则先调用该对象的`valueOf`方法。如果返回结果为简单类型的值,则运用上面两条规则;否则继续调用该对象的`toString`方法,对其返回值运用上面两条规则。\n\n```javascript\n1 + [1,2]// \"11,2\"\nvar arr = [];\narr.valueOf() === arr //true\n```\n上面代码的运行顺序是,先调用`[1,2].valueOf()`,结果还是数组`[1,2]`本身,则继续调用`[1,2].toString()`,结果字符串`“1,2”`,所以最终结果为字符串`“11,2”`。\n```javascript\n1 + {a:1} // \"1[object Object]\"\n```\n对象`{a:1}`的`valueOf`方法,返回的就是这个对象的本身,因此接着对它调用`toString`方法。`({a:1}).toString()`默认返回字符串`\"[object Object]\"`,所以最终结果就是字符串`“1[object Object]”`。\n\n有趣的是,如果更换上面代码的运算次序,就会得到不同的值。\n```javascript\n{a:1} + 1 // 1\n```\n原来此时,JavaScript引擎不将`{a:1}`视为对象,而是视为一个代码块,这个代码块没有返回值,所以被忽略。因此上面的代码,实际上等同于 `{a:1};+1`,所以最终结果就是1。为了避免这种情况,需要对{a:1}加上括号。\n```javascript\n({a:1})+1 //\"[object Object]1\"\nconsole.log({a:1}+1) // \"[object Object]1\"\n```\n将`{a:1}`放置在括号之中,由于JavaScript引擎预期括号之中是一个值,所以不把它当作代码块处理,而是当作对象处理,所以最终结果为`“[object Object]1”`。\n\n```javascript\n1 + {valueOf:function(){return 2;}}// 3\n```\n上面代码的valueOf方法返回数值2,所以最终结果为3。\n```javascript\n1 + {valueOf:function(){return {};}}// \"1[object Object]\"\n```\n上面代码的`valueOf`方法返回一个空对象,则继续调用`toString`方法,所以最终结果是`“1[object Object]”`。\n```javascript\n1 + {valueOf:function(){return {};},toString:function(){return 2;}}// 3\n```\n上面代码的toString方法返回数值2(不是字符串),则最终结果就是数值3。\n```javascript\n1 + {valueOf:function(){return {};},toString:function(){return {};}}// TypeError: Cannot convert object to primitive value\n```\n上面代码的`toString`方法返回一个空对象,JavaScript就会报错,表示无法获得原始类型的值。\n\n### 四个特殊表达式\n\n有了上面这些例子,我们再进一步来看四个特殊表达式。\n\n**(1)空数组 + 空数组**\n\n```javascript\n[] + []// \"\"\n```\n\n首先,对空数组调用`valueOf`方法,返回的是数组本身;因此再对空数组调用`toString`方法,生成空字符串;所以,最终结果就是空字符串。\n\n**(2)空数组 + 空对象**\n```javascript\n[] + {}// \"[object Object]\"\n```\n\n这等同于空字符串与字符串`“[object Object]”`相加。因此,结果就是`“[object Object]”`。\n\n**(3)空对象 + 空数组**\n\n```javascript\n{} + []// 0\n```\n\nJavaScript引擎将空对象视为一个空的代码块,加以忽略。因此,整个表达式就变成`“+ []”`,等于对空数组求正值,因此结果就是0。转化过程如下:\n\n```javascript\n+ []\n\n// Number([])\n// Number([].toString())\n// Number(\"\")\n// 0\n```\n\n如果JavaScript不把前面的空对象视为代码块,则结果为字符串`“[object Object]”`。\n\n```javascript\n({}) + []// \"[object Object]\"\n```\n\n**(4)空对象 + 空对象**\n\n```javascript\n{} + {}// NaN\n```\n\nJavaScript同样将第一个空对象视为一个空代码块,整个表达式就变成`“+ {}”`。这时,后一个空对象的ValueOf方法得到本身,再调用toSting方法,得到字符串`“[object Object]”`,然后再将这个字符串转成数值,得到`NaN`。所以,最后的结果就是`NaN`。转化过程如下:\n\n```javascript\n+ {}\n\n// Number({})\n// Number({}.toString())\n// Number(\"[object Object]\")\n```\n\n如果,第一个空对象不被JavaScript视为空代码块,就会得到`“[object Object][object Object]”`的结果。\n\n```javascript\n({}) + {}// \"[object Object][object Object]\"\n\n({} + {})// \"[object Object][object Object]\"\t\n\nconsole.log({} + {})// \"[object Object][object Object]\"\n\nvar a = {} + {};\na\n// \"[object Object][object Object]\"\t\n```\n\n需要指出的是,对于第三和第四种情况,Node.js的运行结果不同于浏览器环境。\n\n```javascript\n{} + {}// \"[object Object][object Object]\"\n\n{} + []// \"[object Object]\"\n```\n\n可以看到,Node.js没有把第一个空对象视为代码块。原因是Node.js的命令行环境,内部执行机制大概是下面的样子:\n\n```javascript\neval.call(this,\"(function(){return {} + {}}).call(this)\")\n```\nNode.js把命令行输入都放在eval中执行,所以不会把起首的大括号理解为空代码块加以忽略。\n\n参考链接:[阮一峰](http://javascript.ruanyifeng.com/grammar/conversion.html)","source":"_posts/类型转换-2016-02-18.md","raw":"title: 类型转换\ndate: 2016-02-18 16:08:26\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\nJavaScript是一种动态类型语言,变量是没有类型的,可以随时赋予任意值。但是,数据本身和各种运算是有类型的,因此运算时变量需要转换类型。大多数情况下,这种数据类型转换是自动的,但是有时也需要手动强制转换。\n<!--more-->\n## 强制转换\n强制转换主要指使用Number、String和Boolean三个构造函数,手动将各种类型的值,转换成数字、字符串或者布尔值。\n详细了解看这里:[好好学学Number!](http://www.yangshengdonghome.com/2016/01/29/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6number/)\n## 自动转换\n\n当遇到以下几种情况,JavaScript会自动转换数据类型:\n\n- 不同类型的数据进行互相运算;\n- 对非布尔值类型的数据求布尔值;\n- 对非数值类型的数据使用一元运算符(即“+”和“-”)。\n\n### 自动转换为布尔值\n\n当JavaScript遇到预期为布尔值的地方(比如if语句的条件部分),就会将非布尔值的参数自动转换为布尔值。它的转换规则与上面的“强制转换成布尔值”的规则相同,也就是说,在预期为布尔值的地方,系统内部会自动调用Boolean方法。\n\n因此除了以下六个值,其他都是自动转为true:\n\n- undefined\n- null\n- -0\n- +0\n- NaN\n- ''(空字符串)\n\n### 自动转换为字符串\n\n当JavaScript遇到预期为字符串的地方,就会将非字符串的数据自动转为字符串,转换规则与“强制转换为字符串”相同。\n\n字符串的自动转换,主要发生在加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。\n\n```javascript\n'5' + 1 // '51'\n'5' + true // \"5true\"\n'5' + false // \"5false\"\n'5' + {} // \"5[object Object]\"\n'5' + [] // \"5\"\n'5' + function (){} // \"5function (){}\"\n'5' + undefined // \"5undefined\"\n'5' + null // \"5null\"\n```\n\n### 自动转换为数值\n\n当JavaScript遇到预期为数值的地方,就会将参数值自动转换为数值,转换规则与“强制转换为数值”相同。\n\n除了加法运算符有可能把运算子转为字符串,其他运算符都会把两侧的运算子自动转成数值。\n\n```javascript\n'5' - '2' // 3\n'5' * '2' // 10\ntrue - 1 // 0\nfalse - 1 // -1\n'1' - 1 // 0\n'5'*[] // 0\nfalse/'5' // 0\n'abc'-1 // NaN\n```\n上面都是二元算术运算符的例子,JavaScript的两个一元算术运算符——正号和负号——也会把运算子自动转为数值。\n```javascript\n+'abc' // NaN\n-'abc' // NaN\n+true // 1\n-false // 0\n```\n\n### 小结\n\n由于自动转换有很大的不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean、Number和String方法进行显式转换。\n\n## 加法运算符的类型转化\n\n加法运算符(+)需要特别讨论,因为它可以完成两种运算(加法和字符连接),所以不仅涉及到数据类型的转换,还涉及到确定运算类型。\n\n### 三种抽象操作\n加法运算符会触发三种类型转换(不包括一元`+`): 将值转换为原始值,转换为数字,转换为字符串,这刚好对应了JavaScript引擎内部的三种抽象操作: `ToPrimitive()`,`ToNumber()`,`ToString()`。\n\n#### 通过ToPrimitive()将值转换为原始值\n\nJavaScript引擎内部的抽象操作`ToPrimitive()`有着这样的签名:\n```javascript\n ToPrimitive(input, PreferredType?)\n```\n可选参数PreferredType可以是Number或者String,它只代表了一个转换的偏好,转换结果不一定必须是这个参数所指的类型,但转换结果一定是一个原始值。\n如果PreferredType被标志为Number,则会进行下面的操作来转换输入的值:\n\n1. 如果输入的值已经是个原始值,则直接返回它。\n2. 否则,如果输入的值是一个对象。则调用该对象的valueOf()方法。如果valueOf()方法的返回值是一个原始值,则返回这个原始值。\n3. 否则,调用这个对象的toString()方法。如果toString()方法的返回值是一个原始值,则返回这个原始值。\n4. 否则,抛出TypeError异常。\n\n如果PreferredType被标志为String,则转换操作的第二步和第三步的顺序会调换。\n如果没有PreferredType这个参数,则PreferredType的值会按照这样的规则来自动设置:<span style=\"color:red;\">Date类型的对象会被设置为String,其它类型的值会被设置为Number。</span>\n如:\n```javascript\nnew Date() + 1 // \"Sat Jun 11 2016 12:32:55 GMT+0800 1\" 调用了 toString()\n\nvar a = {\n valueOf:function(){\n return 222\n \n },\n toString:function(){\n return \"aaa\"\n \n }};\n \"1\" + a//\"1222\"\n```\n#### 通过ToNumber()将值转换为数字\n下面的表格解释了ToNumber()是如何将原始值转换成数字的。\n\n| 参数 | 结果 |\n|:-----|:---:|\n|undefined| **`NaN`** |\n|null| **+0** |\n|布尔值| **true被转换为1,false转换为+0** |\n|数字| **无需转换** |\n|字符串| **由字符串解析为数字。例如,\"324\"被转换为324** |\n\n如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, Number)将该对象转换为原始值,然后在调用ToNumber()将这个原始值转换为数字。\n\n#### 通过ToString()将值转换为字符串\n下面的表格解释了ToString()是如何将原始值转换成字符串的。\n\n| 参数 | 结果 |\n|:-----|:---:|\n|undefined| **\"undefined\"** |\n|null| **\"null\"** |\n|布尔值| **\"true\" 或者 \"false\"** |\n|数字| **数字作为字符串,比如。\"1.765\"** |\n|字符串| **无需转换** |\n如果输入的值是一个对象,则会首先会调用ToPrimitive(obj, String)将该对象转换为原始值,然后再调用ToString()将这个原始值转换为字符串。\n\n### 三种情况\n\n加法运算符的类型转换,可以分成三种情况讨论。\n\n**(1)运算子之中存在字符串**\n\n两个运算子之中,只要有一个是字符串,则另一个不管是什么类型,都会被自动转为字符串,然后执行字符串连接运算。前面的《自动转换为字符串》一节,已经举了很多例子。\n\n**(2)两个运算子都为数值或布尔值**\n\n这种情况下,执行加法运算,布尔值转为数值(`true`为1,`false`为0)。\n\n```javascript\ntrue + 5 // 6\n\ntrue + true // 2\n```\n\n**(3)运算子之中存在对象**\n\n运算子之中存在对象(或者准确地说,存在非简单类型的值),则先调用该对象的`valueOf`方法。如果返回结果为简单类型的值,则运用上面两条规则;否则继续调用该对象的`toString`方法,对其返回值运用上面两条规则。\n\n```javascript\n1 + [1,2]// \"11,2\"\nvar arr = [];\narr.valueOf() === arr //true\n```\n上面代码的运行顺序是,先调用`[1,2].valueOf()`,结果还是数组`[1,2]`本身,则继续调用`[1,2].toString()`,结果字符串`“1,2”`,所以最终结果为字符串`“11,2”`。\n```javascript\n1 + {a:1} // \"1[object Object]\"\n```\n对象`{a:1}`的`valueOf`方法,返回的就是这个对象的本身,因此接着对它调用`toString`方法。`({a:1}).toString()`默认返回字符串`\"[object Object]\"`,所以最终结果就是字符串`“1[object Object]”`。\n\n有趣的是,如果更换上面代码的运算次序,就会得到不同的值。\n```javascript\n{a:1} + 1 // 1\n```\n原来此时,JavaScript引擎不将`{a:1}`视为对象,而是视为一个代码块,这个代码块没有返回值,所以被忽略。因此上面的代码,实际上等同于 `{a:1};+1`,所以最终结果就是1。为了避免这种情况,需要对{a:1}加上括号。\n```javascript\n({a:1})+1 //\"[object Object]1\"\nconsole.log({a:1}+1) // \"[object Object]1\"\n```\n将`{a:1}`放置在括号之中,由于JavaScript引擎预期括号之中是一个值,所以不把它当作代码块处理,而是当作对象处理,所以最终结果为`“[object Object]1”`。\n\n```javascript\n1 + {valueOf:function(){return 2;}}// 3\n```\n上面代码的valueOf方法返回数值2,所以最终结果为3。\n```javascript\n1 + {valueOf:function(){return {};}}// \"1[object Object]\"\n```\n上面代码的`valueOf`方法返回一个空对象,则继续调用`toString`方法,所以最终结果是`“1[object Object]”`。\n```javascript\n1 + {valueOf:function(){return {};},toString:function(){return 2;}}// 3\n```\n上面代码的toString方法返回数值2(不是字符串),则最终结果就是数值3。\n```javascript\n1 + {valueOf:function(){return {};},toString:function(){return {};}}// TypeError: Cannot convert object to primitive value\n```\n上面代码的`toString`方法返回一个空对象,JavaScript就会报错,表示无法获得原始类型的值。\n\n### 四个特殊表达式\n\n有了上面这些例子,我们再进一步来看四个特殊表达式。\n\n**(1)空数组 + 空数组**\n\n```javascript\n[] + []// \"\"\n```\n\n首先,对空数组调用`valueOf`方法,返回的是数组本身;因此再对空数组调用`toString`方法,生成空字符串;所以,最终结果就是空字符串。\n\n**(2)空数组 + 空对象**\n```javascript\n[] + {}// \"[object Object]\"\n```\n\n这等同于空字符串与字符串`“[object Object]”`相加。因此,结果就是`“[object Object]”`。\n\n**(3)空对象 + 空数组**\n\n```javascript\n{} + []// 0\n```\n\nJavaScript引擎将空对象视为一个空的代码块,加以忽略。因此,整个表达式就变成`“+ []”`,等于对空数组求正值,因此结果就是0。转化过程如下:\n\n```javascript\n+ []\n\n// Number([])\n// Number([].toString())\n// Number(\"\")\n// 0\n```\n\n如果JavaScript不把前面的空对象视为代码块,则结果为字符串`“[object Object]”`。\n\n```javascript\n({}) + []// \"[object Object]\"\n```\n\n**(4)空对象 + 空对象**\n\n```javascript\n{} + {}// NaN\n```\n\nJavaScript同样将第一个空对象视为一个空代码块,整个表达式就变成`“+ {}”`。这时,后一个空对象的ValueOf方法得到本身,再调用toSting方法,得到字符串`“[object Object]”`,然后再将这个字符串转成数值,得到`NaN`。所以,最后的结果就是`NaN`。转化过程如下:\n\n```javascript\n+ {}\n\n// Number({})\n// Number({}.toString())\n// Number(\"[object Object]\")\n```\n\n如果,第一个空对象不被JavaScript视为空代码块,就会得到`“[object Object][object Object]”`的结果。\n\n```javascript\n({}) + {}// \"[object Object][object Object]\"\n\n({} + {})// \"[object Object][object Object]\"\t\n\nconsole.log({} + {})// \"[object Object][object Object]\"\n\nvar a = {} + {};\na\n// \"[object Object][object Object]\"\t\n```\n\n需要指出的是,对于第三和第四种情况,Node.js的运行结果不同于浏览器环境。\n\n```javascript\n{} + {}// \"[object Object][object Object]\"\n\n{} + []// \"[object Object]\"\n```\n\n可以看到,Node.js没有把第一个空对象视为代码块。原因是Node.js的命令行环境,内部执行机制大概是下面的样子:\n\n```javascript\neval.call(this,\"(function(){return {} + {}}).call(this)\")\n```\nNode.js把命令行输入都放在eval中执行,所以不会把起首的大括号理解为空代码块加以忽略。\n\n参考链接:[阮一峰](http://javascript.ruanyifeng.com/grammar/conversion.html)","slug":"类型转换","published":1,"updated":"2016-06-11T06:28:09.517Z","layout":"post","photos":[],"link":"","_id":"citf9olww000askv773708h1r"},{"title":"类型检验","date":"2016-02-19T02:38:35.000Z","comments":1,"_content":"在JavaScript中,有5种简单数据类型和1种复杂数据类型,简单数据类型有:Undefined,Null,Boolean, Number和String;复杂数据类型是Object,Object中还细分了很多具体的类型,比如:Array,Function,Date,RegExp等等,还有我们自己定义的对象,自定义类型。今天我们就来探讨一下,使用什么方法判断一个出一个变量的类型。\n<!--more-->\n## 类型系统\n{% asset_img type.jpg %}\n\n## 类型转化表\n| Value | Boolean | Number | String |\n|:-----|:-----:|:-----:|:-----:|\n| undefined | false | NaN | \"undefined\" |\n| null | false | 0 | \"null\" |\n| true | true | 1 | \"true\" |\n| false | false | 0 | \"false\" |\n| '' | false | 0 | \"\" |\n| '123' | true | 123 | \"123\" |\n| '1a' | true | NaN | \"1a\" |\n| 0 | false | 0 | \"0\" |\n| 1 | true | 1 | \"1\" |\n| Infinity | true | Infinity | \"Infinity\" |\n| NaN | false | NaN | \"NaN\" |\n| {} | true | NaN | \"[object Object]\" |\n\n## 类型判断\n\n### typeof\n\n* 可以判别简单数据类型(null除外)\n* 不可判别具体的复杂数据类型(Function除外)\n\n```javascript\n//1. 可以判别简单数据类型(`null`除外)\nvar obj = 1;\ntypeof obj; //\"number\"\nobj = \"abc\"\ntypeof obj; //\"string\"\nobj = false\ntypeof obj; //\"boolean\"\nobj = undefined;\ntypeof obj; //\"undefined\"\nobj = null;\ntypeof obj; //\"object\",WTF,其实这是js的一个bug,人艰不拆 T_T\n//2. 不可判别具体的复杂数据类型(`Function`除外)\nobj = function(){};\ntypeof obj; //\"function\"\nobj = [];\ntypeof obj; //\"object\"\nobj = {};\ntypeof obj; //\"object\"\nobj = /w/g;\ntypeof obj //\"object\"\nobj = new Error();\ntypeof obj //\"object\"\n```\n\n### instanceof\n`instanceof`左侧为查询变量,右侧为标识对象的类。\n* 能够判别复杂数据类型,但是不能判别`具体的复杂数据类型`和Object类型。\n* 不能判别简单数据类型,但是能判别通过基本包装行创建的变量。\n* 能够判别自定义类型。\n\n```javascript\nconsole.log(\n //1. 不能判别简单数据类型 (number、string、boolean、null、undefined),\n 99 instanceof Number,//false\n \"2\" instanceof String,//false\n true instanceof Boolean,//false\n undefined instanceof Object,//false\n null instanceof Object,//false\n \n //2.能够判别复杂数据类型,\n [] instanceof Array,//true\n {} instanceof Object,//true\n (function(){}) instanceof Function,//true\n (new Date()) instanceof Date,//true\n /\\d/ instanceof RegExp,//true\n (new Error()) instanceof Error,//true\n //但是不能判别`具体的复杂数据类型`和Object类型\n [] instanceof Object,//true\n /\\d/ instanceof Object//true\n)\n//但是使用基本包装类型创建的对象通过 instanceof 可以判别。\n//如果指定obj = \"abc\"则obj保存的实际上就是abc的值,是一个基本类型。而如果指定obj = new String('abc')那么obj实际上保存的是一个指向字符串对象的指针。\nvar obj = new String('abc');\nobj instanceof String//true\n\"abc\" instanceof String//false \n\n//3. 能够判别自定义类型\nfunction Point(x, y) {\n this.x = x;\n this.y = y;\n}\nvar c = new Point(2,3);\n\nc instanceof Point;//true\n```\n### Object.prototype.toString.call()\n* 能判别所有类型(除去自定义类型)\n* 不能判别自定义类型\n\n```javascript\n//1. 所有类型(除去自定义类型)\nconsole.log(\n Object.prototype.toString.call(1),// '[object Number]'\n Object.prototype.toString.call(\"abc\"),//'[object String]'\n Object.prototype.toString.call(true),//'[object Boolean]'\n Object.prototype.toString.call([]),//'[object Array]'\n Object.prototype.toString.call({}),//'[object Object]'\n Object.prototype.toString.call(function(){}),//'[object Function]'\n Object.prototype.toString.call(undefined),//'[object Undefined]'\n Object.prototype.toString.call(null),//'[object Null]'\n Object.prototype.toString.call(new Date()),//'[object Date]'\n Object.prototype.toString.call(/^[a-zA-Z]{5,20}$/),//'[object RegExp]'\n Object.prototype.toString.call(new Error())//'[object Error]'\n);\n\n//2. 不能判别自定义类型\nfunction Point(x, y) {\n this.x = x;\n this.y = y;\n}\n\nvar c = new Point(2,3);//c instanceof Point;//true\nObject.prototype.toString.call(c);//\"[object Object]\"\n```\n**简单封装**\n```javascript\nfunction typeProto(obj) {\n return Object.prototype.toString.call(obj).slice(8,-1);\n}\n\ntypeProto(\"guo\");//\"String\"\ntypeProto({});//\"Object\"\n```\n\n### constructor\nconstructor本来是原型对象上的属性,指向构造函数。但是根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的。\n* 不可判别`null`、`undefined`\n* 可判别简单类型数据(`null`、`undefined`除外)\n* 可判别复杂类型数据包括自定义类型数据\n```javascript\nfunction Person(){}\nvar Tom = new Person();\n// undefined和null没有constructor属性\nconsole.log(\n Tom.constructor === Person,\n (2).constructor === Number,//或者 2..constructor\n \"abc\".constructor === String,\n true.constructor === Boolean,\n [].constructor === Array,\n {}.constructor === Object,\n (function aa(){}).constructor === Function,\n (new Date()).constructor === Date,\n /\\d/.constructor === RegExp,\n (new Error()).constructor === Error\n);\n```\n不过使用constructor也不是保险的,因为constructor属性是可以被修改的,会导致检测出的结果不正确,例如:\n```javascript\nfunction Person(){}\nfunction Student(){}\nStudent.prototype = new Person();\nvar John = new Student();\nconsole.log(John.constructor==Student); // false\nconsole.log(John.constructor==Person); // true\n```\n在上面的例子中,Student原型中的constructor被修改为指向到Person,导致检测不出实例对象John真实的构造函数。\n**注意:**\n同时,使用`instaceof`和`construcor`,被判断的array必须是在当前页面声明的!比如,一个页面(父页面)有一个框架,框架中引用了一个页面(子页面),在子页面中声明了一个array,并将其赋值给父页面的一个变量,这时在父页面判断该变量,Array(父页面的Array构造函数) === object(子页面穿过来的字页面的Array对象).constructor;肯定会返回false;原因:\n1. array属于引用型数据,在传递过程中,仅仅是引用地址的传递。\n2. 每个页面的Array原生对象所引用的地址是不一样的,在子页面声明的array,所对应的构造函数,是子页面的Array对象;父页面来进行判断,使用的Array并不等于子页面的Array;切记,不然很难跟踪问题!\n\n## 各种检验方法对应值\n| 类型判断 | typeof | instanceof | constructor | toString.call | $.type(jQuery库的方法) |\n| :-----|:-----:|:-----:|:-----:|:-----:|:-----:|\n| 99 | number | false | true | [object Number] | number |\n| \"abc\" | string | false | true | [object String] | string |\n| true | boolean | false | true | [object Boolean] | boolean |\n| [1,2] | object | true | true | [object Array] | array |\n| `{}` | object | true | true | [object Object] | object |\n| `(function aa(){})` | function | true | true | [object Function] | function |\n| `undefined` | undefined | false | - | [object Undefined] | undefined | \n| `null` | object | false | - | [object Null] | null |\n| `new Date()` | object | true | true | [object Date] | date |\n| `/\\d/` | object | true | true | [object RegExp] | regexp |\n| `new Error()` | object | true | true | [object Error] | error |\n| 优点\t| 使用简单,能直接输出结果 | 能检测出复杂的类型 | 基本能检测出所有的类型 | 检测出所有的类型\t|-|\n| 缺点\t| 检测出的类型太少 | 基本类型检测不出,且不能跨iframe | 不能跨iframe,且constructor易被修改 | IE6下undefined,null均为Object |-|\n $.type 原理:先判断 undefined 和 null\n obj == null ? String( obj ) : Object.prototype.toString.call(obj)\n\n## 一些常用的校验函数\n\n```javascript\n//低版本ie中undefined变量可以被修改,所以使用void 0 获取真实的undefined值,\nvar isUndefined = function(obj) {\n //or: return typeof obj === 'undefined';\n return obj === void 0;\n};\n//typeof null 的结果是\"object\"。\nvar isNull = function(obj) {\n return obj === null;\n};\n// boolean值,number值和string值需要考虑两种情况,值为字面量时使用typeof和Object.prototype.toString能检测; \n// 值为构造函数构建的时候需要使用Object.prototype.toString或者instanceof检测\nvar isBoolean = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Boolean]';\n};\nvar isNumber = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Number]';\n};\nvar isString = function(obj) {\n return Object.prototype.toString.call(obj) == '[object String]';\n};\nvar isNaN = function(obj) {\n return obj !== obj;\n};\n\n//typeof 操作符在引用类型的变量里能对function有效。\nvar isFunction = function(obj) {\n //or: return Object.prototype.toString.call(obj) == '[object Function]';\n return typeof obj === 'function';\n\n};\nvar isDate = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Date]';\n}\nvar isArray = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Array]';\n}\nvar isObject = function(obj) {\n //or: return obj === Object(obj);\n return Object.prototype.toString.call(obj) == '[object Object]';\n}\n```","source":"_posts/类型检验-2016-02-19.md","raw":"title: 类型检验\ndate: 2016-02-19 10:38:35\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\n在JavaScript中,有5种简单数据类型和1种复杂数据类型,简单数据类型有:Undefined,Null,Boolean, Number和String;复杂数据类型是Object,Object中还细分了很多具体的类型,比如:Array,Function,Date,RegExp等等,还有我们自己定义的对象,自定义类型。今天我们就来探讨一下,使用什么方法判断一个出一个变量的类型。\n<!--more-->\n## 类型系统\n{% asset_img type.jpg %}\n\n## 类型转化表\n| Value | Boolean | Number | String |\n|:-----|:-----:|:-----:|:-----:|\n| undefined | false | NaN | \"undefined\" |\n| null | false | 0 | \"null\" |\n| true | true | 1 | \"true\" |\n| false | false | 0 | \"false\" |\n| '' | false | 0 | \"\" |\n| '123' | true | 123 | \"123\" |\n| '1a' | true | NaN | \"1a\" |\n| 0 | false | 0 | \"0\" |\n| 1 | true | 1 | \"1\" |\n| Infinity | true | Infinity | \"Infinity\" |\n| NaN | false | NaN | \"NaN\" |\n| {} | true | NaN | \"[object Object]\" |\n\n## 类型判断\n\n### typeof\n\n* 可以判别简单数据类型(null除外)\n* 不可判别具体的复杂数据类型(Function除外)\n\n```javascript\n//1. 可以判别简单数据类型(`null`除外)\nvar obj = 1;\ntypeof obj; //\"number\"\nobj = \"abc\"\ntypeof obj; //\"string\"\nobj = false\ntypeof obj; //\"boolean\"\nobj = undefined;\ntypeof obj; //\"undefined\"\nobj = null;\ntypeof obj; //\"object\",WTF,其实这是js的一个bug,人艰不拆 T_T\n//2. 不可判别具体的复杂数据类型(`Function`除外)\nobj = function(){};\ntypeof obj; //\"function\"\nobj = [];\ntypeof obj; //\"object\"\nobj = {};\ntypeof obj; //\"object\"\nobj = /w/g;\ntypeof obj //\"object\"\nobj = new Error();\ntypeof obj //\"object\"\n```\n\n### instanceof\n`instanceof`左侧为查询变量,右侧为标识对象的类。\n* 能够判别复杂数据类型,但是不能判别`具体的复杂数据类型`和Object类型。\n* 不能判别简单数据类型,但是能判别通过基本包装行创建的变量。\n* 能够判别自定义类型。\n\n```javascript\nconsole.log(\n //1. 不能判别简单数据类型 (number、string、boolean、null、undefined),\n 99 instanceof Number,//false\n \"2\" instanceof String,//false\n true instanceof Boolean,//false\n undefined instanceof Object,//false\n null instanceof Object,//false\n \n //2.能够判别复杂数据类型,\n [] instanceof Array,//true\n {} instanceof Object,//true\n (function(){}) instanceof Function,//true\n (new Date()) instanceof Date,//true\n /\\d/ instanceof RegExp,//true\n (new Error()) instanceof Error,//true\n //但是不能判别`具体的复杂数据类型`和Object类型\n [] instanceof Object,//true\n /\\d/ instanceof Object//true\n)\n//但是使用基本包装类型创建的对象通过 instanceof 可以判别。\n//如果指定obj = \"abc\"则obj保存的实际上就是abc的值,是一个基本类型。而如果指定obj = new String('abc')那么obj实际上保存的是一个指向字符串对象的指针。\nvar obj = new String('abc');\nobj instanceof String//true\n\"abc\" instanceof String//false \n\n//3. 能够判别自定义类型\nfunction Point(x, y) {\n this.x = x;\n this.y = y;\n}\nvar c = new Point(2,3);\n\nc instanceof Point;//true\n```\n### Object.prototype.toString.call()\n* 能判别所有类型(除去自定义类型)\n* 不能判别自定义类型\n\n```javascript\n//1. 所有类型(除去自定义类型)\nconsole.log(\n Object.prototype.toString.call(1),// '[object Number]'\n Object.prototype.toString.call(\"abc\"),//'[object String]'\n Object.prototype.toString.call(true),//'[object Boolean]'\n Object.prototype.toString.call([]),//'[object Array]'\n Object.prototype.toString.call({}),//'[object Object]'\n Object.prototype.toString.call(function(){}),//'[object Function]'\n Object.prototype.toString.call(undefined),//'[object Undefined]'\n Object.prototype.toString.call(null),//'[object Null]'\n Object.prototype.toString.call(new Date()),//'[object Date]'\n Object.prototype.toString.call(/^[a-zA-Z]{5,20}$/),//'[object RegExp]'\n Object.prototype.toString.call(new Error())//'[object Error]'\n);\n\n//2. 不能判别自定义类型\nfunction Point(x, y) {\n this.x = x;\n this.y = y;\n}\n\nvar c = new Point(2,3);//c instanceof Point;//true\nObject.prototype.toString.call(c);//\"[object Object]\"\n```\n**简单封装**\n```javascript\nfunction typeProto(obj) {\n return Object.prototype.toString.call(obj).slice(8,-1);\n}\n\ntypeProto(\"guo\");//\"String\"\ntypeProto({});//\"Object\"\n```\n\n### constructor\nconstructor本来是原型对象上的属性,指向构造函数。但是根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的。\n* 不可判别`null`、`undefined`\n* 可判别简单类型数据(`null`、`undefined`除外)\n* 可判别复杂类型数据包括自定义类型数据\n```javascript\nfunction Person(){}\nvar Tom = new Person();\n// undefined和null没有constructor属性\nconsole.log(\n Tom.constructor === Person,\n (2).constructor === Number,//或者 2..constructor\n \"abc\".constructor === String,\n true.constructor === Boolean,\n [].constructor === Array,\n {}.constructor === Object,\n (function aa(){}).constructor === Function,\n (new Date()).constructor === Date,\n /\\d/.constructor === RegExp,\n (new Error()).constructor === Error\n);\n```\n不过使用constructor也不是保险的,因为constructor属性是可以被修改的,会导致检测出的结果不正确,例如:\n```javascript\nfunction Person(){}\nfunction Student(){}\nStudent.prototype = new Person();\nvar John = new Student();\nconsole.log(John.constructor==Student); // false\nconsole.log(John.constructor==Person); // true\n```\n在上面的例子中,Student原型中的constructor被修改为指向到Person,导致检测不出实例对象John真实的构造函数。\n**注意:**\n同时,使用`instaceof`和`construcor`,被判断的array必须是在当前页面声明的!比如,一个页面(父页面)有一个框架,框架中引用了一个页面(子页面),在子页面中声明了一个array,并将其赋值给父页面的一个变量,这时在父页面判断该变量,Array(父页面的Array构造函数) === object(子页面穿过来的字页面的Array对象).constructor;肯定会返回false;原因:\n1. array属于引用型数据,在传递过程中,仅仅是引用地址的传递。\n2. 每个页面的Array原生对象所引用的地址是不一样的,在子页面声明的array,所对应的构造函数,是子页面的Array对象;父页面来进行判断,使用的Array并不等于子页面的Array;切记,不然很难跟踪问题!\n\n## 各种检验方法对应值\n| 类型判断 | typeof | instanceof | constructor | toString.call | $.type(jQuery库的方法) |\n| :-----|:-----:|:-----:|:-----:|:-----:|:-----:|\n| 99 | number | false | true | [object Number] | number |\n| \"abc\" | string | false | true | [object String] | string |\n| true | boolean | false | true | [object Boolean] | boolean |\n| [1,2] | object | true | true | [object Array] | array |\n| `{}` | object | true | true | [object Object] | object |\n| `(function aa(){})` | function | true | true | [object Function] | function |\n| `undefined` | undefined | false | - | [object Undefined] | undefined | \n| `null` | object | false | - | [object Null] | null |\n| `new Date()` | object | true | true | [object Date] | date |\n| `/\\d/` | object | true | true | [object RegExp] | regexp |\n| `new Error()` | object | true | true | [object Error] | error |\n| 优点\t| 使用简单,能直接输出结果 | 能检测出复杂的类型 | 基本能检测出所有的类型 | 检测出所有的类型\t|-|\n| 缺点\t| 检测出的类型太少 | 基本类型检测不出,且不能跨iframe | 不能跨iframe,且constructor易被修改 | IE6下undefined,null均为Object |-|\n $.type 原理:先判断 undefined 和 null\n obj == null ? String( obj ) : Object.prototype.toString.call(obj)\n\n## 一些常用的校验函数\n\n```javascript\n//低版本ie中undefined变量可以被修改,所以使用void 0 获取真实的undefined值,\nvar isUndefined = function(obj) {\n //or: return typeof obj === 'undefined';\n return obj === void 0;\n};\n//typeof null 的结果是\"object\"。\nvar isNull = function(obj) {\n return obj === null;\n};\n// boolean值,number值和string值需要考虑两种情况,值为字面量时使用typeof和Object.prototype.toString能检测; \n// 值为构造函数构建的时候需要使用Object.prototype.toString或者instanceof检测\nvar isBoolean = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Boolean]';\n};\nvar isNumber = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Number]';\n};\nvar isString = function(obj) {\n return Object.prototype.toString.call(obj) == '[object String]';\n};\nvar isNaN = function(obj) {\n return obj !== obj;\n};\n\n//typeof 操作符在引用类型的变量里能对function有效。\nvar isFunction = function(obj) {\n //or: return Object.prototype.toString.call(obj) == '[object Function]';\n return typeof obj === 'function';\n\n};\nvar isDate = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Date]';\n}\nvar isArray = function(obj) {\n return Object.prototype.toString.call(obj) == '[object Array]';\n}\nvar isObject = function(obj) {\n //or: return obj === Object(obj);\n return Object.prototype.toString.call(obj) == '[object Object]';\n}\n```","slug":"类型检验","published":1,"updated":"2016-06-11T05:26:42.267Z","layout":"post","photos":[],"link":"","_id":"citf9olx3000dskv77fldnkk3"},{"title":"理解react和redux","date":"2016-07-31T06:04:41.000Z","_content":"作者:Wang Namelos\n链接:https://www.zhihu.com/question/41312576/answer/90782136\n来源:知乎\n著作权归作者所有,转载请联系作者获得授权。\n\n解答这个问题并不困难:唯一的要求是你熟悉React。\n不要光听别人描述名词,理解起来是很困难的。\n从需求出发,看看使用React需要什么:\n1. React有props和state: props意味着父级分发下来的属性,state意味着组件内部可以自行管理的状态,并且整个React没有数据向上回溯的能力,也就是说数据只能单向向下分发,或者自行内部消化。\n理解这个是理解React和Redux的前提。\n2. 一般构建的React组件内部可能是一个完整的应用,它自己工作良好,你可以通过属性作为API控制它。但是更多的时候发现React根本无法让两个组件互相交流,使用对方的数据。\n然后这时候不通过DOM沟通(也就是React体制内)解决的唯一办法就是提升state,将state放到共有的父组件中来管理,再作为props分发回子组件。\n3. 子组件改变父组件state的办法只能是通过onClick触发父组件声明好的回调,也就是父组件提前声明好函数或方法作为契约描述自己的state将如何变化,再将它同样作为属性交给子组件使用。\n这样就出现了一个模式:数据总是单向从顶层向下分发的,但是只有子组件回调在概念上可以回到state顶层影响数据。这样state一定程度上是响应式的。\n4. 为了面临所有可能的扩展问题,最容易想到的办法就是把所有state集中放到所有组件顶层,然后分发给所有组件。\n5. 为了有更好的state管理,就需要一个库来作为更专业的顶层state分发给所有React应用,这就是Redux。让我们回来看看重现上面结构的需求:\na. 需要回调通知state (等同于回调参数) -> action\nb. 需要根据回调处理 (等同于父级方法) -> reducer\nc. 需要state (等同于总状态) -> store\n对Redux来说只有这三个要素:\na. action是纯声明式的数据结构,只提供事件的所有要素,不提供逻辑。\nb. reducer是一个匹配函数,action的发送是全局的:所有的reducer都可以捕捉到并匹配与自己相关与否,相关就拿走action中的要素进行逻辑处理,修改store中的状态,不相关就不对state做处理原样返回。\nc. store负责存储状态并可以被react api回调,发布action.\n当然一般不会直接把两个库拿来用,还有一个binding叫react-redux, 提供一个Provider和connect。很多人其实看懂了redux卡在这里。\na. Provider是一个普通组件,可以作为顶层app的分发点,它只需要store属性就可以了。它会将state分发给所有被connect的组件,不管它在哪里,被嵌套多少层。\nb. connect是真正的重点,它是一个科里化函数,意思是先接受两个参数(数据绑定mapStateToProps和事件绑定mapDispatchToProps),再接受一个参数(将要绑定的组件本身):\nmapStateToProps:构建好Redux系统的时候,它会被自动初始化,但是你的React组件并不知道它的存在,因此你需要分拣出你需要的Redux状态,所以你需要绑定一个函数,它的参数是state,简单返回你关心的几个值。\nmapDispatchToProps:声明好的action作为回调,也可以被注入到组件里,就是通过这个函数,它的参数是dispatch,通过redux的辅助方法bindActionCreator绑定所有action以及参数的dispatch,就可以作为属性在组件里面作为函数简单使用了,不需要手动dispatch。这个mapDispatchToProps是可选的,如果不传这个参数redux会简单把dispatch作为属性注入给组件,可以手动当做store.dispatch使用。这也是为什么要科里化的原因。\n\n做好以上流程Redux和React就可以工作了。简单地说就是:\n1.顶层分发状态,让React组件被动地渲染。\n2.监听事件,事件有权利回到所有状态顶层影响状态。","source":"_posts/理解react和redux-2016-07-31.md","raw":"title: 理解react和redux\ndate: 2016-07-31 14:04:41\ntags: \n- redux\n- react\n---\n作者:Wang Namelos\n链接:https://www.zhihu.com/question/41312576/answer/90782136\n来源:知乎\n著作权归作者所有,转载请联系作者获得授权。\n\n解答这个问题并不困难:唯一的要求是你熟悉React。\n不要光听别人描述名词,理解起来是很困难的。\n从需求出发,看看使用React需要什么:\n1. React有props和state: props意味着父级分发下来的属性,state意味着组件内部可以自行管理的状态,并且整个React没有数据向上回溯的能力,也就是说数据只能单向向下分发,或者自行内部消化。\n理解这个是理解React和Redux的前提。\n2. 一般构建的React组件内部可能是一个完整的应用,它自己工作良好,你可以通过属性作为API控制它。但是更多的时候发现React根本无法让两个组件互相交流,使用对方的数据。\n然后这时候不通过DOM沟通(也就是React体制内)解决的唯一办法就是提升state,将state放到共有的父组件中来管理,再作为props分发回子组件。\n3. 子组件改变父组件state的办法只能是通过onClick触发父组件声明好的回调,也就是父组件提前声明好函数或方法作为契约描述自己的state将如何变化,再将它同样作为属性交给子组件使用。\n这样就出现了一个模式:数据总是单向从顶层向下分发的,但是只有子组件回调在概念上可以回到state顶层影响数据。这样state一定程度上是响应式的。\n4. 为了面临所有可能的扩展问题,最容易想到的办法就是把所有state集中放到所有组件顶层,然后分发给所有组件。\n5. 为了有更好的state管理,就需要一个库来作为更专业的顶层state分发给所有React应用,这就是Redux。让我们回来看看重现上面结构的需求:\na. 需要回调通知state (等同于回调参数) -> action\nb. 需要根据回调处理 (等同于父级方法) -> reducer\nc. 需要state (等同于总状态) -> store\n对Redux来说只有这三个要素:\na. action是纯声明式的数据结构,只提供事件的所有要素,不提供逻辑。\nb. reducer是一个匹配函数,action的发送是全局的:所有的reducer都可以捕捉到并匹配与自己相关与否,相关就拿走action中的要素进行逻辑处理,修改store中的状态,不相关就不对state做处理原样返回。\nc. store负责存储状态并可以被react api回调,发布action.\n当然一般不会直接把两个库拿来用,还有一个binding叫react-redux, 提供一个Provider和connect。很多人其实看懂了redux卡在这里。\na. Provider是一个普通组件,可以作为顶层app的分发点,它只需要store属性就可以了。它会将state分发给所有被connect的组件,不管它在哪里,被嵌套多少层。\nb. connect是真正的重点,它是一个科里化函数,意思是先接受两个参数(数据绑定mapStateToProps和事件绑定mapDispatchToProps),再接受一个参数(将要绑定的组件本身):\nmapStateToProps:构建好Redux系统的时候,它会被自动初始化,但是你的React组件并不知道它的存在,因此你需要分拣出你需要的Redux状态,所以你需要绑定一个函数,它的参数是state,简单返回你关心的几个值。\nmapDispatchToProps:声明好的action作为回调,也可以被注入到组件里,就是通过这个函数,它的参数是dispatch,通过redux的辅助方法bindActionCreator绑定所有action以及参数的dispatch,就可以作为属性在组件里面作为函数简单使用了,不需要手动dispatch。这个mapDispatchToProps是可选的,如果不传这个参数redux会简单把dispatch作为属性注入给组件,可以手动当做store.dispatch使用。这也是为什么要科里化的原因。\n\n做好以上流程Redux和React就可以工作了。简单地说就是:\n1.顶层分发状态,让React组件被动地渲染。\n2.监听事件,事件有权利回到所有状态顶层影响状态。","slug":"理解react和redux","published":1,"updated":"2016-07-31T06:05:18.590Z","comments":1,"layout":"post","photos":[],"link":"","_id":"citf9olxk000gskv7u9swgkrd"},{"title":"如何学习javascript(转帖)","date":"2016-01-27T02:24:34.000Z","comments":1,"_content":"# 首先说明\n首先要说明的是,咱现在不是高手,最多还是一个半桶水,算是入了JS的门。 谈不上经验,都是一些教训。这个时候有人要说,“靠,你丫半桶水,凭啥教我们”。您先别急着骂,先听我说。你叫一个大学生去教小学数学,不见得比一个初中生教得好。因为大学生早已经过了那个阶段,都忘记自己怎么走过来的了。而对于初中生,刚好走过那个阶段,对自己怎么走过来的还记忆犹新,或者还有一些自己的总结。比如,很多高手觉得那本犀牛书入门很好,他们觉得太简单了,但以我的经验来看,它不是入门的最好选择。\n<!--more-->\n# 先说说学js的条件\n论条件,咱是文科生,大学专业工商管理,和计算机毛关系都没;有人说英语,读了四年大学,很遗憾,咱还四级没混过;就咱这条件都学得乐呵呵的,您还等啥。 当然学习JS也是有门槛的,就是你的html和css至少还比较熟练,您不能连<body>这东东是干啥的都不知道就开始上JS了,学乘除前,学好加减法总是有益无害的。\n# 再说几点忠告\n\n1. 不要着急看一些复杂网页效果的代码,这样除了打击你自信心,什么也学不到。没达到一定的武功水平,割了小JJ也学不会葵花宝典的。\n2. 别急着加技术交流QQ群,加牛人QQ。如果你找张三丰交流武功,你上去第一句问“丰哥,where is 丹田?”,你会被他一掌劈死的。\n3. 看网上什么多少天精通JS,啥啥啥从入门到精通,这种教程直接跳过吧,太多的事实证明,以一种浮躁的心态去做任何事都会以失败而告终。\n4. 千万别去弄啥电脑培训,花了钱和时间不说,关键是学不到东西。本来你买两本好书自学3个月能学会的,他们硬是能折腾你两年。\n\n# 推荐几本好书\n“超毛,你丫吹了半天牛B,还是没说怎么学啊” 呵呵,我也没啥特别的办法,只是推荐几本好书。推荐的书,得按先后顺序看。别第一本没看完,就急着上第二本,并不是每次“穿越”都能成功的\n## 第一阶段:《JavaScript DOM编程艺术》\n看这本书之前,请先确认您对Javascript有个基本的了解,应该知道if else之类的语法,如果不懂,先去看看我第二阶段推荐的《Javascript高级程序设计》的前三章,记住看三章就别往下看了,回到《JavaScript DOM编程艺术》这本书上来。 学习Javascript用《JavaScript DOM编程艺术》来入门最好不过了,老老实实看两遍,看完了你就会对JS有一个大概的了解,整本书都围绕着一个网页效果例子展开,你跟着老老实实敲一篇,敲完之后,你会发现这个效果不是常在网页中看到么,发现自己也能做出来网上的效果了,嘿嘿,小有成就感吧。\n## 第二阶段:《JavaScript高级程序设计》\n有的书是用来成为经典的,比如犀牛书;还有些书是用来超越经典的,显然这本书就是这种。书中章章经典,由浅入深,其中第6章,关于JS面向对象的解说,没有教程出其右。 如果有一场满分100分的JS考试,看了《JavaScript DOM编程艺术》能让你拿到20分,那么看完这本书,你就能拿到60分以上了。学完后,你会成就感倍增的,相信我(至少看两遍,推荐三篇,跟着书上的代码一行行的敲)。 这本书强烈推荐购买,写的太TMD牛逼了,给你带来的价值超过百倍千倍。 这本书最新的是第三版,貌似就是前些日子出来的,我看的是第二版,第三版相对第二版变动不大,添加了几章内容,价格目前相差10元左右。 接下来,恭喜你可以下山了,这个时候可以自己做一些事情了。 你可以去Ferris这个教程看看他写的这些效果,看看源代码,怎么样,是不是觉得有一部分很简单了,尝试着跟着他写一写这些效果吧。 学技术闭门造车是行不通的,适当的加一两个QQ群交流(注重质量),常去论坛逛逛,你会经常有些小收获的。 再有就是看看前辈这些牛人前辈们分享的文章,它会让你的学习事半功倍的,这里是热心人收集的国内一些牛人的博客、个人网站,点这里。\n## 第三阶段:《JavaScript语言精粹》和《高性能JavaScript》\n接下来两本书《JavaScript语言精粹》和《高性能JavaScript》算是JS高级教程的补充,里面有一些内容和JS高级教程重复了,两本书可以同时看,都不厚,可以对前面所学的有一个很好的加强和巩固。\n## 第四阶段:《JavaScript DOM高级程序设计》和《JavaScript设计模式》\n在吃透了前面所说的书之后,接下来两本书的顺序已经无关紧要了,《JavaScript DOM高级程序设计》(注意和《JavaScript 高级程序设计》相区别)和《JavaScript设计模式》,这两本都是重量级的书,能让你的JS技术上一个新的台阶;这两本书前者主修炼外功,后者主修炼内功,有点想乾坤大挪移和九阳神功的关系。《JavaScript DOM高级程序设计》 首先教你搭建一个类似JQuery的额工具函数库,然后通过讲解几个实际中经常遇到的几个应用例子,会让初学者受益匪浅。《JavaScript设计模式》主要讲Javascript的设计模式,说实话,翻译的质量很一般,有些生硬,但已经基本不影响你的学习,看代码完全可以理解出自己的意思。\n# 最后想说的\n不安逸,不浮躁。任何学习都不是一蹴而就的,牛B就是一个学习积累的过程,别指望两三个月,你的水平就多么厉害。倚天屠龙记里面的武功最牛B的是张三丰,而不是张无忌。任何工作都需要多种技能,别忽略了html, css等其他知识的学习。\n\n>转自 (博客园)[http://kb.cnblogs.com/page/191787/]","source":"_posts/如何学习javascript-转帖-2016-01-27.md","raw":"title: 如何学习javascript(转帖)\ndate: 2016-01-27 10:24:34\ntags:\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# 首先说明\n首先要说明的是,咱现在不是高手,最多还是一个半桶水,算是入了JS的门。 谈不上经验,都是一些教训。这个时候有人要说,“靠,你丫半桶水,凭啥教我们”。您先别急着骂,先听我说。你叫一个大学生去教小学数学,不见得比一个初中生教得好。因为大学生早已经过了那个阶段,都忘记自己怎么走过来的了。而对于初中生,刚好走过那个阶段,对自己怎么走过来的还记忆犹新,或者还有一些自己的总结。比如,很多高手觉得那本犀牛书入门很好,他们觉得太简单了,但以我的经验来看,它不是入门的最好选择。\n<!--more-->\n# 先说说学js的条件\n论条件,咱是文科生,大学专业工商管理,和计算机毛关系都没;有人说英语,读了四年大学,很遗憾,咱还四级没混过;就咱这条件都学得乐呵呵的,您还等啥。 当然学习JS也是有门槛的,就是你的html和css至少还比较熟练,您不能连<body>这东东是干啥的都不知道就开始上JS了,学乘除前,学好加减法总是有益无害的。\n# 再说几点忠告\n\n1. 不要着急看一些复杂网页效果的代码,这样除了打击你自信心,什么也学不到。没达到一定的武功水平,割了小JJ也学不会葵花宝典的。\n2. 别急着加技术交流QQ群,加牛人QQ。如果你找张三丰交流武功,你上去第一句问“丰哥,where is 丹田?”,你会被他一掌劈死的。\n3. 看网上什么多少天精通JS,啥啥啥从入门到精通,这种教程直接跳过吧,太多的事实证明,以一种浮躁的心态去做任何事都会以失败而告终。\n4. 千万别去弄啥电脑培训,花了钱和时间不说,关键是学不到东西。本来你买两本好书自学3个月能学会的,他们硬是能折腾你两年。\n\n# 推荐几本好书\n“超毛,你丫吹了半天牛B,还是没说怎么学啊” 呵呵,我也没啥特别的办法,只是推荐几本好书。推荐的书,得按先后顺序看。别第一本没看完,就急着上第二本,并不是每次“穿越”都能成功的\n## 第一阶段:《JavaScript DOM编程艺术》\n看这本书之前,请先确认您对Javascript有个基本的了解,应该知道if else之类的语法,如果不懂,先去看看我第二阶段推荐的《Javascript高级程序设计》的前三章,记住看三章就别往下看了,回到《JavaScript DOM编程艺术》这本书上来。 学习Javascript用《JavaScript DOM编程艺术》来入门最好不过了,老老实实看两遍,看完了你就会对JS有一个大概的了解,整本书都围绕着一个网页效果例子展开,你跟着老老实实敲一篇,敲完之后,你会发现这个效果不是常在网页中看到么,发现自己也能做出来网上的效果了,嘿嘿,小有成就感吧。\n## 第二阶段:《JavaScript高级程序设计》\n有的书是用来成为经典的,比如犀牛书;还有些书是用来超越经典的,显然这本书就是这种。书中章章经典,由浅入深,其中第6章,关于JS面向对象的解说,没有教程出其右。 如果有一场满分100分的JS考试,看了《JavaScript DOM编程艺术》能让你拿到20分,那么看完这本书,你就能拿到60分以上了。学完后,你会成就感倍增的,相信我(至少看两遍,推荐三篇,跟着书上的代码一行行的敲)。 这本书强烈推荐购买,写的太TMD牛逼了,给你带来的价值超过百倍千倍。 这本书最新的是第三版,貌似就是前些日子出来的,我看的是第二版,第三版相对第二版变动不大,添加了几章内容,价格目前相差10元左右。 接下来,恭喜你可以下山了,这个时候可以自己做一些事情了。 你可以去Ferris这个教程看看他写的这些效果,看看源代码,怎么样,是不是觉得有一部分很简单了,尝试着跟着他写一写这些效果吧。 学技术闭门造车是行不通的,适当的加一两个QQ群交流(注重质量),常去论坛逛逛,你会经常有些小收获的。 再有就是看看前辈这些牛人前辈们分享的文章,它会让你的学习事半功倍的,这里是热心人收集的国内一些牛人的博客、个人网站,点这里。\n## 第三阶段:《JavaScript语言精粹》和《高性能JavaScript》\n接下来两本书《JavaScript语言精粹》和《高性能JavaScript》算是JS高级教程的补充,里面有一些内容和JS高级教程重复了,两本书可以同时看,都不厚,可以对前面所学的有一个很好的加强和巩固。\n## 第四阶段:《JavaScript DOM高级程序设计》和《JavaScript设计模式》\n在吃透了前面所说的书之后,接下来两本书的顺序已经无关紧要了,《JavaScript DOM高级程序设计》(注意和《JavaScript 高级程序设计》相区别)和《JavaScript设计模式》,这两本都是重量级的书,能让你的JS技术上一个新的台阶;这两本书前者主修炼外功,后者主修炼内功,有点想乾坤大挪移和九阳神功的关系。《JavaScript DOM高级程序设计》 首先教你搭建一个类似JQuery的额工具函数库,然后通过讲解几个实际中经常遇到的几个应用例子,会让初学者受益匪浅。《JavaScript设计模式》主要讲Javascript的设计模式,说实话,翻译的质量很一般,有些生硬,但已经基本不影响你的学习,看代码完全可以理解出自己的意思。\n# 最后想说的\n不安逸,不浮躁。任何学习都不是一蹴而就的,牛B就是一个学习积累的过程,别指望两三个月,你的水平就多么厉害。倚天屠龙记里面的武功最牛B的是张三丰,而不是张无忌。任何工作都需要多种技能,别忽略了html, css等其他知识的学习。\n\n>转自 (博客园)[http://kb.cnblogs.com/page/191787/]","slug":"如何学习javascript-转帖","published":1,"updated":"2016-01-27T02:42:52.902Z","layout":"post","photos":[],"link":"","_id":"citf9olxz000lskv79hdug5tj"},{"title":"好好学学undefined!","date":"2016-01-29T01:48:56.000Z","comments":1,"_content":"# undefined类型\nundefined类型只有一个值,即特殊的undefined,我们称之为`字面值undefined`,undefined意为`未定义`。\n<!--more-->\n`字面值undefined`是全局Global对象(window)的一个特殊属性,其值是未定义的。但 typeof window.undefined 返回\"undefined\" 。\n\n我们可以通过下面的例子来验证undefined是否为全局Global对象(window)的属性:\n\n alert('undefined' in window);//输出:true \n var anObj = {}; \n alert('undefined' in anObj); //输出:false \n 从中可以看出,undefined是window对象的一个属性,但却不是anObj对象的一个属性。\n## `字面值undefined`的产生\n`字面值undefined`产生的原因有5种:\n\n * 访问对象不存在的属性或方法\n * 声明了变量但从未赋值\n * 调用函数时,应该提供的参数没有提供,该参数等于undefined。\n * 方法没有返回值,默认返回undefined\n * 访问越界的数组。\n * void(expression) 形式的表达式。\n \n {% codeblock lang:JavaScript %}\n var v1,obj = {}; \n \n console.log(v1); //`字面值undefined` \n console.log(obj.get); //`字面值undefined`\n \n typeof v1; // \"undefined\" \n typeof v2; // 对未声明的变量使用typeof 也会输出 \"undefined\"。 \n typeof obj.get; // \"undefined\"\n \n var message1 = undefined; //显示的设置为undefined\n typeof message1 //\"undefined\"\n \n function test(){}; \n console.log(test()); //`字面值undefined`\n \n var arr = []; \n console.log(arr[8]) //`字面值undefined`\n {% endcodeblock %}\n \n<span style=\"color: red;\">当我们在程序中使用`字面值undefined`时,实际上使用的是window对象的undefined属性,同样,当我们定义一个变量但未赋予其初始值,例如:`var aValue;`这时,JavaScript在预编译时会将其初始值设置为对window.undefined属性的引用,于是,当我们将一个变量或值与undefined比较时,实际上是与window对象的undefined属性比较。这个比较过程中,JavaScript会搜索window对象名叫\"undefined\"的属性,然后再比较两个操作数的引用指针是否相同。</span>\n\n---\n您可以通过将变量与`字面值undefined`进行比较确定变量是否存在,您也可以通过将变量的类型与字符串“undefined”进行比较确定其类型是否为 undefined类型。\n以下示例演示了如何确定已声明的变量的 x:\n \n var x;\n \n // This method works.\n if (x == undefined) { //这种方式只能对已经声明的变量使用,对未声明的变量使用会报错。\n document.write(\"comparing x to undefined <br/>\");\n }\n \n // This method doesn't work - you must check for the string \"undefined\".\n if (typeof(x) == undefined) {//未执行,因为typeof 方法返回的是字符串。\n document.write(\"comparing the type of x to undefined <br/>\");\n }\n // This method does work. \n if (typeof(x) == \"undefined\") {\n document.write(\"comparing the type of x to the string 'undefined'\");\n }\n \n // Output: \n // comparing x to undefined \n // comparing the type of x to the string 'undefined'\n## 提高访问`字面值undefined`的性能:\n由于window对象的属性值是非常多的,在每一次与`字面值undefined`的比较中,搜索window对象的undefined属性都会花费时间。在需要频繁与undefined进行比较的函数中,这可能会是一个性能问题点。因此,在这种情况下,我们可以自行定义一个局部的undefined变量,来加快对undefined的比较速度。例如:\n\n function anyFunc() {\n var undefined; //自定义局部undefined变量\n if (x == undefined){} //作用域上的引用比较\n while (y != undefined){} //作用域上的引用比较\n };\n其中,定义undefined局部变量时,其初始值会是对window.undefined属性值的引用。新定义的局部undefined变量存在与该函数的作用域上。\n在随后的比较操作中,JavaScript代码的书写方式没有任何的改变,但比较速度却很快。因为作用域上的变量数量会远远少于window对象的属性,搜索变量的速度会极大提高。\n这就是许多前端JS框架为什么常常要自己定义一个局部undefined变量的原因!!!\n比如jQuery 源码:\n\n (function( window, undefined ) {\n /*\n * \n * code\n * \n * */\n })( window );\n ","source":"_posts/好好学学undefined!-2016-01-29.md","raw":"title: 好好学学undefined!\ndate: 2016-01-29 09:48:56\ntags:\n- jQuery\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# undefined类型\nundefined类型只有一个值,即特殊的undefined,我们称之为`字面值undefined`,undefined意为`未定义`。\n<!--more-->\n`字面值undefined`是全局Global对象(window)的一个特殊属性,其值是未定义的。但 typeof window.undefined 返回\"undefined\" 。\n\n我们可以通过下面的例子来验证undefined是否为全局Global对象(window)的属性:\n\n alert('undefined' in window);//输出:true \n var anObj = {}; \n alert('undefined' in anObj); //输出:false \n 从中可以看出,undefined是window对象的一个属性,但却不是anObj对象的一个属性。\n## `字面值undefined`的产生\n`字面值undefined`产生的原因有5种:\n\n * 访问对象不存在的属性或方法\n * 声明了变量但从未赋值\n * 调用函数时,应该提供的参数没有提供,该参数等于undefined。\n * 方法没有返回值,默认返回undefined\n * 访问越界的数组。\n * void(expression) 形式的表达式。\n \n {% codeblock lang:JavaScript %}\n var v1,obj = {}; \n \n console.log(v1); //`字面值undefined` \n console.log(obj.get); //`字面值undefined`\n \n typeof v1; // \"undefined\" \n typeof v2; // 对未声明的变量使用typeof 也会输出 \"undefined\"。 \n typeof obj.get; // \"undefined\"\n \n var message1 = undefined; //显示的设置为undefined\n typeof message1 //\"undefined\"\n \n function test(){}; \n console.log(test()); //`字面值undefined`\n \n var arr = []; \n console.log(arr[8]) //`字面值undefined`\n {% endcodeblock %}\n \n<span style=\"color: red;\">当我们在程序中使用`字面值undefined`时,实际上使用的是window对象的undefined属性,同样,当我们定义一个变量但未赋予其初始值,例如:`var aValue;`这时,JavaScript在预编译时会将其初始值设置为对window.undefined属性的引用,于是,当我们将一个变量或值与undefined比较时,实际上是与window对象的undefined属性比较。这个比较过程中,JavaScript会搜索window对象名叫\"undefined\"的属性,然后再比较两个操作数的引用指针是否相同。</span>\n\n---\n您可以通过将变量与`字面值undefined`进行比较确定变量是否存在,您也可以通过将变量的类型与字符串“undefined”进行比较确定其类型是否为 undefined类型。\n以下示例演示了如何确定已声明的变量的 x:\n \n var x;\n \n // This method works.\n if (x == undefined) { //这种方式只能对已经声明的变量使用,对未声明的变量使用会报错。\n document.write(\"comparing x to undefined <br/>\");\n }\n \n // This method doesn't work - you must check for the string \"undefined\".\n if (typeof(x) == undefined) {//未执行,因为typeof 方法返回的是字符串。\n document.write(\"comparing the type of x to undefined <br/>\");\n }\n // This method does work. \n if (typeof(x) == \"undefined\") {\n document.write(\"comparing the type of x to the string 'undefined'\");\n }\n \n // Output: \n // comparing x to undefined \n // comparing the type of x to the string 'undefined'\n## 提高访问`字面值undefined`的性能:\n由于window对象的属性值是非常多的,在每一次与`字面值undefined`的比较中,搜索window对象的undefined属性都会花费时间。在需要频繁与undefined进行比较的函数中,这可能会是一个性能问题点。因此,在这种情况下,我们可以自行定义一个局部的undefined变量,来加快对undefined的比较速度。例如:\n\n function anyFunc() {\n var undefined; //自定义局部undefined变量\n if (x == undefined){} //作用域上的引用比较\n while (y != undefined){} //作用域上的引用比较\n };\n其中,定义undefined局部变量时,其初始值会是对window.undefined属性值的引用。新定义的局部undefined变量存在与该函数的作用域上。\n在随后的比较操作中,JavaScript代码的书写方式没有任何的改变,但比较速度却很快。因为作用域上的变量数量会远远少于window对象的属性,搜索变量的速度会极大提高。\n这就是许多前端JS框架为什么常常要自己定义一个局部undefined变量的原因!!!\n比如jQuery 源码:\n\n (function( window, undefined ) {\n /*\n * \n * code\n * \n * */\n })( window );\n ","slug":"好好学学undefined!","published":1,"updated":"2016-02-02T05:56:57.673Z","layout":"post","photos":[],"link":"","_id":"citf9oly7000pskv7h5cdd2w3"},{"title":"好好学学number!","date":"2016-01-29T01:55:43.000Z","comments":1,"_content":"# Number类型\n\nJavaScript内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相等的,而且1加上1.0得到的还是一个整数,不会像有些语言那样变成小数。\n<!--more-->\n```javascript\n1 === 1.0 // true\n1 + 1.0 // 2\n```\n也就是说,在JavaScript语言的底层,根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时JavaScript会自动把64位浮点数,转成32位整数,然后再进行运算。\n\n由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。\n```javascript \n0.1 + 0.2 === 0.3 // false\n\n0.3 / 0.1 // 2.9999999999999996\n\n(0.3 - 0.2) === (0.2 - 0.1) // false\n```\n{% asset_img number2.png %}\n\n解释:根据国际标准IEEE-754,64位浮点数格式的64个二进制位中,第0位到第51位储存有效数字部分(共52位),第52到第62位储存指数部分,第63位是符号位,0表示正数,1表示负数。\n\n```javascript\nMath.pow(2, 53) // 9007199254740992\n\nMath.pow(2, 53) + 1 // 9007199254740992\n\nMath.pow(2, 53) + 2 // 9007199254740994\n\nMath.pow(2, 53) + 3 // 9007199254740996\n\nMath.pow(2, 53) + 4 // 9007199254740996\n```\n \n从上面示例可以看到,大于2的53次方以后,整数运算的结果开始出现错误。所以,大于等于2的53次方的数值,都无法保持精度。因此,JavaScript提供的有效数字的精度为53个二进制位(IEEE 754规定有效数字第一位默认总是为1,不保存在64位浮点数之中,这一位再加上后面的52位,就是总共53位),也就是说,绝对值小于2的53次方的整数,即-(2<sup>53</sup>-1)到2<sup>53</sup>-1,都可以精确表示。\n \n那么超过2<sup>53</sup>部分的数字怎么处理呢?\n\n```javascript\nMath.pow(2, 53) // 9007199254740992\n\n9007199254740992111 // 9007199254740992000 \n```\n上面示例表明,大于2的53次方以后,多出来的有效数字(最后三位的`111`)都会无法保存,变成0。\n\n另一方面,64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047(2的11次方减1)。也就是说,64位浮点数的指数部分的值最大为2047,分出一半表示负数,则JavaScript能够表示的数值范围为2<sup>1024</sup>到2<sup>-1023</sup>(开区间),超出这个范围的数无法表示。\n\n如果指数部分等于或超过最大正值1024,JavaScript会返回`Infinity`(关于Infinity的介绍参见下文),这称为“正向溢出”;如果等于或超过最小负值-1023(即非常接近0),JavaScript会直接把这个数转为0,这称为“负向溢出”。事实上,JavaScript对指数部分的两个极端值(`11111111111`(二进制)和`00000000000`(二进制))做了定义,`11111111111`(二进制)表示`NaN`和`Infinity`,`00000000000`(二进制)表示0。\n \n```javascript\n var x = 0.5;\n \n for(var i = 0; i < 25; i++) {\n x = x * x;\n }\n console.log(x);// 0\n```\n上面代码对0.5连续做25次平方,由于最后结果太接近0,超出了可表示的范围,JavaScript就直接将其转为0。\n\n>* 精确地描述,Number类型拥有 18437736874454810627(即,2<sup>64</sup>-2<sup>53</sup>+3)个值,表示为 IEEE-754 格式 64 位双精度数值(IEEE 二进制浮点数算术中描述了它)。\n>* <span style=\"color: red;\">除了 IEEE 标准中的 9007199254740990(即,2<sup>53</sup>-2)个明显的“非数字”值;在 ECMAScript 中,它们被表示为一个单独的特殊值:NaN。</span>(请注意,NaN 值由程序表达式 NaN 产生,并假设执行程序不能调整定义的全局变量 NaN,在javascript中NaN是global全局对象的一个属性`window.NaN`) 在某些实现中,外部代码也许有能力探测出众多非数字值之间的不同,但此类行为依赖于具体实现;对于 ECMAScript 代码而言,NaN 值相互之间无法区别。\n>* 还有另外两个特殊值,称为正无穷和负无穷。为简洁起见,在说明目的时,用符号 +∞ 和 -∞ 分别代表它们。(请注意,两个无限数值由程序表达式 +Infinity(简作 Infinity) 和 -Infinity 产生,并假设执行程序不能调整定义的全局变量 Infinity,在javascript中global全局对象的一个Infinity属性`window.Infinity`)。\n>* 另外 18437736874454810624(即,2<sup>64</sup>-2<sup>53</sup>)个值被称为有限数值,其中的一半是正数,另一半是负数,对于每个正数而言,都有一个与之对应的、相同规模的负数。\n>* 请注意,还有一个正零和一个负零。\n>* [查看number类型数值的二进制](http://alvarto.github.io/VisualNumeric64/)\n\n数轴:\n\n{% asset_img number.png [200] [400] %}\n\n \n> 对于这个如果有兴趣细扣就[点这里](http://www.cnblogs.com/kingwolfofsky/archive/2011/07/21/2112299.html),[还有这里](https://www.w3.org/html/ig/zh/wiki/ES5/types#Number_.E7.B1.BB.E5.9E.8B)\n\n## 整数\n\n整数可以被表示成十进制(基数为10)、十六进制(基数为16)以及八进制(基数为8)。\n\n* 十进制:十进制整数字组成的数字序列,不带前导0(零)。\n* 八进制:八进制整数只能包括数字0-7,通过在八进制整数前面加前导`0`(零)或者或`0o`的数值来表示八进制整数。八进制整数只包含 0 到 7 的数字。<span style=\"color:red;\">具有前导`0`并包含数字“8”和/或“9”的数字将被解释为十进制数字</span>,具有前导`0o`并包含数字“8”和/或“9”的数字将会报错。\n* 十六进制:通过在整数前面加前导“0x”(零和 x|X)来表示十六进制(“hex”)整数。字母 A 到 F 以单个数字的形式表示以 10 为基数的 10 到 15。字母 A 到 F 用于以单个数字的形式表示以 10 为基数的 10 到 15。即,0xF 相当于 15,0x10 相当于 16。\n* 二进制:有前缀`0b`或`0B`的数值。\n\n在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值,还有严格模式下禁止使用八进制。\n \n```javascript\n //默认情况下,JavaScript内部会自动将八进制、十六进制、二进制转为十进制。 \n 0xff // 255\n -0xF1A7// -61863\n 0o377 // 255\n 015 // 13\n 019 // 19\n 0b11 // 3\n \n //如果八进制、十六进制、二进制的数值里面,出现不属于该进制的数字,就会报错。\n 0xzz // 报错\n 0o88 // 报错\n 0b22 // 报错\n```\n\n<span style=\"color:#555;\">IEBUG:从 Internet Explorer 9 标准模式、Internet Explorer 10 标准模式、Internet Explorer 11 标准模式和 Windows 应用商店应用 开始,parseInt 函数不将前缀为“0”的字符串视为八进制。但在不使用 parseInt 函数时,前缀为“0”的字符串仍可被解释为八进制。</span>\n\n## 浮点值\n\n* 一个十进制整数,它可以带符号(即前面的“+”或“ - ”号),\n* 一个小数点(“.”),\n* 一个小数部分(由一串十进制数表示),\n* 一个指数部分\n指数部分是以“e”或“E”开头后面跟着一个整数,可以有正负号(即前面写“+”或“-”)。一个浮点数字面值必须至少有一位数字,后接小数点或者“e”(大写“E”也可)组成。一些浮点数字面值的例子,如3.1415,-3.1E13,.1e12以及2E-12。\n简言之,其语法是:\n\n [digits][.digits][(E|e)[(+|-)]digits]\n //实例:\n 3.14\n 2345.789\n .3333333333333333333 // 0 可以省略\n\n以下两种情况,JavaScript会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。\n\n* 小数点前的数字多于21位\n```javascript\n console.log(1234567890123456789012) // 1.2345678901234568e+21\n```\n* 小数点后的零多于5个\n```javascript\n console.log(0.0000003) //3e-7\n```\n\n## NaN\n\nNaN(not a number)用于处理计算中出现的错误情况,比如 0.0 除以 0.0 或者求负数的平方根。由上面的表中可以看出,对于单精度浮点数,NaN 表示为指数为 emax + 1 = 128(指数域全为 1),且尾数域不等于零的浮点数。IEEE 标准没有要求具体的尾数域,所以 <span style=\"red;\">NaN 实际上不是一个,而是一族</span>,它是全局对象global对象的一个属性(`window.NaN`)。\n需要注意的是,NaN不是一种独立的数据类型,而是一种特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。\n\n```javascript\n console.log(typeof NaN) // 'number\n```\n### 特性:\n* NaN不等于任何值,包括它本身。`NaN === NaN // false`\n* 由于数组的indexOf方法,内部使用的是严格相等运算符,所以该方法对NaN不成立。`NaN].indexOf(NaN) // -1`\n* NaN在布尔运算时被当作false。`Boolean(NaN) // false`\n* NaN与任何数(包括它自己)的运算,得到的都是NaN。\n\n```javascript\n NaN + 32 // NaN\n NaN - 32 // NaN\n NaN * 32 // NaN\n NaN / 32 // NaN\n```\n \n### 判断NaN的方法 isNaN\n\n```javascript\n isNaN(NaN) // true\n isNaN(123) // false\n```\n \n但是,`isNaN`只对数值有效,<span style=\"color:red;\">如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成`NaN`,所以最后返回`true`</span>,这一点要特别引起注意。也就是说,`isNaN`为`true`的值,有可能不是`NaN`,而是一个字符串。出于同样的原因,对于对象和数组,`isNaN`也返回`true`。\n\n```javascript\n isNaN('Hello') // true\n // 等同于\n isNaN(Number('Hello')) // true\n\n isNaN({}) // true\n // 等同于\n isNaN(Number({})) // true\n\n isNaN(['xzy']) // true\n // 等同于\n isNaN(Number(['xzy'])) // true\n```\n\n但是,对于空数组和只有一个数值成员的数组,`isNaN`返回`false`。\n\n```javascript\n isNaN([]) // false\n isNaN([123]) // false\n isNaN(['123']) // false\n```\n \n上面的代码之所以返回`false`,原因是这些数组能被`Number`函数转成数值,请参见《数据类型转换》一节。\n\n因此,使用`isNaN`之前,最好判断一下数据类型。\n\n```javascript\n function myIsNaN(value) {\n return typeof value === 'number' && isNaN(value);\n }\n 判断NaN更可靠的方法是,利用`NaN`是JavaScript之中唯一不等于自身的值这个特点,进行判断。\n\n function myIsNaN(value) {\n return value !== value;\n }\n```\n \n## +0 和 -0\n\n先看一道题: 假如 A === B 并且 1/A < 1/B; 请问 A 等于 几?\n\n### 产生原因\n数字需要被编码才能进行数字化存储.举个例子,假如我们要将一个整数编码为4位的二进制数,使用原码(sign-and-magnitude)方法,则最高位是符号位(0代表正,1代表负),剩下的三位表示大小(具体的值).因此,−2和+2会编码成为下面这样:\n\n 1010 // +2\n 0010 // -2\n 这就意味着将会有两个零:\n 1000 // -0\n 0000 // +0\n在JavaScript中,所有的数字都是浮点数,都是根据IEEE-754标准中的浮点数算法以双精度格式被编码。这个标准中正负号的处理方式类似于原码(sign-and-magnitude)方法中整数的编码方式,所以也有正负零。\n\n### 参与运算\n```javascript\n+0 === -0 //true 虽然有正0和负0但是他们两个是相等\n-0 < +0 //false \n+0 < -0 //false\n\n-0 + -0 // -0\n-0 + +0 // +0\n+0 X -5 // -0\n-0 X -5 // +0\n1 / +0 // Infinity\n1 / -0 // -Infinity\n+0 / -0 // NaN\n```\n \n### 参与Math对象的方法\n\n* Math.pow(x,y)方法可返回从 之间的角度。\n \n```javascript\nMath.pow(+0, -1) // Infinity\nMath.pow(-0, -1) // -Infinity\n```\n \n* Math.atan2(x,y)方法可返回从 x<sup>y</sup> 的值。\n \n```javascript\nx = 0 , y <= -0 // π(3.141592653589793)\nMath.atan2(+0, -0) // π(3.141592653589793)\n\nx = 0 , y >= 0 // 0\nMath.atan2(+0, +0) // 0\n\nx = -0 , y >= +0 // -0\nMath.atan2(-0, +0) // -0\n\nx = -0 , y <= -0 // -π(-3.141592653589793)\nMath.atan2(-0, -0) // -π(-3.141592653589793)\n```\n \n* Math.round()是另外一个参数不为零却产生-0结果的操作:\n```javascript\nMath.round(-0.1) // -0\n```\n\n### 区分这两个零\n\n方法1: 判断一个零是正还是负的标准解法是用它除1,然后看计算的结果是-Infinity还是+Infinity\n \n```javascript\nfunction isNegativeZero(x) {\n return x === 0 && (1/x < 0);\n}\n```\n\n方法2: 除了上面讲的几种解法.还有一个解法来自Allen Wirfs-Brock(译者注:TC39编辑,ES标准就是他写出来的。):\n \n```javascript\nfunction isNegativeZero(x) {\n if (x !== 0) return false;\n var obj = {};\n Object.defineProperty(obj, 'z', { value: -0, configurable: false });\n try {\n // 如果x的值和z属性的当前值不相等的话,就会抛出异常.\n Object.defineProperty(obj, 'z', { value: x });\n } catch (e) {\n return false\n };\n return true;\n}\n```\n \n解释: 通常情况下,你不能重新定义一个不可配置的对象属性,否则会抛出异常:`TypeError: Cannot redefine property: z`可是,如果你重新定义属性时指定的属性特性的值与该特性当前的值相等,则JavaScript会忽略掉这个重定义,不会抛出异常。其中在判断两个值是否相等时使用的运算不是===,是一个称之为SameValue的内部算法,该算法可以区分开 -0 和 +0 。可以从Wirfs-Brock的原文中了解更多细。(冻结一个对象会让该对象的所有属性变的不可配置)。\n\n SameValue 算法\n 内部严格比较操作 SameValue(x,y),x 和 y 为 ECMAScript 语言中的值,需要产出 true 或 false。Note.png Note.png V8.png\n\n 比较过程如下:\n\n 如果 Type(x) 与 Type(y) 的结果不一致,返回 false,否则\n 如果 Type(x) 结果为 Undefined,返回 true\n 如果 Type(x) 结果为 Null,返回 true\n 如果 Type(x) 结果为 Number,则\n 如果 x 为 NaN,且 y 也为 NaN,返回 true\n 如果 x 为 +0,y 为 -0,返回 false\n 如果 x 为 -0,y 为 +0,返回 false\n 如果 x 与 y 为同一个数字,返回 true\n 返回 false\n 如果 Type(x) 结果为 String,如果 x 与 y 为完全相同的字符序列(相同的长度和相同的字符对应相同的位置),返回 true,否则,返回 false\n 如果 Type(x) 结果为 Boolean,如果 x 与 y 都为 true 或 false,则返回 true,否则,返回 false\n 如果 x 和 y 引用到同一个 Object 对象,返回 true,否则,返回 false\n\n\n> 在执行一些特殊方法的时候,比如alert或innerHTML等方法,它将由脚本解析器自动调用toString()方法。\n\n看完上面这些,那么这节开头的题目的结果自然而然就知道喽。\n\n## Infinity\nInfinity 表示“无穷”,挂在global对象下的Infinity属性上(window.Infinity)。除了0除以0得到NaN,其他任意数除以0,得到Infinity。获得方式\n\n```javascript\nNumber.NEGATIVE_INFINITY // +infinity\nNumber.POSITIVE_INFINITY // -infinity\n\nwindow.Infinity === Number.POSITIVE_INFINITY // ture\n```\n\n```javascript\n1 / -0 // -Infinity\n1 / +0 // Infinity\n```\n \n### 正负之分\n和+0\\-0 不同,`Infinity` 不等于 `-Infinity`。\n\n```javascript\nInfinity === -Infinity // false\n```\n### 产生原因\n\n```javascript\nMath.pow(+0, -1) // Infinity\nMath.pow(-0, -1) // -Infinity\n\n//运算结果超出JavaScript可接受范围,也会返回无穷。\nMath.pow(2, 2048) // Infinity\n-Math.pow(2, 2048) // -Infinity\n```\n\n### 参与运算\n\nInfinity的四则运算,符合无穷的数学计算规则。\n\n```javascript\n5 * Infinity // Infinity\n5 - Infinity // -Infinity\nInfinity / 5 // Infinity\n5 / Infinity // 0\n```\n\n```javascript\nInfinity - Infinity // NaN\nInfinity / Infinity // NaN\n\nInfinity + Infinity // Infinity\nInfinity * Infinity // Infinity\n```\n \nnfinity可以用于布尔运算。可以记住,Infinity是JavaScript中最大的值(NaN除外),-Infinity是最小的值(NaN除外)。\n\n```javascript \n5 > -Infinity // true\n5 > Infinity // false\n```\n\n由于数值正向溢出(overflow)、负向溢出(underflow)和被0除,JavaScript都不报错,所以单纯的数学运算几乎没有可能抛出错误。\n\n### isFinite函数\nisFinite函数返回一个布尔值,检查某个值是否为正常值,而不是Infinity。\n\n```javascript\nisFinite(Infinity) // false\nisFinite(-1) // true\nisFinite(true) // true\nisFinite(NaN) // false //如果对NaN使用isFinite函数,也返回false,表示NaN不是一个正常值\n```\n\n# 数值转换\n\n## Number()\n\n使用Number函数,可以将任意类型的值(<span style=\"color:red;\">parseInt和parseFloat只能转换字符串和数值类型</span>)转化成数字。\n\n* **简单类型转换规则**\n\n - 数值:转换为十进制(因为默认调用toString(),会以十进制输出)。\n ```javascript\n Number(10); // 10 \n Number(010); // 8\n Number(090); // 90 因为八进制中没有9,所以按照十进制处理 \n Number(0x16); // 22 \n \n Number(0o10); // 在chrome和firefox为8,IE报错 `缺少 “)”` \n Number(0b1000); // 在chrome和firefox为8,IE报错 `缺少 “)”` \n ```\n\n - 字符串:先去掉字符串前后的空格,如果可以被解析为数值,则转换为相应的数值,否则得到NaN。**空字符串`\"\"`转为0**。\n - 如果字符串是`Number(\"0o17\")`和`Number(\"0b10000\")`(包括前面带正号或负号的情况)在chrome和firefox会按照八进制转换为十进制 16,但是IE不会生产`NaN`,十六进制没问题chrome和firefox、IE没问题。\n - 如果字符串中包含有效的浮点格式,如\"1.1\",则将其转换为对应的浮点数值(同样,也会忽略前导零)。\n \n - 布尔值:`true`转成 1,`false` 转成 0。\n\n - `undefined`:转成`NaN`。\n\n - `null`:转成0。\n\n ```javascript\n Number(\"324\") // 324\n Number(\" -9.8 \") // -9.8\n \n Number(\"010\") // 10 和parseInt(\"010\")为不同\n Number(\"0o17\")// 在chrome和firefox为16。IE为NaN。 \n Number(\"0b10000\") // 在chrome和firefox为16。IE为NaN。 \n Number(\"0x10\") // 在chrome和firefox为16。IE也为16。\n \n Number(\"324abc\") // NaN\n\n Number(\"\") // 0\n\n Number(false) // 0\n\n Number(undefined) // NaN\n\n Number(null) // 0\n ```\n* **对象的转换规则**\n 对象的转换规则比较复杂。\n 1. 先调用对象自身的`valueOf`方法,如果该方法返回原始类型的值(数值、字符串和布尔值),则直接对该值使用`Number`方法,不再进行后续步骤。\n 2. 如果`valueOf`方法返回复合类型的值,再调用对象自身的`toString`方法,如果`toString`方法返回原始类型的值,则对该值使用`Number`方法,不再进行后续步骤。\n 3. 如果`toString`方法返回的是复合类型的值,则报错。\n \n ```javascript\n Number({a:1,valueOf:function(){return \"5\"}}); //5 先调用对象自身的`valueOf`方法,如果该方法返回原始类型的值(数值、字符串和布尔值),则直接对该值使用`Number`方法,不再进行后续步骤。\n Number({a:1,valueOf:function(){return {b:5}},toString:function(){return 4}}); //4 如果`valueOf`方法返回复合类型的值,再调用对象自身的`toString`方法,如果`toString`方法返回原始类型的值,则对该值使用`Number`方法,不再进行后续步骤。\n Number({a:1,valueOf:function(){return {b:5}},toString:function(){return {}}}); //TypeError: Cannot convert object to primitive value 如果`toString`方法返回的是复合类型的值,则报错。\n ```\n## parseInt()\n* 基本用法 \n `parseInt()`方法可以将字符串转化为整数。如果字符串头部有空格,空格会被自动去除。\n ```javascript\n parseInt('8a') // 8\n parseInt(\" -12 ba\") //-12\n \n //十六进制\n parseInt('0xf00') // 3840 开头两个字符是`0x`或`0X`,`parseInt`将其视为十六进制数\n //八进制\n parseInt('056') // 56\n parseInt('0o56') // 0\n parseInt('0O56') // 0\n //二进制\n parseInt(\"0B10\") // 0\n ```\n 上面代码中,`parseInt`的参数都是字符串,结果只返回字符串头部可以转为数字的部分。最后一行的`0xf00`之所以可以转为数字,因为如果开头两个字符是`0x`或`0X`,`parseInt`将其视为十六进制数,但是八进制和二进制确默认视为普通字符串。\n 如果字符串的第一个`非空格字符`不能转化为数字(数字的正负号除外),返回`NaN`。\n \n ```javascript\n parseInt('abc') // NaN\n parseInt('.3') // NaN\n parseInt('') // NaN 不同于Number()\n parseInt(null) // NaN 不同于Number()\n parseInt('+') // NaN\n ```\n* 进制转换\n`parseInt()`方法还可以接受第二个参数(2到36之间,超出区间(`包括负数但0除外`)返回`NaN`),表示被解析的值的进制,返回该值对应的十进制数。\n如果第二个参数不是数值,会被自动转为(调用`Number()`)一个整数,这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回`NaN`,如果转换的结果为`0,NaN`,都会直接忽略返回原值。\n如果第二个参数是`0`、`undefined`和`null`等等一些不能转为正常整数的值,则直接忽略。\n\n ```javascript\n parseInt(1000, 2) // 8\n parseInt(1000, 6) // 216\n parseInt(1000, 8) // 512\n //特殊情况\n parseInt(1000, \"8\") // 512 把\"8\"自动转为8\n parseInt(1000, \" +8 \") // 512 会自动去掉前置和后置空格\n parseInt(1000, \" -8 \") // NaN 负数超出范围\n parseInt(1000, 1) // NaN 超出范围\n parseInt(1000, 37) // NaN 超出范围\n parseInt(1000, \"8aa\") // 1000 因为Number(\"8aa\")等于NaN\n parseInt(1000, 0) // 1000 直接忽略返回原值\n parseInt(1000, NaN) // 1000\n parseInt(1000, null) // 1000 因为Number(NaN)等于0\n parseInt(1000, undefined) // 1000 因为Number(undefined)等于NaN\n parseInt(\"1000\", {\n a:1,\n valueOf:function(){\n return 5;\n }\n }) //125 因为Number({a:1,valueOf:function(){return 5;}}) 返回5 所以\n ```\n \n* 特别注意\n需要注意的是,进制转换的时候,参数是字符串或数值,`parseInt`的行为不一致。\n1. 如果第一个参数是数值,会将这个数值先转为十进制,然后再应用第二个参数。\n\n ```javascript\n parseInt(0x11, 36) // 43\n parseInt(17, 36) // 43\n ```\n 上面代码中,`0x11`会被先转为十进制的17,然后再用36进制解读这个17,最后返回结果43。\n\n2. 如果第一个参数是字符串,则会直接用指定进制解读这个字符串。\n \n ```javascript\n parseInt('0x11', 36) // 42805\n parseInt('x', 36) // 33\n ```\n\n 上面代码中,字符串`0x11`会被直接当作一个36进制的数。由于字符`x`在36进制中代表33,导致`0x11`被解读为42805。\n\n ```javascript\n parseInt(010, 10) // 8\n parseInt('010', 10) // 10\n\n parseInt(010, 2) // NaN 因为`010数值`转换为十进制是8,而8在二进制是不存在的所以返回`NaN`\n parseInt('010', 2) // 2\n\n parseInt(010, 8) // NaN 因为`010数值`转换为十进制是8,而8在八进制是不存在的所以返回`NaN`\n parseInt('010', 8) // 8\n\n parseInt(020, 10) // 16\n parseInt('020', 10) // 20\n\n parseInt(020, 8) // 14 因为`020数值`转换为十进制是16,再转换为8进制刚好14\n parseInt('020', 8) // 16 因为`020字符串`转换为十进制是20,再转换为8进制刚好16\n ```\n\n 上面代码中,`010`会被先转为十进制8,然后再应用第二个参数,由于二进制和八进制中没有8这个数字,所以`parseInt(010, 2)`和`parseInt(010, 8)`返回`NaN`。同理,数值`020`会被先转为十进制的16,然后再应用第二个参数。\n > `parseInt`的很多复杂行为,都是由八进制的前缀0引发的,这增加编程处理的复杂性。因此,ECMAScript 5不再允许parseInt将带有前缀0的数字,视为八进制数,而是要求忽略这个`0`。但是,为了保证兼容性,大部分浏览器并没有部署这一条规定。\n\n3. 如果第一个参数是以`0x`或`0X`开头的字符串,而第二个参数省略或为0,则`parseInt`自动将第二个参数设为16。\n ```javascript\n parseInt('0xFF') // 255\n parseInt('0xFF', 0) // 255\n parseInt('0xFF', 16) // 255\n \n parseInt('0xFF', 10) // 0\n parseInt('0xFF', 17) // 0\n ```\n 上面代码中,第二个参数除了0、16和省略,其他情况都会依次解析第一个参数,直到遇到第一个不可解析字符。\n\n4. 如果参数是对象,数据或者{}\n 和Number()过程相反,先调用 toString(),如果返回的不是 简单数据类型则再调用valueOf方法。\n `parseInt({a:1,valueOf:function(){return 5},toString:function(){return 4}});// 4`\n `parseInt([2,3]);//2因为数组的valueOf返回还是数组,继续调用toString方法,返回\"2,3\",再parseInt返回 2`\n\n5. 科学计数法产生的扯淡问题\n 对于那些会自动转为科学计数法的数字,`parseInt`会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。\n \n ```javascript\n parseInt(1000000000000000000000.5, 10) // 1\n // 等同于\n parseInt('1e+21', 10) // 1\n\n parseInt(0.0000008, 10) // 8\n // 等同于\n parseInt('8e-7', 10) // 8\n ```\n \n## parseFloat()\n* 基本用法\n parseFloat`方法用于将一个字符串转为浮点数。 \n ```javascript\n parseFloat(\"3.14\") // 3.14\n ```\n\n1. 如果字符串符合科学计数法,则会进行相应的转换。\n \n ```javascript\n parseFloat('314e-2') // 3.14\n parseFloat('0.0314E+2') // 3.14\n ```\n2. 如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。\n ```javascript\n parseFloat('3.14more non-digit characters') // 3.14\n ```\n `parseFloat`方法会自动过滤字符串前导的空格。\n ```javascript\n parseFloat('\\t\\v\\r12.34\\n ') // 12.34\n ```\n\n3. 如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回`NaN`。\n ```javascript\n parseFloat([]) // NaN\n parseFloat('FF2') // NaN\n\n parseFloat(true) // NaN 不同于`Number`函数\n Number(true) // 1\n\n parseFloat(null) // NaN 不同于`Number`函数\n Number(null) // 0\n\n parseFloat('') // NaN 不同于`Number`函数\n Number('') // 0\n\n parseFloat('123.45#') // 123.45 不同于`Number`函数\n Number('123.45#') // NaN\n ```\n 4. 如果参数是对象,数据或者{}\n 和Number()过程相反,先调用 toString(),如果返回的不是 简单数据类型则再调用valueOf方法。\n `parseFloat({a:1,valueOf:function(){return 5},toString:function(){return 4}});// 4`\n `parseInt([2,3]);//2因为数组的valueOf返回还是数组,继续调用toString方法,返回\"2,3\",再parseInt返回 2`\n \n** 特别注意:`parseInt()`和`parseFloat()`会先调用值的`toString()`方法,`Number()`是先调用`valueOf`,返回结果有问题再调用`toString`**\n```javascript\nparseInt({\na: 1,\ntoString: function(){\n return \"11\"\n }\n});\n\nparseFloat({\na: 1,\ntoString: function(){\n return \"11\"\n }\n});\n```\n上面这两种情况也会正确的返回`11`,看到这儿你会说“你不是说parseInt只能转换字符串”吗?其实真正的原因是,所有的转换第一步都是调用`toString()`方法。\n\n```javascript\nNumber.prototype.toString = function(){\n console.log(\"我被调用了\")\n return 123123;\n}\nNumber.prototype.valueOf = function(){\n console.log(\"我被调用了-valueOf\")\n return 123123;\n}\n\nvar a = new Number(12);\n\n\nparseInt(a) // 我被调用了 123123\nNumber(a) // 我被调用了-valueOf 123123\n```\n上面这段code 足以证明**特别注意**。\n详见:[Ex igne vita](http://es5.github.io/#x15.1.2.2)\n\n** 引用:**\n\n> [紫云飞](http://www.cnblogs.com/ziyunfei/archive/2012/12/10/2777099.html)\n> [阮一峰](http://javascript.ruanyifeng.com/)","source":"_posts/好好学学number-2016-01-29.md","raw":"title: '好好学学number!'\ndate: 2016-01-29 09:55:43\ntags:\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# Number类型\n\nJavaScript内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相等的,而且1加上1.0得到的还是一个整数,不会像有些语言那样变成小数。\n<!--more-->\n```javascript\n1 === 1.0 // true\n1 + 1.0 // 2\n```\n也就是说,在JavaScript语言的底层,根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时JavaScript会自动把64位浮点数,转成32位整数,然后再进行运算。\n\n由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。\n```javascript \n0.1 + 0.2 === 0.3 // false\n\n0.3 / 0.1 // 2.9999999999999996\n\n(0.3 - 0.2) === (0.2 - 0.1) // false\n```\n{% asset_img number2.png %}\n\n解释:根据国际标准IEEE-754,64位浮点数格式的64个二进制位中,第0位到第51位储存有效数字部分(共52位),第52到第62位储存指数部分,第63位是符号位,0表示正数,1表示负数。\n\n```javascript\nMath.pow(2, 53) // 9007199254740992\n\nMath.pow(2, 53) + 1 // 9007199254740992\n\nMath.pow(2, 53) + 2 // 9007199254740994\n\nMath.pow(2, 53) + 3 // 9007199254740996\n\nMath.pow(2, 53) + 4 // 9007199254740996\n```\n \n从上面示例可以看到,大于2的53次方以后,整数运算的结果开始出现错误。所以,大于等于2的53次方的数值,都无法保持精度。因此,JavaScript提供的有效数字的精度为53个二进制位(IEEE 754规定有效数字第一位默认总是为1,不保存在64位浮点数之中,这一位再加上后面的52位,就是总共53位),也就是说,绝对值小于2的53次方的整数,即-(2<sup>53</sup>-1)到2<sup>53</sup>-1,都可以精确表示。\n \n那么超过2<sup>53</sup>部分的数字怎么处理呢?\n\n```javascript\nMath.pow(2, 53) // 9007199254740992\n\n9007199254740992111 // 9007199254740992000 \n```\n上面示例表明,大于2的53次方以后,多出来的有效数字(最后三位的`111`)都会无法保存,变成0。\n\n另一方面,64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047(2的11次方减1)。也就是说,64位浮点数的指数部分的值最大为2047,分出一半表示负数,则JavaScript能够表示的数值范围为2<sup>1024</sup>到2<sup>-1023</sup>(开区间),超出这个范围的数无法表示。\n\n如果指数部分等于或超过最大正值1024,JavaScript会返回`Infinity`(关于Infinity的介绍参见下文),这称为“正向溢出”;如果等于或超过最小负值-1023(即非常接近0),JavaScript会直接把这个数转为0,这称为“负向溢出”。事实上,JavaScript对指数部分的两个极端值(`11111111111`(二进制)和`00000000000`(二进制))做了定义,`11111111111`(二进制)表示`NaN`和`Infinity`,`00000000000`(二进制)表示0。\n \n```javascript\n var x = 0.5;\n \n for(var i = 0; i < 25; i++) {\n x = x * x;\n }\n console.log(x);// 0\n```\n上面代码对0.5连续做25次平方,由于最后结果太接近0,超出了可表示的范围,JavaScript就直接将其转为0。\n\n>* 精确地描述,Number类型拥有 18437736874454810627(即,2<sup>64</sup>-2<sup>53</sup>+3)个值,表示为 IEEE-754 格式 64 位双精度数值(IEEE 二进制浮点数算术中描述了它)。\n>* <span style=\"color: red;\">除了 IEEE 标准中的 9007199254740990(即,2<sup>53</sup>-2)个明显的“非数字”值;在 ECMAScript 中,它们被表示为一个单独的特殊值:NaN。</span>(请注意,NaN 值由程序表达式 NaN 产生,并假设执行程序不能调整定义的全局变量 NaN,在javascript中NaN是global全局对象的一个属性`window.NaN`) 在某些实现中,外部代码也许有能力探测出众多非数字值之间的不同,但此类行为依赖于具体实现;对于 ECMAScript 代码而言,NaN 值相互之间无法区别。\n>* 还有另外两个特殊值,称为正无穷和负无穷。为简洁起见,在说明目的时,用符号 +∞ 和 -∞ 分别代表它们。(请注意,两个无限数值由程序表达式 +Infinity(简作 Infinity) 和 -Infinity 产生,并假设执行程序不能调整定义的全局变量 Infinity,在javascript中global全局对象的一个Infinity属性`window.Infinity`)。\n>* 另外 18437736874454810624(即,2<sup>64</sup>-2<sup>53</sup>)个值被称为有限数值,其中的一半是正数,另一半是负数,对于每个正数而言,都有一个与之对应的、相同规模的负数。\n>* 请注意,还有一个正零和一个负零。\n>* [查看number类型数值的二进制](http://alvarto.github.io/VisualNumeric64/)\n\n数轴:\n\n{% asset_img number.png [200] [400] %}\n\n \n> 对于这个如果有兴趣细扣就[点这里](http://www.cnblogs.com/kingwolfofsky/archive/2011/07/21/2112299.html),[还有这里](https://www.w3.org/html/ig/zh/wiki/ES5/types#Number_.E7.B1.BB.E5.9E.8B)\n\n## 整数\n\n整数可以被表示成十进制(基数为10)、十六进制(基数为16)以及八进制(基数为8)。\n\n* 十进制:十进制整数字组成的数字序列,不带前导0(零)。\n* 八进制:八进制整数只能包括数字0-7,通过在八进制整数前面加前导`0`(零)或者或`0o`的数值来表示八进制整数。八进制整数只包含 0 到 7 的数字。<span style=\"color:red;\">具有前导`0`并包含数字“8”和/或“9”的数字将被解释为十进制数字</span>,具有前导`0o`并包含数字“8”和/或“9”的数字将会报错。\n* 十六进制:通过在整数前面加前导“0x”(零和 x|X)来表示十六进制(“hex”)整数。字母 A 到 F 以单个数字的形式表示以 10 为基数的 10 到 15。字母 A 到 F 用于以单个数字的形式表示以 10 为基数的 10 到 15。即,0xF 相当于 15,0x10 相当于 16。\n* 二进制:有前缀`0b`或`0B`的数值。\n\n在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值,还有严格模式下禁止使用八进制。\n \n```javascript\n //默认情况下,JavaScript内部会自动将八进制、十六进制、二进制转为十进制。 \n 0xff // 255\n -0xF1A7// -61863\n 0o377 // 255\n 015 // 13\n 019 // 19\n 0b11 // 3\n \n //如果八进制、十六进制、二进制的数值里面,出现不属于该进制的数字,就会报错。\n 0xzz // 报错\n 0o88 // 报错\n 0b22 // 报错\n```\n\n<span style=\"color:#555;\">IEBUG:从 Internet Explorer 9 标准模式、Internet Explorer 10 标准模式、Internet Explorer 11 标准模式和 Windows 应用商店应用 开始,parseInt 函数不将前缀为“0”的字符串视为八进制。但在不使用 parseInt 函数时,前缀为“0”的字符串仍可被解释为八进制。</span>\n\n## 浮点值\n\n* 一个十进制整数,它可以带符号(即前面的“+”或“ - ”号),\n* 一个小数点(“.”),\n* 一个小数部分(由一串十进制数表示),\n* 一个指数部分\n指数部分是以“e”或“E”开头后面跟着一个整数,可以有正负号(即前面写“+”或“-”)。一个浮点数字面值必须至少有一位数字,后接小数点或者“e”(大写“E”也可)组成。一些浮点数字面值的例子,如3.1415,-3.1E13,.1e12以及2E-12。\n简言之,其语法是:\n\n [digits][.digits][(E|e)[(+|-)]digits]\n //实例:\n 3.14\n 2345.789\n .3333333333333333333 // 0 可以省略\n\n以下两种情况,JavaScript会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。\n\n* 小数点前的数字多于21位\n```javascript\n console.log(1234567890123456789012) // 1.2345678901234568e+21\n```\n* 小数点后的零多于5个\n```javascript\n console.log(0.0000003) //3e-7\n```\n\n## NaN\n\nNaN(not a number)用于处理计算中出现的错误情况,比如 0.0 除以 0.0 或者求负数的平方根。由上面的表中可以看出,对于单精度浮点数,NaN 表示为指数为 emax + 1 = 128(指数域全为 1),且尾数域不等于零的浮点数。IEEE 标准没有要求具体的尾数域,所以 <span style=\"red;\">NaN 实际上不是一个,而是一族</span>,它是全局对象global对象的一个属性(`window.NaN`)。\n需要注意的是,NaN不是一种独立的数据类型,而是一种特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。\n\n```javascript\n console.log(typeof NaN) // 'number\n```\n### 特性:\n* NaN不等于任何值,包括它本身。`NaN === NaN // false`\n* 由于数组的indexOf方法,内部使用的是严格相等运算符,所以该方法对NaN不成立。`NaN].indexOf(NaN) // -1`\n* NaN在布尔运算时被当作false。`Boolean(NaN) // false`\n* NaN与任何数(包括它自己)的运算,得到的都是NaN。\n\n```javascript\n NaN + 32 // NaN\n NaN - 32 // NaN\n NaN * 32 // NaN\n NaN / 32 // NaN\n```\n \n### 判断NaN的方法 isNaN\n\n```javascript\n isNaN(NaN) // true\n isNaN(123) // false\n```\n \n但是,`isNaN`只对数值有效,<span style=\"color:red;\">如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成`NaN`,所以最后返回`true`</span>,这一点要特别引起注意。也就是说,`isNaN`为`true`的值,有可能不是`NaN`,而是一个字符串。出于同样的原因,对于对象和数组,`isNaN`也返回`true`。\n\n```javascript\n isNaN('Hello') // true\n // 等同于\n isNaN(Number('Hello')) // true\n\n isNaN({}) // true\n // 等同于\n isNaN(Number({})) // true\n\n isNaN(['xzy']) // true\n // 等同于\n isNaN(Number(['xzy'])) // true\n```\n\n但是,对于空数组和只有一个数值成员的数组,`isNaN`返回`false`。\n\n```javascript\n isNaN([]) // false\n isNaN([123]) // false\n isNaN(['123']) // false\n```\n \n上面的代码之所以返回`false`,原因是这些数组能被`Number`函数转成数值,请参见《数据类型转换》一节。\n\n因此,使用`isNaN`之前,最好判断一下数据类型。\n\n```javascript\n function myIsNaN(value) {\n return typeof value === 'number' && isNaN(value);\n }\n 判断NaN更可靠的方法是,利用`NaN`是JavaScript之中唯一不等于自身的值这个特点,进行判断。\n\n function myIsNaN(value) {\n return value !== value;\n }\n```\n \n## +0 和 -0\n\n先看一道题: 假如 A === B 并且 1/A < 1/B; 请问 A 等于 几?\n\n### 产生原因\n数字需要被编码才能进行数字化存储.举个例子,假如我们要将一个整数编码为4位的二进制数,使用原码(sign-and-magnitude)方法,则最高位是符号位(0代表正,1代表负),剩下的三位表示大小(具体的值).因此,−2和+2会编码成为下面这样:\n\n 1010 // +2\n 0010 // -2\n 这就意味着将会有两个零:\n 1000 // -0\n 0000 // +0\n在JavaScript中,所有的数字都是浮点数,都是根据IEEE-754标准中的浮点数算法以双精度格式被编码。这个标准中正负号的处理方式类似于原码(sign-and-magnitude)方法中整数的编码方式,所以也有正负零。\n\n### 参与运算\n```javascript\n+0 === -0 //true 虽然有正0和负0但是他们两个是相等\n-0 < +0 //false \n+0 < -0 //false\n\n-0 + -0 // -0\n-0 + +0 // +0\n+0 X -5 // -0\n-0 X -5 // +0\n1 / +0 // Infinity\n1 / -0 // -Infinity\n+0 / -0 // NaN\n```\n \n### 参与Math对象的方法\n\n* Math.pow(x,y)方法可返回从 之间的角度。\n \n```javascript\nMath.pow(+0, -1) // Infinity\nMath.pow(-0, -1) // -Infinity\n```\n \n* Math.atan2(x,y)方法可返回从 x<sup>y</sup> 的值。\n \n```javascript\nx = 0 , y <= -0 // π(3.141592653589793)\nMath.atan2(+0, -0) // π(3.141592653589793)\n\nx = 0 , y >= 0 // 0\nMath.atan2(+0, +0) // 0\n\nx = -0 , y >= +0 // -0\nMath.atan2(-0, +0) // -0\n\nx = -0 , y <= -0 // -π(-3.141592653589793)\nMath.atan2(-0, -0) // -π(-3.141592653589793)\n```\n \n* Math.round()是另外一个参数不为零却产生-0结果的操作:\n```javascript\nMath.round(-0.1) // -0\n```\n\n### 区分这两个零\n\n方法1: 判断一个零是正还是负的标准解法是用它除1,然后看计算的结果是-Infinity还是+Infinity\n \n```javascript\nfunction isNegativeZero(x) {\n return x === 0 && (1/x < 0);\n}\n```\n\n方法2: 除了上面讲的几种解法.还有一个解法来自Allen Wirfs-Brock(译者注:TC39编辑,ES标准就是他写出来的。):\n \n```javascript\nfunction isNegativeZero(x) {\n if (x !== 0) return false;\n var obj = {};\n Object.defineProperty(obj, 'z', { value: -0, configurable: false });\n try {\n // 如果x的值和z属性的当前值不相等的话,就会抛出异常.\n Object.defineProperty(obj, 'z', { value: x });\n } catch (e) {\n return false\n };\n return true;\n}\n```\n \n解释: 通常情况下,你不能重新定义一个不可配置的对象属性,否则会抛出异常:`TypeError: Cannot redefine property: z`可是,如果你重新定义属性时指定的属性特性的值与该特性当前的值相等,则JavaScript会忽略掉这个重定义,不会抛出异常。其中在判断两个值是否相等时使用的运算不是===,是一个称之为SameValue的内部算法,该算法可以区分开 -0 和 +0 。可以从Wirfs-Brock的原文中了解更多细。(冻结一个对象会让该对象的所有属性变的不可配置)。\n\n SameValue 算法\n 内部严格比较操作 SameValue(x,y),x 和 y 为 ECMAScript 语言中的值,需要产出 true 或 false。Note.png Note.png V8.png\n\n 比较过程如下:\n\n 如果 Type(x) 与 Type(y) 的结果不一致,返回 false,否则\n 如果 Type(x) 结果为 Undefined,返回 true\n 如果 Type(x) 结果为 Null,返回 true\n 如果 Type(x) 结果为 Number,则\n 如果 x 为 NaN,且 y 也为 NaN,返回 true\n 如果 x 为 +0,y 为 -0,返回 false\n 如果 x 为 -0,y 为 +0,返回 false\n 如果 x 与 y 为同一个数字,返回 true\n 返回 false\n 如果 Type(x) 结果为 String,如果 x 与 y 为完全相同的字符序列(相同的长度和相同的字符对应相同的位置),返回 true,否则,返回 false\n 如果 Type(x) 结果为 Boolean,如果 x 与 y 都为 true 或 false,则返回 true,否则,返回 false\n 如果 x 和 y 引用到同一个 Object 对象,返回 true,否则,返回 false\n\n\n> 在执行一些特殊方法的时候,比如alert或innerHTML等方法,它将由脚本解析器自动调用toString()方法。\n\n看完上面这些,那么这节开头的题目的结果自然而然就知道喽。\n\n## Infinity\nInfinity 表示“无穷”,挂在global对象下的Infinity属性上(window.Infinity)。除了0除以0得到NaN,其他任意数除以0,得到Infinity。获得方式\n\n```javascript\nNumber.NEGATIVE_INFINITY // +infinity\nNumber.POSITIVE_INFINITY // -infinity\n\nwindow.Infinity === Number.POSITIVE_INFINITY // ture\n```\n\n```javascript\n1 / -0 // -Infinity\n1 / +0 // Infinity\n```\n \n### 正负之分\n和+0\\-0 不同,`Infinity` 不等于 `-Infinity`。\n\n```javascript\nInfinity === -Infinity // false\n```\n### 产生原因\n\n```javascript\nMath.pow(+0, -1) // Infinity\nMath.pow(-0, -1) // -Infinity\n\n//运算结果超出JavaScript可接受范围,也会返回无穷。\nMath.pow(2, 2048) // Infinity\n-Math.pow(2, 2048) // -Infinity\n```\n\n### 参与运算\n\nInfinity的四则运算,符合无穷的数学计算规则。\n\n```javascript\n5 * Infinity // Infinity\n5 - Infinity // -Infinity\nInfinity / 5 // Infinity\n5 / Infinity // 0\n```\n\n```javascript\nInfinity - Infinity // NaN\nInfinity / Infinity // NaN\n\nInfinity + Infinity // Infinity\nInfinity * Infinity // Infinity\n```\n \nnfinity可以用于布尔运算。可以记住,Infinity是JavaScript中最大的值(NaN除外),-Infinity是最小的值(NaN除外)。\n\n```javascript \n5 > -Infinity // true\n5 > Infinity // false\n```\n\n由于数值正向溢出(overflow)、负向溢出(underflow)和被0除,JavaScript都不报错,所以单纯的数学运算几乎没有可能抛出错误。\n\n### isFinite函数\nisFinite函数返回一个布尔值,检查某个值是否为正常值,而不是Infinity。\n\n```javascript\nisFinite(Infinity) // false\nisFinite(-1) // true\nisFinite(true) // true\nisFinite(NaN) // false //如果对NaN使用isFinite函数,也返回false,表示NaN不是一个正常值\n```\n\n# 数值转换\n\n## Number()\n\n使用Number函数,可以将任意类型的值(<span style=\"color:red;\">parseInt和parseFloat只能转换字符串和数值类型</span>)转化成数字。\n\n* **简单类型转换规则**\n\n - 数值:转换为十进制(因为默认调用toString(),会以十进制输出)。\n ```javascript\n Number(10); // 10 \n Number(010); // 8\n Number(090); // 90 因为八进制中没有9,所以按照十进制处理 \n Number(0x16); // 22 \n \n Number(0o10); // 在chrome和firefox为8,IE报错 `缺少 “)”` \n Number(0b1000); // 在chrome和firefox为8,IE报错 `缺少 “)”` \n ```\n\n - 字符串:先去掉字符串前后的空格,如果可以被解析为数值,则转换为相应的数值,否则得到NaN。**空字符串`\"\"`转为0**。\n - 如果字符串是`Number(\"0o17\")`和`Number(\"0b10000\")`(包括前面带正号或负号的情况)在chrome和firefox会按照八进制转换为十进制 16,但是IE不会生产`NaN`,十六进制没问题chrome和firefox、IE没问题。\n - 如果字符串中包含有效的浮点格式,如\"1.1\",则将其转换为对应的浮点数值(同样,也会忽略前导零)。\n \n - 布尔值:`true`转成 1,`false` 转成 0。\n\n - `undefined`:转成`NaN`。\n\n - `null`:转成0。\n\n ```javascript\n Number(\"324\") // 324\n Number(\" -9.8 \") // -9.8\n \n Number(\"010\") // 10 和parseInt(\"010\")为不同\n Number(\"0o17\")// 在chrome和firefox为16。IE为NaN。 \n Number(\"0b10000\") // 在chrome和firefox为16。IE为NaN。 \n Number(\"0x10\") // 在chrome和firefox为16。IE也为16。\n \n Number(\"324abc\") // NaN\n\n Number(\"\") // 0\n\n Number(false) // 0\n\n Number(undefined) // NaN\n\n Number(null) // 0\n ```\n* **对象的转换规则**\n 对象的转换规则比较复杂。\n 1. 先调用对象自身的`valueOf`方法,如果该方法返回原始类型的值(数值、字符串和布尔值),则直接对该值使用`Number`方法,不再进行后续步骤。\n 2. 如果`valueOf`方法返回复合类型的值,再调用对象自身的`toString`方法,如果`toString`方法返回原始类型的值,则对该值使用`Number`方法,不再进行后续步骤。\n 3. 如果`toString`方法返回的是复合类型的值,则报错。\n \n ```javascript\n Number({a:1,valueOf:function(){return \"5\"}}); //5 先调用对象自身的`valueOf`方法,如果该方法返回原始类型的值(数值、字符串和布尔值),则直接对该值使用`Number`方法,不再进行后续步骤。\n Number({a:1,valueOf:function(){return {b:5}},toString:function(){return 4}}); //4 如果`valueOf`方法返回复合类型的值,再调用对象自身的`toString`方法,如果`toString`方法返回原始类型的值,则对该值使用`Number`方法,不再进行后续步骤。\n Number({a:1,valueOf:function(){return {b:5}},toString:function(){return {}}}); //TypeError: Cannot convert object to primitive value 如果`toString`方法返回的是复合类型的值,则报错。\n ```\n## parseInt()\n* 基本用法 \n `parseInt()`方法可以将字符串转化为整数。如果字符串头部有空格,空格会被自动去除。\n ```javascript\n parseInt('8a') // 8\n parseInt(\" -12 ba\") //-12\n \n //十六进制\n parseInt('0xf00') // 3840 开头两个字符是`0x`或`0X`,`parseInt`将其视为十六进制数\n //八进制\n parseInt('056') // 56\n parseInt('0o56') // 0\n parseInt('0O56') // 0\n //二进制\n parseInt(\"0B10\") // 0\n ```\n 上面代码中,`parseInt`的参数都是字符串,结果只返回字符串头部可以转为数字的部分。最后一行的`0xf00`之所以可以转为数字,因为如果开头两个字符是`0x`或`0X`,`parseInt`将其视为十六进制数,但是八进制和二进制确默认视为普通字符串。\n 如果字符串的第一个`非空格字符`不能转化为数字(数字的正负号除外),返回`NaN`。\n \n ```javascript\n parseInt('abc') // NaN\n parseInt('.3') // NaN\n parseInt('') // NaN 不同于Number()\n parseInt(null) // NaN 不同于Number()\n parseInt('+') // NaN\n ```\n* 进制转换\n`parseInt()`方法还可以接受第二个参数(2到36之间,超出区间(`包括负数但0除外`)返回`NaN`),表示被解析的值的进制,返回该值对应的十进制数。\n如果第二个参数不是数值,会被自动转为(调用`Number()`)一个整数,这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回`NaN`,如果转换的结果为`0,NaN`,都会直接忽略返回原值。\n如果第二个参数是`0`、`undefined`和`null`等等一些不能转为正常整数的值,则直接忽略。\n\n ```javascript\n parseInt(1000, 2) // 8\n parseInt(1000, 6) // 216\n parseInt(1000, 8) // 512\n //特殊情况\n parseInt(1000, \"8\") // 512 把\"8\"自动转为8\n parseInt(1000, \" +8 \") // 512 会自动去掉前置和后置空格\n parseInt(1000, \" -8 \") // NaN 负数超出范围\n parseInt(1000, 1) // NaN 超出范围\n parseInt(1000, 37) // NaN 超出范围\n parseInt(1000, \"8aa\") // 1000 因为Number(\"8aa\")等于NaN\n parseInt(1000, 0) // 1000 直接忽略返回原值\n parseInt(1000, NaN) // 1000\n parseInt(1000, null) // 1000 因为Number(NaN)等于0\n parseInt(1000, undefined) // 1000 因为Number(undefined)等于NaN\n parseInt(\"1000\", {\n a:1,\n valueOf:function(){\n return 5;\n }\n }) //125 因为Number({a:1,valueOf:function(){return 5;}}) 返回5 所以\n ```\n \n* 特别注意\n需要注意的是,进制转换的时候,参数是字符串或数值,`parseInt`的行为不一致。\n1. 如果第一个参数是数值,会将这个数值先转为十进制,然后再应用第二个参数。\n\n ```javascript\n parseInt(0x11, 36) // 43\n parseInt(17, 36) // 43\n ```\n 上面代码中,`0x11`会被先转为十进制的17,然后再用36进制解读这个17,最后返回结果43。\n\n2. 如果第一个参数是字符串,则会直接用指定进制解读这个字符串。\n \n ```javascript\n parseInt('0x11', 36) // 42805\n parseInt('x', 36) // 33\n ```\n\n 上面代码中,字符串`0x11`会被直接当作一个36进制的数。由于字符`x`在36进制中代表33,导致`0x11`被解读为42805。\n\n ```javascript\n parseInt(010, 10) // 8\n parseInt('010', 10) // 10\n\n parseInt(010, 2) // NaN 因为`010数值`转换为十进制是8,而8在二进制是不存在的所以返回`NaN`\n parseInt('010', 2) // 2\n\n parseInt(010, 8) // NaN 因为`010数值`转换为十进制是8,而8在八进制是不存在的所以返回`NaN`\n parseInt('010', 8) // 8\n\n parseInt(020, 10) // 16\n parseInt('020', 10) // 20\n\n parseInt(020, 8) // 14 因为`020数值`转换为十进制是16,再转换为8进制刚好14\n parseInt('020', 8) // 16 因为`020字符串`转换为十进制是20,再转换为8进制刚好16\n ```\n\n 上面代码中,`010`会被先转为十进制8,然后再应用第二个参数,由于二进制和八进制中没有8这个数字,所以`parseInt(010, 2)`和`parseInt(010, 8)`返回`NaN`。同理,数值`020`会被先转为十进制的16,然后再应用第二个参数。\n > `parseInt`的很多复杂行为,都是由八进制的前缀0引发的,这增加编程处理的复杂性。因此,ECMAScript 5不再允许parseInt将带有前缀0的数字,视为八进制数,而是要求忽略这个`0`。但是,为了保证兼容性,大部分浏览器并没有部署这一条规定。\n\n3. 如果第一个参数是以`0x`或`0X`开头的字符串,而第二个参数省略或为0,则`parseInt`自动将第二个参数设为16。\n ```javascript\n parseInt('0xFF') // 255\n parseInt('0xFF', 0) // 255\n parseInt('0xFF', 16) // 255\n \n parseInt('0xFF', 10) // 0\n parseInt('0xFF', 17) // 0\n ```\n 上面代码中,第二个参数除了0、16和省略,其他情况都会依次解析第一个参数,直到遇到第一个不可解析字符。\n\n4. 如果参数是对象,数据或者{}\n 和Number()过程相反,先调用 toString(),如果返回的不是 简单数据类型则再调用valueOf方法。\n `parseInt({a:1,valueOf:function(){return 5},toString:function(){return 4}});// 4`\n `parseInt([2,3]);//2因为数组的valueOf返回还是数组,继续调用toString方法,返回\"2,3\",再parseInt返回 2`\n\n5. 科学计数法产生的扯淡问题\n 对于那些会自动转为科学计数法的数字,`parseInt`会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。\n \n ```javascript\n parseInt(1000000000000000000000.5, 10) // 1\n // 等同于\n parseInt('1e+21', 10) // 1\n\n parseInt(0.0000008, 10) // 8\n // 等同于\n parseInt('8e-7', 10) // 8\n ```\n \n## parseFloat()\n* 基本用法\n parseFloat`方法用于将一个字符串转为浮点数。 \n ```javascript\n parseFloat(\"3.14\") // 3.14\n ```\n\n1. 如果字符串符合科学计数法,则会进行相应的转换。\n \n ```javascript\n parseFloat('314e-2') // 3.14\n parseFloat('0.0314E+2') // 3.14\n ```\n2. 如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。\n ```javascript\n parseFloat('3.14more non-digit characters') // 3.14\n ```\n `parseFloat`方法会自动过滤字符串前导的空格。\n ```javascript\n parseFloat('\\t\\v\\r12.34\\n ') // 12.34\n ```\n\n3. 如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回`NaN`。\n ```javascript\n parseFloat([]) // NaN\n parseFloat('FF2') // NaN\n\n parseFloat(true) // NaN 不同于`Number`函数\n Number(true) // 1\n\n parseFloat(null) // NaN 不同于`Number`函数\n Number(null) // 0\n\n parseFloat('') // NaN 不同于`Number`函数\n Number('') // 0\n\n parseFloat('123.45#') // 123.45 不同于`Number`函数\n Number('123.45#') // NaN\n ```\n 4. 如果参数是对象,数据或者{}\n 和Number()过程相反,先调用 toString(),如果返回的不是 简单数据类型则再调用valueOf方法。\n `parseFloat({a:1,valueOf:function(){return 5},toString:function(){return 4}});// 4`\n `parseInt([2,3]);//2因为数组的valueOf返回还是数组,继续调用toString方法,返回\"2,3\",再parseInt返回 2`\n \n** 特别注意:`parseInt()`和`parseFloat()`会先调用值的`toString()`方法,`Number()`是先调用`valueOf`,返回结果有问题再调用`toString`**\n```javascript\nparseInt({\na: 1,\ntoString: function(){\n return \"11\"\n }\n});\n\nparseFloat({\na: 1,\ntoString: function(){\n return \"11\"\n }\n});\n```\n上面这两种情况也会正确的返回`11`,看到这儿你会说“你不是说parseInt只能转换字符串”吗?其实真正的原因是,所有的转换第一步都是调用`toString()`方法。\n\n```javascript\nNumber.prototype.toString = function(){\n console.log(\"我被调用了\")\n return 123123;\n}\nNumber.prototype.valueOf = function(){\n console.log(\"我被调用了-valueOf\")\n return 123123;\n}\n\nvar a = new Number(12);\n\n\nparseInt(a) // 我被调用了 123123\nNumber(a) // 我被调用了-valueOf 123123\n```\n上面这段code 足以证明**特别注意**。\n详见:[Ex igne vita](http://es5.github.io/#x15.1.2.2)\n\n** 引用:**\n\n> [紫云飞](http://www.cnblogs.com/ziyunfei/archive/2012/12/10/2777099.html)\n> [阮一峰](http://javascript.ruanyifeng.com/)","slug":"好好学学number","published":1,"updated":"2016-06-11T04:57:41.618Z","layout":"post","photos":[],"link":"","_id":"citf9olz2000uskv7cvjqsn0x"},{"title":"好好学学String!","date":"2016-02-02T06:36:10.000Z","comments":1,"_content":"## 概述\n\n### 定义\n\n字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。\n<!--more-->\n```javascript\n'abc'\n\"abc\"\n```\n\n单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号。\n\n```javascript\n'key=\"value\"'\n\"It's a long journey\"\n```\n\n上面两个都是合法的字符串。\n\n如果要在单引号字符串的内部,使用单引号(或者在双引号字符串的内部,使用双引号),就必须在内部的单引号(或者双引号)前面加上反斜杠,用来转义。\n\n```javascript\n'Did she say \\'Hello\\'?'\n// \"Did she say 'Hello'?\"\n\n\"Did she say \\\"Hello\\\"?\"\n// \"Did she say \"Hello\"?\"\n```\n\n字符串默认只能写在一行内,分成多行将会报错。\n\n```javascript\n'a\nb\nc'\n// SyntaxError: Unexpected token ILLEGAL\n```\n\n上面代码将一个字符串分成三行,JavaScript就会报错。\n\n多行字符串是正式规范(ECMA 265 5th edition)的一部分,ES5扩展了字符串字面量的语法。在(7.8.4 String Literals)中添加了`DoubleStringCharacter`和 `SingleStringCharacter`: `LineContinuation`。\nLineTerminatorSequence 指的是下面这些字符中的一个:\n* 换行符 `<LF>`\n* 回车符 `<CR>`\n* 行分割符 `<LS>`\n* 段分隔符 `<PS>`\n\nLineContinuation 的语法是:\n```javascript\n \\ LineTerminatorSequence\n```\n如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。\n\n```javascript\nvar longString = \"Long\\\\n\\\nlong \\\nlong \\\nstring\";\n\nlongString\n// \"Long\\nlong long string\"\n```\n\n上面代码表示,加了反斜杠以后,原来写在一行的字符串,可以分成多行,效果与写在同一行完全一样。注意,反斜杠的后面必须是`LineTerminatorSequence`中的一个上例中的是换行符,而不能有其他字符(比如空格),否则会报错。\n\n连接运算符(`+`)可以连接多个单行字符串,用来模拟多行字符串。\n\n```javascript\nvar longString = 'Long '\n + 'long '\n + 'long '\n + 'string';\n```\n\n另外,有一种利用多行注释,生成多行字符串的变通方法。\n\n```javascript\n(function () { /*\nline 1\nline 2\nline 3\n*/}).toString().split('\\n').slice(1,-1).join('\\n')\n// \"line 1 line 2 line 3\"\n\n### 转义\n\n反斜杠(`\\\\`)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。\n\n需要用反斜杠转义的特殊字符,主要有下面这些:\n\n- `\\0` 代表没有内容的字符(\\u0000)\n- `\\b` 后退键(\\u0008)\n- `\\f` 换页符(\\u000C)\n- `\\n` 换行符(\\u000A)\n- `\\r` 回车键(\\u000D)\n- `\\t` 制表符(\\u0009)\n- `\\v` 垂直制表符(\\u000B)\n- `\\'` 单引号(\\u0027)\n- `\\\"` 双引号(\\u0022)\n- `\\\\\\\\` 反斜杠(\\u005C)\n- `\\XXX` 用三个八进制数(000到377)表示字符,`XXX`对应该字符的Unicode,比如`\\251`表示版权符号。\n- `\\xXX` 用两个十六进制数(00到FF)表示字符,`XX`对应该字符的Unicode,比如`\\xA9`表示版权符号。\n- `\\uXXXX` 用四位十六进制的Unicode编号代表某个字符,比如`\\u00A9`表示版权符号。\n\n下面是最后三种字符的特殊写法的例子。\n\n```javascript\n'\\251' // \"©\"\n'\\xA9' // \"©\"\n'\\u00A9' // \"©\"\n\n'\\172' === 'z' // true\n'\\x7A' === 'z' // true\n'\\u007A' === 'z' // true\n```\n\n如果非特殊字符前面使用反斜杠,则反斜杠会被省略。\n\n```javascript\n'\\a' // \"a\"\n```\n\n上面代码表示`a`是一个正常字符,前面加反斜杠没有特殊含义,则反斜杠会被自动省略。\n\n如果字符串的正常内容之中,需要包含反斜杠,则反斜杠前需要再加一个反斜杠,用来对自身转义。\n\n```javascript\n\"Prev \\\\ Next\" // \"Prev \\ Next\"\n```\n\n### 字符串与数组\n\n字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(从0开始)。\n\n```javascript\nvar s = 'hello';\n\ns[0] // \"h\"\ns[1] // \"e\"\ns[4] // \"o\"\n\n// 也可以直接对字符串使用方括号运算符\n'hello'[1] // \"e\"\n```\n\n如果方括号中的数字超过字符串的范围,或者方括号中根本不是数字,则返回`undefined`。\n\n```javascript\n'abc'[3] // undefined\n'abc'[-1] // undefined\n'abc'['x'] // undefined\n```\n\n但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。\n\n```javascript\nvar s = 'hello';\n\ndelete s[0];\ns // \"hello\"\n\ns[1] = 'a';\ns // \"hello\"\n\ns[5] = '!';\ns // \"hello\"\n```\n\n上面代码表示,字符串内部的单个字符无法改变和增删,这些操作会默默地失败。\n\n字符串也无法添加新属性。\n\n```javascript\nvar s = 'Hello World';\ns.x = 123;\ns.x // undefined\n```\n\n上面代码为字符串`s`添加了一个`x`属性,结果无效,总是返回`undefined`。\n\n上面这些行为的原因是,在JavaScript内部,变量`s`其实指向字符串`Hello World`的地址,而`Hello World`本身是一个常量,所以无法改变它,既不能新增,也不能删除。另一方面,当一个字符串被调用属性时,它会自动转为String对象的实例(参见《标准库》一章),调用结束后,该对象自动销毁。这意味着,下一次调用字符串的属性时,实际是调用一个临时生成的新对象,而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性。如果想要为字符串添加属性,只有在它的原型对象`String.prototype`上定义(参见《面向对象编程》一章)。\n\n### length属性\n\n`length`属性返回字符串的长度,该属性也是无法改变的。\n\n```javascript\nvar s = 'hello';\ns.length // 5\n\ns.length = 3;\ns.length // 5\n\ns.length = 7;\ns.length // 5\n```\n\n上面代码表示字符串的`length`属性无法改变,但是不会报错。\n\n## 字符集\n\nJavaScript使用Unicode字符集,也就是说在JavaScript内部,所有字符都用Unicode表示。\n\n不仅JavaScript内部使用Unicode储存字符,而且还可以直接在程序中使用Unicode,所有字符都可以写成\"\\uxxxx\"的形式,其中xxxx代表该字符的Unicode编码。比如,`\\u00A9`代表版权符号。\n\n```javascript\nvar s = '\\u00A9';\ns // \"©\"\n```\n\n每个字符在JavaScript内部都是以16位(即2个字节)的UTF-16格式储存。也就是说,JavaScript的单位字符长度固定为16位长度,即2个字节。\n\n但是,UTF-16有两种长度:对于`U+0000`到`U+FFFF`之间的字符,长度为16位(即2个字节);对于`U+10000`到`U+10FFFF`之间的字符,长度为32位(即4个字节),而且前两个字节在`0xD800`到`0xDBFF`之间,后两个字节在`0xDC00`到`0xDFFF`之间。举例来说,`U+1D306`对应的字符为𝌆,它写成UTF-16就是`0xD834 0xDF06`。浏览器会正确将这四个字节识别为一个字符,但是JavaScript内部的字符长度总是固定为16位,会把这四个字节视为两个字符。\n\n```javascript\nvar s = '\\uD834\\uDF06';\n\ns // \"𝌆\"\ns.length // 2\n/^.$/.test(s) // false\ns.charAt(0) // \"\"\ns.charAt(1) // \"\"\ns.charCodeAt(0) // 55348\ns.charCodeAt(1) // 57094\n```\n\n上面代码说明,对于于`U+10000`到`U+10FFFF`之间的字符,JavaScript总是视为两个字符(字符的`length`属性为2),用来匹配单个字符的正则表达式会失败(JavaScript认为这里不止一个字符),`charAt`方法无法返回单个字符,`charCodeAt`方法返回每个字节对应的十进制值。\n\n所以处理的时候,必须把这一点考虑在内。对于4个字节的Unicode字符,假定`C`是字符的Unicode编号,`H`是前两个字节,`L`是后两个字节,则它们之间的换算关系如下。\n\n```javascript\n// 将大于U+FFFF的字符,从Unicode转为UTF-16\nH = Math.floor((C - 0x10000) / 0x400) + 0xD800\nL = (C - 0x10000) % 0x400 + 0xDC00\n\n// 将大于U+FFFF的字符,从UTF-16转为Unicode\nC = (H - 0xD800) * 0x400 + L - 0xDC00 + 0x10000\n```\n\n下面的正则表达式可以识别所有UTF-16字符。\n\n```javascript\n([\\0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF])\n```\n\n由于JavaScript引擎(严格说是ES5规格)不能自动识别辅助平面(编号大于0xFFFF)的Unicode字符,导致所有字符串处理函数遇到这类字符,都会产生错误的结果(详见《标准库》一章的`String`对象章节)。如果要完成字符串相关操作,就必须判断字符是否落在`0xD800`到`0xDFFF`这个区间。\n\n下面是能够正确处理字符串遍历的函数。\n\n```javascript\nfunction getSymbols(string) {\n var length = string.length;\n var index = -1;\n var output = [];\n var character;\n var charCode;\n while (++index < length) {\n character = string.charAt(index);\n charCode = character.charCodeAt(0);\n if (charCode >= 0xD800 && charCode <= 0xDBFF) {\n output.push(character + string.charAt(++index));\n } else {\n output.push(character);\n }\n }\n return output;\n}\n\nvar symbols = getSymbols('𝌆');\n\nsymbols.forEach(function(symbol) {\n // ...\n});\n```\n\n替换(`String.prototype.replace`)、截取子字符串(`String.prototype.substring`, `String.prototype.slice`)等其他字符串操作,都必须做类似的处理。\n\n## Base64转码\n\nBase64是一种编码方法,可以将任意字符转成可打印字符。使用这种编码方法,主要不是为了加密,而是为了不出现特殊字符,简化程序的处理。\n\nJavaScript原生提供两个Base64相关方法。\n\n- btoa():字符串或二进制值转为Base64编码\n- atob():Base64编码转为原来的编码\n\n```javascript\nvar string = 'Hello World!';\nbtoa(string) // \"SGVsbG8gV29ybGQh\"\natob('SGVsbG8gV29ybGQh') // \"Hello World!\"\n```\n\n这两个方法不适合非ASCII码的字符,会报错。\n\n```javascript\nbtoa('你好')\n// Uncaught DOMException: The string to be encoded contains characters outside of the Latin1 range.\n```\n\n要将非ASCII码字符转为Base64编码,必须中间插入一个转码环节,再使用这两个方法。\n\n```javascript\nfunction b64Encode( str ) {\n return btoa(unescape(encodeURIComponent( str )));\n}\n\nfunction b64Decode( str ) {\n return decodeURIComponent(escape(atob( str )));\n}\n\nb64Encode('你好') // \"5L2g5aW9\"\nb64Decode('5L2g5aW9') // \"你好\"\n```\n## 转换为字符串 String()\n\n### toString()\n数值、布尔值、对象和字符串值(没错,每个字符串也都有一个`toString()`方法,该方法返回字符串的一个副本)都有`toString()`方法。但`null` 和 `undefined` 值没有这个方法。\n多数情况下,调用`toString()`方法不必传递参数。但是,在调用数值的`toString()`方法时,可以传递一个参数:输出数值的基数。默认情况下,`toString()`方法以十进制格式返回数值的字符串表示。而通过传递基数,`toString()`可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值。\n在不知道要转换的值是不是`null` 或 `undefined` 的情况下,还可以使用转型函数`String()`。\n### String()\n\n使用String函数,可以将任意类型的值转化成字符串。规则如下:\n\n1. 如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果。\n2. 如果值是null,则返回\"null\"。\n3. 如果值是undefined,则返回\"undefined\"。\n\n**(1)原始类型值的转换规则**\n\n- **数值**:调用Number的toString()方法,转换后还是原来的值。转为相应的字符串。\n\n- **字符串**:调用String的toString()方法,转换后还是原来的值。\n\n- **布尔值**:调用Boolean的toString()方法,true转为“true”,false转为“false”。\n\n- **undefined**:转为“undefined”。\n\n- **null**:转为“null”。\n```javascript\nString(123) // \"123\"\n\nString(\"abc\") // \"abc\"\n\nString(true) // \"true\"\n\nString(undefined) // \"undefined\"\n\nString(null) // \"null\"\n\nBoolean.prototype.toString = function(){\n console.log(\"wo bei diaoyongle\");\n}\nvar a = new Boolean(true);\nString(a) // \"wo bei diaoyongle\" \"undefined\"\n\n```\n**(2)对象的转换规则**\n\n如果要将对象转为字符串,则是采用以下步骤。\n\n1. 先调用`toString`方法,如果toString方法返回的是简单类型(`Boolean`、`number`、`string`、`null`、`undefined`)的值,则对该值使用`String`方法,不再进行以下步骤。\n\n2. 如果`toString`方法返回的是复合类型的值,再调用`valueOf`方法,如果`valueOf`方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤。\n\n3. 如果`valueOf`方法返回的是复合类型的值,则报错。\n\nString方法的这种过程正好与Number方法相反。\n\n```javascript\n\nString({a:1}) // \"[object Object]\"\n```\n\n上面代码相当于下面这样。\n\n```javascript\n\nString({a:1}.toString()) // \"[object Object]\"\n\n```\n\n如果toString方法和valueOf方法,返回的都不是原始类型的值,则String方法报错。\n```javascript\nvar T = {toString:function(){return true},valueOf:function(){return undefined}};\nString(T) // \"true\" 调用toString\n\nvar T = {toString:function(){return {}},valueOf:function(){return undefined}};\nString(T) // \"undefined\" 调用valueOf\n\nvar T = {toString:function(){return {}},valueOf:function(){return {}}};\nString(T) // Error Uncaught TypeError: Cannot convert object to primitive value(…)\n\n```\n\n下面是一个自定义toString方法的例子。\n\n```javascript\n\nString({toString:function(){return 3;}}) // \"3\"\n\nString({valueOf:function (){return 2;}}) // \"[object Object]\"\n\nString({valueOf:function (){return 2;},toString:function(){return 3;}}) // \"3\"\n```\n\n上面代码对三个对象使用String方法。第一个对象返回toString方法的值(数值3),然后对其使用String方法,得到字符串“3”;第二个对象返回的还是toString方法的值(\"[object Object]\"),这次直接就是字符串;第三个对象表示toString方法先于valueOf方法执行。\n\n\n> ** 转自:** (阮一峰)[http://javascript.ruanyifeng.com/grammar/string.html]","source":"_posts/好好学学String-2016-02-02.md","raw":"title: 好好学学String!\ndate: 2016-02-02 14:36:10\ntags:\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n## 概述\n\n### 定义\n\n字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。\n<!--more-->\n```javascript\n'abc'\n\"abc\"\n```\n\n单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号。\n\n```javascript\n'key=\"value\"'\n\"It's a long journey\"\n```\n\n上面两个都是合法的字符串。\n\n如果要在单引号字符串的内部,使用单引号(或者在双引号字符串的内部,使用双引号),就必须在内部的单引号(或者双引号)前面加上反斜杠,用来转义。\n\n```javascript\n'Did she say \\'Hello\\'?'\n// \"Did she say 'Hello'?\"\n\n\"Did she say \\\"Hello\\\"?\"\n// \"Did she say \"Hello\"?\"\n```\n\n字符串默认只能写在一行内,分成多行将会报错。\n\n```javascript\n'a\nb\nc'\n// SyntaxError: Unexpected token ILLEGAL\n```\n\n上面代码将一个字符串分成三行,JavaScript就会报错。\n\n多行字符串是正式规范(ECMA 265 5th edition)的一部分,ES5扩展了字符串字面量的语法。在(7.8.4 String Literals)中添加了`DoubleStringCharacter`和 `SingleStringCharacter`: `LineContinuation`。\nLineTerminatorSequence 指的是下面这些字符中的一个:\n* 换行符 `<LF>`\n* 回车符 `<CR>`\n* 行分割符 `<LS>`\n* 段分隔符 `<PS>`\n\nLineContinuation 的语法是:\n```javascript\n \\ LineTerminatorSequence\n```\n如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。\n\n```javascript\nvar longString = \"Long\\\\n\\\nlong \\\nlong \\\nstring\";\n\nlongString\n// \"Long\\nlong long string\"\n```\n\n上面代码表示,加了反斜杠以后,原来写在一行的字符串,可以分成多行,效果与写在同一行完全一样。注意,反斜杠的后面必须是`LineTerminatorSequence`中的一个上例中的是换行符,而不能有其他字符(比如空格),否则会报错。\n\n连接运算符(`+`)可以连接多个单行字符串,用来模拟多行字符串。\n\n```javascript\nvar longString = 'Long '\n + 'long '\n + 'long '\n + 'string';\n```\n\n另外,有一种利用多行注释,生成多行字符串的变通方法。\n\n```javascript\n(function () { /*\nline 1\nline 2\nline 3\n*/}).toString().split('\\n').slice(1,-1).join('\\n')\n// \"line 1 line 2 line 3\"\n\n### 转义\n\n反斜杠(`\\\\`)在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。\n\n需要用反斜杠转义的特殊字符,主要有下面这些:\n\n- `\\0` 代表没有内容的字符(\\u0000)\n- `\\b` 后退键(\\u0008)\n- `\\f` 换页符(\\u000C)\n- `\\n` 换行符(\\u000A)\n- `\\r` 回车键(\\u000D)\n- `\\t` 制表符(\\u0009)\n- `\\v` 垂直制表符(\\u000B)\n- `\\'` 单引号(\\u0027)\n- `\\\"` 双引号(\\u0022)\n- `\\\\\\\\` 反斜杠(\\u005C)\n- `\\XXX` 用三个八进制数(000到377)表示字符,`XXX`对应该字符的Unicode,比如`\\251`表示版权符号。\n- `\\xXX` 用两个十六进制数(00到FF)表示字符,`XX`对应该字符的Unicode,比如`\\xA9`表示版权符号。\n- `\\uXXXX` 用四位十六进制的Unicode编号代表某个字符,比如`\\u00A9`表示版权符号。\n\n下面是最后三种字符的特殊写法的例子。\n\n```javascript\n'\\251' // \"©\"\n'\\xA9' // \"©\"\n'\\u00A9' // \"©\"\n\n'\\172' === 'z' // true\n'\\x7A' === 'z' // true\n'\\u007A' === 'z' // true\n```\n\n如果非特殊字符前面使用反斜杠,则反斜杠会被省略。\n\n```javascript\n'\\a' // \"a\"\n```\n\n上面代码表示`a`是一个正常字符,前面加反斜杠没有特殊含义,则反斜杠会被自动省略。\n\n如果字符串的正常内容之中,需要包含反斜杠,则反斜杠前需要再加一个反斜杠,用来对自身转义。\n\n```javascript\n\"Prev \\\\ Next\" // \"Prev \\ Next\"\n```\n\n### 字符串与数组\n\n字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(从0开始)。\n\n```javascript\nvar s = 'hello';\n\ns[0] // \"h\"\ns[1] // \"e\"\ns[4] // \"o\"\n\n// 也可以直接对字符串使用方括号运算符\n'hello'[1] // \"e\"\n```\n\n如果方括号中的数字超过字符串的范围,或者方括号中根本不是数字,则返回`undefined`。\n\n```javascript\n'abc'[3] // undefined\n'abc'[-1] // undefined\n'abc'['x'] // undefined\n```\n\n但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。\n\n```javascript\nvar s = 'hello';\n\ndelete s[0];\ns // \"hello\"\n\ns[1] = 'a';\ns // \"hello\"\n\ns[5] = '!';\ns // \"hello\"\n```\n\n上面代码表示,字符串内部的单个字符无法改变和增删,这些操作会默默地失败。\n\n字符串也无法添加新属性。\n\n```javascript\nvar s = 'Hello World';\ns.x = 123;\ns.x // undefined\n```\n\n上面代码为字符串`s`添加了一个`x`属性,结果无效,总是返回`undefined`。\n\n上面这些行为的原因是,在JavaScript内部,变量`s`其实指向字符串`Hello World`的地址,而`Hello World`本身是一个常量,所以无法改变它,既不能新增,也不能删除。另一方面,当一个字符串被调用属性时,它会自动转为String对象的实例(参见《标准库》一章),调用结束后,该对象自动销毁。这意味着,下一次调用字符串的属性时,实际是调用一个临时生成的新对象,而不是上一次调用时生成的那个对象,所以取不到赋值在上一个对象的属性。如果想要为字符串添加属性,只有在它的原型对象`String.prototype`上定义(参见《面向对象编程》一章)。\n\n### length属性\n\n`length`属性返回字符串的长度,该属性也是无法改变的。\n\n```javascript\nvar s = 'hello';\ns.length // 5\n\ns.length = 3;\ns.length // 5\n\ns.length = 7;\ns.length // 5\n```\n\n上面代码表示字符串的`length`属性无法改变,但是不会报错。\n\n## 字符集\n\nJavaScript使用Unicode字符集,也就是说在JavaScript内部,所有字符都用Unicode表示。\n\n不仅JavaScript内部使用Unicode储存字符,而且还可以直接在程序中使用Unicode,所有字符都可以写成\"\\uxxxx\"的形式,其中xxxx代表该字符的Unicode编码。比如,`\\u00A9`代表版权符号。\n\n```javascript\nvar s = '\\u00A9';\ns // \"©\"\n```\n\n每个字符在JavaScript内部都是以16位(即2个字节)的UTF-16格式储存。也就是说,JavaScript的单位字符长度固定为16位长度,即2个字节。\n\n但是,UTF-16有两种长度:对于`U+0000`到`U+FFFF`之间的字符,长度为16位(即2个字节);对于`U+10000`到`U+10FFFF`之间的字符,长度为32位(即4个字节),而且前两个字节在`0xD800`到`0xDBFF`之间,后两个字节在`0xDC00`到`0xDFFF`之间。举例来说,`U+1D306`对应的字符为𝌆,它写成UTF-16就是`0xD834 0xDF06`。浏览器会正确将这四个字节识别为一个字符,但是JavaScript内部的字符长度总是固定为16位,会把这四个字节视为两个字符。\n\n```javascript\nvar s = '\\uD834\\uDF06';\n\ns // \"𝌆\"\ns.length // 2\n/^.$/.test(s) // false\ns.charAt(0) // \"\"\ns.charAt(1) // \"\"\ns.charCodeAt(0) // 55348\ns.charCodeAt(1) // 57094\n```\n\n上面代码说明,对于于`U+10000`到`U+10FFFF`之间的字符,JavaScript总是视为两个字符(字符的`length`属性为2),用来匹配单个字符的正则表达式会失败(JavaScript认为这里不止一个字符),`charAt`方法无法返回单个字符,`charCodeAt`方法返回每个字节对应的十进制值。\n\n所以处理的时候,必须把这一点考虑在内。对于4个字节的Unicode字符,假定`C`是字符的Unicode编号,`H`是前两个字节,`L`是后两个字节,则它们之间的换算关系如下。\n\n```javascript\n// 将大于U+FFFF的字符,从Unicode转为UTF-16\nH = Math.floor((C - 0x10000) / 0x400) + 0xD800\nL = (C - 0x10000) % 0x400 + 0xDC00\n\n// 将大于U+FFFF的字符,从UTF-16转为Unicode\nC = (H - 0xD800) * 0x400 + L - 0xDC00 + 0x10000\n```\n\n下面的正则表达式可以识别所有UTF-16字符。\n\n```javascript\n([\\0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF])\n```\n\n由于JavaScript引擎(严格说是ES5规格)不能自动识别辅助平面(编号大于0xFFFF)的Unicode字符,导致所有字符串处理函数遇到这类字符,都会产生错误的结果(详见《标准库》一章的`String`对象章节)。如果要完成字符串相关操作,就必须判断字符是否落在`0xD800`到`0xDFFF`这个区间。\n\n下面是能够正确处理字符串遍历的函数。\n\n```javascript\nfunction getSymbols(string) {\n var length = string.length;\n var index = -1;\n var output = [];\n var character;\n var charCode;\n while (++index < length) {\n character = string.charAt(index);\n charCode = character.charCodeAt(0);\n if (charCode >= 0xD800 && charCode <= 0xDBFF) {\n output.push(character + string.charAt(++index));\n } else {\n output.push(character);\n }\n }\n return output;\n}\n\nvar symbols = getSymbols('𝌆');\n\nsymbols.forEach(function(symbol) {\n // ...\n});\n```\n\n替换(`String.prototype.replace`)、截取子字符串(`String.prototype.substring`, `String.prototype.slice`)等其他字符串操作,都必须做类似的处理。\n\n## Base64转码\n\nBase64是一种编码方法,可以将任意字符转成可打印字符。使用这种编码方法,主要不是为了加密,而是为了不出现特殊字符,简化程序的处理。\n\nJavaScript原生提供两个Base64相关方法。\n\n- btoa():字符串或二进制值转为Base64编码\n- atob():Base64编码转为原来的编码\n\n```javascript\nvar string = 'Hello World!';\nbtoa(string) // \"SGVsbG8gV29ybGQh\"\natob('SGVsbG8gV29ybGQh') // \"Hello World!\"\n```\n\n这两个方法不适合非ASCII码的字符,会报错。\n\n```javascript\nbtoa('你好')\n// Uncaught DOMException: The string to be encoded contains characters outside of the Latin1 range.\n```\n\n要将非ASCII码字符转为Base64编码,必须中间插入一个转码环节,再使用这两个方法。\n\n```javascript\nfunction b64Encode( str ) {\n return btoa(unescape(encodeURIComponent( str )));\n}\n\nfunction b64Decode( str ) {\n return decodeURIComponent(escape(atob( str )));\n}\n\nb64Encode('你好') // \"5L2g5aW9\"\nb64Decode('5L2g5aW9') // \"你好\"\n```\n## 转换为字符串 String()\n\n### toString()\n数值、布尔值、对象和字符串值(没错,每个字符串也都有一个`toString()`方法,该方法返回字符串的一个副本)都有`toString()`方法。但`null` 和 `undefined` 值没有这个方法。\n多数情况下,调用`toString()`方法不必传递参数。但是,在调用数值的`toString()`方法时,可以传递一个参数:输出数值的基数。默认情况下,`toString()`方法以十进制格式返回数值的字符串表示。而通过传递基数,`toString()`可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值。\n在不知道要转换的值是不是`null` 或 `undefined` 的情况下,还可以使用转型函数`String()`。\n### String()\n\n使用String函数,可以将任意类型的值转化成字符串。规则如下:\n\n1. 如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果。\n2. 如果值是null,则返回\"null\"。\n3. 如果值是undefined,则返回\"undefined\"。\n\n**(1)原始类型值的转换规则**\n\n- **数值**:调用Number的toString()方法,转换后还是原来的值。转为相应的字符串。\n\n- **字符串**:调用String的toString()方法,转换后还是原来的值。\n\n- **布尔值**:调用Boolean的toString()方法,true转为“true”,false转为“false”。\n\n- **undefined**:转为“undefined”。\n\n- **null**:转为“null”。\n```javascript\nString(123) // \"123\"\n\nString(\"abc\") // \"abc\"\n\nString(true) // \"true\"\n\nString(undefined) // \"undefined\"\n\nString(null) // \"null\"\n\nBoolean.prototype.toString = function(){\n console.log(\"wo bei diaoyongle\");\n}\nvar a = new Boolean(true);\nString(a) // \"wo bei diaoyongle\" \"undefined\"\n\n```\n**(2)对象的转换规则**\n\n如果要将对象转为字符串,则是采用以下步骤。\n\n1. 先调用`toString`方法,如果toString方法返回的是简单类型(`Boolean`、`number`、`string`、`null`、`undefined`)的值,则对该值使用`String`方法,不再进行以下步骤。\n\n2. 如果`toString`方法返回的是复合类型的值,再调用`valueOf`方法,如果`valueOf`方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤。\n\n3. 如果`valueOf`方法返回的是复合类型的值,则报错。\n\nString方法的这种过程正好与Number方法相反。\n\n```javascript\n\nString({a:1}) // \"[object Object]\"\n```\n\n上面代码相当于下面这样。\n\n```javascript\n\nString({a:1}.toString()) // \"[object Object]\"\n\n```\n\n如果toString方法和valueOf方法,返回的都不是原始类型的值,则String方法报错。\n```javascript\nvar T = {toString:function(){return true},valueOf:function(){return undefined}};\nString(T) // \"true\" 调用toString\n\nvar T = {toString:function(){return {}},valueOf:function(){return undefined}};\nString(T) // \"undefined\" 调用valueOf\n\nvar T = {toString:function(){return {}},valueOf:function(){return {}}};\nString(T) // Error Uncaught TypeError: Cannot convert object to primitive value(…)\n\n```\n\n下面是一个自定义toString方法的例子。\n\n```javascript\n\nString({toString:function(){return 3;}}) // \"3\"\n\nString({valueOf:function (){return 2;}}) // \"[object Object]\"\n\nString({valueOf:function (){return 2;},toString:function(){return 3;}}) // \"3\"\n```\n\n上面代码对三个对象使用String方法。第一个对象返回toString方法的值(数值3),然后对其使用String方法,得到字符串“3”;第二个对象返回的还是toString方法的值(\"[object Object]\"),这次直接就是字符串;第三个对象表示toString方法先于valueOf方法执行。\n\n\n> ** 转自:** (阮一峰)[http://javascript.ruanyifeng.com/grammar/string.html]","slug":"好好学学String","published":1,"updated":"2016-02-03T08:10:14.214Z","layout":"post","photos":[],"link":"","_id":"citf9om0n000xskv73jk3i19w"},{"title":"好好学学Object!","date":"2016-02-03T08:09:30.000Z","comments":1,"_content":"## 概述\n\n### 定义方法\n\n对象(object)是JavaScript的核心概念,也是最重要的数据类型。JavaScript的所有数据都可以被视为对象。\n简单说,所谓对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。\n<!--more-->\n\n```javascript\nvar o = {\n 'p': 'Hello World'\n};\n```\n\n上面代码中,大括号就定义了一个对象,它被赋值给变量`o`。这个对象内部包含一个键值对(又称为“成员”),`p`是“键名”(成员的名称),字符串“Hello World”是“键值”(成员的值)。键名与键值之间用冒号分隔。如果对象内部包含多个键值对,每个键值对之间用逗号分隔。\n\n### 键名\n\n对象的所有键名都是字符串,所以加不加引号都可以。上面的代码也可以写成下面这样。\n\n```javascript\nvar o = {\n p: 'Hello World'\n};\n```\n\n但是,如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),也不是数字,则必须加上引号。\n\n```javascript\nvar o = {\n '1p': \"Hello World\",\n 'h w': \"Hello World\",\n 'p+q': \"Hello World\"\n};\n```\n\n上面对象的三个键名,都不符合标识名的条件,所以必须加上引号。\n\n注意,JavaScript的保留字可以不加引号当作键名。\n\n```javascript\nvar obj = {\n for: 1,\n class: 2\n};\n```\n\n如果键名是数字,则会默认转为对应的字符串。\n\n```javascript\nvar obj = {\n 1e2: true,\n 1e-2: true,\n .234: true,\n 0xFF: true,\n};\n\n//Obj =\n// {\n// 100: true,\n// 255: true,\n// 0.01: true,\n// 0.234: true\n// }\n```\n\n上面代码表示,如果键名为数值,则会先转为标准形式的数值,然后再转为字符串。\n\n### 属性\n\n对象的每一个“键名”又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。\n\n```javascript\nvar o = {\n p: function(x) {\n return 2 * x;\n }\n};\n\no.p(1)\n// 2\n```\n\n上面的对象就有一个方法`p`,它就是一个函数。\n\n对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。\n\n```javascript\nvar o = {\n p: 123,\n m: function () { ... },//这个逗号不能加\n}\n```\n\n上面的代码中m属性后面的那个逗号,有或没有都不算错。但是,ECMAScript 3不允许添加逗号,所以如果要兼容老式浏览器(比如IE 8),那就不能加这个逗号。\n\n### 生成方法\n\n对象的生成方法,通常有三种方法。除了像上面那样直接使用大括号生成(`{}`),还可以用`new`命令生成一个Object对象的实例,或者使用`Object.create`方法生成。\n\n```javascript\nvar o1 = {};\nvar o2 = new Object();// 可以简写为 var o2 = new Object; 但是不推荐\nvar o3 = Object.create(null);\n```\n\n上面三行语句是等价的。一般来说,第一种采用大括号的写法比较简洁,第二种采用构造函数的写法清晰地表示了意图,第三种写法一般用在需要对象继承的场合。\n\n### 读写属性\n\n**(1)读取属性**\n\n读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。\n\n```javascript\nvar o = {\n p: 'Hello World'\n};\n\no.p // \"Hello World\"\no['p'] // \"Hello World\"\n```\n\n上面代码分别采用点运算符和方括号运算符,读取属性`p`。\n\n请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。但是,数字键可以不加引号,因为会被当作字符串处理。\n\n```javascript\nvar o = {\n 0.7: \"Hello World\"\n};\n\no.['0.7'] // \"Hello World\"\no[0.7] // \"Hello World\"\n```\n\n方括号运算符内部可以使用表达式。\n\n```javascript\no['hello' + ' world']\no[3 + 3]\n```\n\n数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。\n\n```javascript\nobj.0xFF\n// SyntaxError: Unexpected token\nobj[0xFF]\n// true\n```\n\n上面代码的第一个表达式,对数值键名0xFF使用点运算符,结果报错。第二个表达式使用方括号运算符,结果就是正确的。\n\n**(2)检查变量是否声明**\n\n如果读取一个不存在的键,会返回undefined,而不是报错。可以利用这一点,来检查一个变量是否被声明。\n\n```javascript\n// 检查a变量是否被声明\n\nif(a) {...} // 报错\n\nif(window.a) {...} // 不报错\nif(window['a']) {...} // 不报错\n```\n\n上面的后两种写法之所以不报错,是因为在浏览器环境,所有全局变量都是window对象的属性。`window.`的含义就是读取window对象的a属性,如果该属性不存在,就返回undefined,并不会报错。需要注意的是,后两种写法有漏洞,如果a属性值是一个空字符串(或其对应的布尔值为false的情况),则无法起到检查变量是否声明的作用。\n正确的写法是使用`in`运算符,或者使用Object原型的方法`hasOwnProperty()`也就是Object原型。\n\n```javascript\nif('a' in window) {\n ...\n}\n\nif(window.hasOwnProperty(\"a\")) {\n ...\n}\n```\n\n**(3)写入属性**\n\n点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。\n\n```javascript\no.p = 'abc';\no['p'] = 'abc';\n```\n\n上面代码分别使用点运算符和方括号运算符,对属性p赋值。\n\nJavaScript允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。\n\n```javascript\nvar o = { p: 1 };\n\n// 等价于\n\nvar o = {};\no.p = 1;\n```\n\n**(4)查看所有属性**\n\n`Object.keys()` 方法会返回一个由给定对象的所有**可枚举自身属性的属性名组成的数组**,数组中属性名的排列顺序和使用`for-in`循环遍历该对象时返回的顺序一致(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。\n支持:`IE9+`(包括IE9)。\n\n```javascript\nvar o = {\n key1: 1,\n key2: 2\n};\n\nObject.keys(o);\n// ['key1', 'key2']\n```\n兼容处理方案:\n```javascirpt\nif (!Object.keys) {\n Object.keys = function(o) {\n if (o !== Object(o)) {\n throw new TypeError('Object.keys called on a non-object');\n }\n var k = [],\n p;\n for (p in o) {\n if (Object.prototype.hasOwnProperty.call(o, p)) {\n k.push(p);\n }\n }\n return k;\n }\n}\n```\n\n### 属性的删除\n\n删除一个属性,需要使用`delete`命令。\n\n```javascript\nvar o = {p: 1};\nObject.keys(o) // [\"p\"]\n\ndelete o.p // true\no.p // undefined\nObject.keys(o) // []\n```\n\n上面代码表示,一旦使用`delete`命令删除某个属性,再读取该属性就会返回`undefined`,而且`Object.keys`方法返回的该对象的所有属性中,也将不再包括该属性。\n\n<span style=\"color: red;\">麻烦的是,如果删除一个不存在的属性,delete不报错,而且返回true。</span>\n\n```javascript\nvar o = {};\ndelete o.p // true\n```\n\n上面代码表示,delete命令只能用来保证某个属性的值为undefined,而无法保证该属性是否真的存在。\n\n只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。\n\n```javascript\n\nvar o = Object.defineProperty({}, \"p\", {\n value: 123,\n configurable: false\n});\n\no.p // 123\ndelete o.p // false\n\n```\n\n上面代码之中,o对象的p属性是不能删除的,所以delete命令返回false(关于Object.defineProperty方法的介绍,请看《标准库》一章的Object对象章节)。\n\n另外,需要注意的是,delete命令只能删除对象本身的属性,不能删除继承的属性(关于继承参见《面向对象编程》一节)。delete命令也不能删除var命令声明的变量,只能用来删除属性。\n\n#### 以下这些都不能delete\n* ```javascript\n function func() {\n console.log(delete arguments);//false\n console.log(Object.getOwnPropertyDescriptor(func,\"arguments\"));//{writable: false, enumerable: false, configurable: false}\n }\n func(1);\n ```\n* var 声明的变量。\n* ```javascript\n function func() {\n\n }\n delete func//false\n Object.getOwnPropertyDescriptor(window,\"func\"); // {writable: true, enumerable: true, configurable: false}\n ```\n* delete不能删除对象继承来自原型上的属性\n总结下:\n 1. 内置对象的属性及方法多数不能delete,保护该语言最核心API,这些API被delete了,基本上就废了。如delete Object.prototype。\n 2. 对象继承于原型的属性和方法不能delete是出于保护原型,否则 “类A的对象delete了原型上的属性,那么继承于A的都将丢失该属性”\n\n### 对象的引用\n\n如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。\n\n```javascript\nvar o1 = {};\nvar o2 = o1;\n\no1.a = 1;\no2.a // 1\n\no2.b = 2;\no1.b // 2\n```\n\n上面代码中,`o1`和`o2`指向同一个对象,因此为其中任何一个变量添加属性,另一个变量都可以读写该属性。\n\n此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。\n\n```javascript\nvar o1 = {};\nvar o2 = o1;\n\no1 = 1;\no2 // {}\n```\n\n上面代码中,`o1`和`o2`指向同一个对象,然后`o1`的值变为1,这时不会对`o2`产生影响,`o2`还是指向原来的那个对象。\n\n但是,这种引用只局限于对象,对于原始类型的数据则是传值引用,也就是说,都是值的拷贝。\n\n```javascript\nvar x = 1;\nvar y = x;\n\nx = 2;\ny // 1\n```\n\n上面的代码中,当`x`的值发生变化后,`y`的值并不变,这就表示`y`和`x`并不是指向同一个内存地址。\n\n### `in`运算符\n\n1. in操作符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回`true`,否则返回`false`。\n\n```javascript\nvar o = { p: 1 };\n'p' in o // true\n```\n\n2. 在JavaScript语言中,所有全局变量都是顶层对象(浏览器的顶层对象就是`window`对象)的属性,因此可以用`in`运算符判断,一个全局变量是否存在。\n\n```javascript\n// 假设变量x未定义\n\n// 写法一:报错\nif (x) { return 1; }\n\n// 写法二:不正确\nif (window.x) { return 1; }\n\n// 写法三:正确\nif ('x' in window) { return 1; }\nif ( x in window) { return 1; } // 注意x要用引号包括起来,是字符串\n\n// 写法四: 正确\nif (window.hasOwnProperty('x')) { return 1; } // 注意x要用引号包括起来,是字符串\n```\n\n上面三种写法之中,如果`x`不存在,第一种写法会报错;如果`x`的值对应布尔值`false`(比如`x`等于空字符串),第二种写法无法得到正确结果;只有第三种、第四种写法,才能正确判断变量`x`是否存在。\n\n<span style=\"color: red;\">`in`运算符的一个问题是,它不能区分对象继承的属性。</span>\n\n```javascript\nvar o = new Object();\no.hasOwnProperty('toString') // false\n\n'toString' in o // true\n```\n\n上面代码中,`toString`方法不是对象`o`自身的属性,而是继承的属性,`hasOwnProperty`方法可以说明这一点。但是,`in`运算符不能区分,对继承的属性也返回`true`。\n3. in的右边必须是一个对象\n```javascript\nvar color1 = new String(\"green\");\n\"length\" in color1 // return true\nvar color2 = \"coral\";\n\"length\" in color2 // Uncaught TypeError: Cannot use 'in' operator to search for 'length' in coral(…)\n```\n4. 检验数组指定角标是否越界。\n```javascript\nvar trees = new Array(\"redwood\", \"bay\", \"cedar\", \"oak\", \"maple\");\n0 in trees // returns true\n3 in trees // returns true\n6 in trees // returns false\n\"bay\" in trees // returns false (you must specify the index number, not the value at that index)\n\"length\" in trees // returns true (length is an Array property)\n```\n5. 如果你使用`delete`操作符删除了一个属性,再次用in检查时,会返回false,如:\n```javascript\nvar mycar = {make: \"Honda\", model: \"Accord\", year: 1998};\nmycar.make = undefined;\n\"make\" in mycar; // return true\ndelete mycar.Accord;\n\"Accord\" in mycar; // return false\n \n\nvar trees = new Array(\"redwood\", \"bay\", \"cedar\", \"oak\", \"maple\");\ntrees[3] = undefined;\n3 in trees; // return true\ndelete trees[2];\n2 in trees; // return false\n```\n6. 如果一个属性是从原型链上继承来的,in 运算符也会返回 true。\n```javascript\n\"toString\" in {}; // 返回true\n```\n### for...in循环\nfor...in 循环不遍历不可枚举属性。使用内建构造器例如 Array 和 Object 创建的对象拥有从 Object.prototype 和 String.prototype 继承的不可枚举属性,例如 String 的 indexOf() 方法或者 Object 的 toString 方法。循环将迭代对象的所有可枚举属性,包括从它的构造函数的 prototype 继承而来的(包括被覆盖的内建属性)。\n** 删除,添加或者修改属性 **\nfor...in 循环以任意序迭代一个对象的属性。通常,在迭代过程中最好不要在对象上进行添加、修改或者删除属性的操作,除非是对当前正在被访问的属性。这里并不保证是否一个被添加的属性在迭代过程中会被访问到,不保证一个修改后的属性(除非是正在被访问的)会在修改前或者修改后被访问,不保证一个被删除的属性将会在它被删除之前被访问。\n\n** Array 迭代和 for...in **\n数组索引仅是可枚举的整数名,其他方面和别的普通对象属性没有什么区别。for...in 并不能够保证返回的是按一定顺序的索引,但是它会返回所有可枚举属性,包括非整数名称的和继承的。因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素。 因此当迭代那些访问次序重要的 arrays 时用整数索引去进行 for 循环 (或者使用 Array.prototype.forEach() 或 for...of 循环) 。\n\n** 仅迭代自身的属性 **\n如果你只要考虑对象本身的属性,而不是它的原型,那么使用 `getOwnPropertyNames()`(自身的可枚举和不可枚举属性都能获得) 或执行 `hasOwnProperty()` 来确定某属性是否是对象本身的属性 (也能使用`propertyIsEnumerable`)。另外,如果你知道外部不存在任何的干扰代码,你可以扩展内置原型与检查方法。\n```javascript\nvar o = {a: 1, b: 2, c: 3};\n\nfor (i in o){\n console.log(o[i]);\n}\n// 1\n// 2\n// 3\n```\n\n下面是一个使用`for...in`循环,进行数组赋值的例子。\n\n```javascript\nvar props = [], i = 0;\n\nfor (props[i++] in {x: 1, y: 2});//循环执行的时候会给每一个变量(`props[i++]`)赋`{x: 1, y: 2}`的属性值。\n\nprops // ['x', 'y']\n```\n\n注意,`for...in`循环遍历的是对象所有可`enumberable`的属性(属性特性`[[enumberable]]`为`true`),其中不仅包括定义在对象本身的属性,还包括对象继承的属性。\n> 属性特性 `enumerable` 定义了对象的属性是否可以在 `for...in` 循环和 `Object.keys()` 中被枚举。\n```javascript\n// name 是 Person 本身的属性\nfunction Person(name) {\n this.name = name;\n}\n\n// describe是Person.prototype的属性\nPerson.prototype.describe = function () {\n return 'Name: '+this.name;\n};\n\nvar person = new Person('Jane');\n\n// for...in循环会遍历实例自身的属性(name),\n// 以及继承的属性(describe)\nfor (var key in person) {\n console.log(key);\n}\n// name\n// describe\n```\n\n上面代码中,`name`是对象本身的属性,`describe`是对象继承的属性,`for...in`循环的遍历会包括这两者。\n\n如果只想遍历对象本身的属性,可以使用hasOwnProperty方法,在循环内部做一个判断。\n\n```javascript\nfor (var key in person) {\n if (person.hasOwnProperty(key)) {\n console.log(key);\n }\n}\n// name\n```\n\n为了避免这一点,可以新建一个继承`null`的对象。由于`null`没有任何属性,所以新对象也就不会有继承的属性了。\n\n## with语句\n\nwith语句的格式如下:\n\n```javascript\n\nwith (object)\n statement\n\n```\n\n它的作用是操作同一个对象的多个属性时,提供一些书写的方便。\n\n```javascript\n\n// 例一\nwith (o) {\n p1 = 1;\n p2 = 2;\n}\n\n// 等同于\n\no.p1 = 1;\no.p2 = 2;\n\n// 例二\nwith (document.links[0]){\n console.log(href);\n console.log(title);\n console.log(style);\n}\n\n// 等同于\n\nconsole.log(document.links[0].href);\nconsole.log(document.links[0].title);\nconsole.log(document.links[0].style);\n\n```\n\n注意,with区块内部的变量,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。这是因为with区块没有改变作用域,它的内部依然是当前作用域。\n\n```javascript\n\nvar o = {};\n\nwith (o){\n x = \"abc\";\n}\n\no.x\n// undefined\n\nx\n// \"abc\"\n\n```\n\n上面代码中,对象o没有属性x,所以with区块内部对x的操作,等于创造了一个全局变量x。正确的写法应该是,先定义对象o的属性x,然后在with区块内操作它。\n\n```javascript\n\nvar o = {};\n\no.x = 1;\n\nwith (o){\n x = 2;\n}\n\no.x\n// 2\n\n```\n\n这是with语句的一个很大的弊病,就是绑定对象不明确。\n\n```javascript\n\nwith (o) {\n console.log(x);\n}\n\n```\n\n单纯从上面的代码块,根本无法判断x到底是全局变量,还是o对象的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用with语句,可以考虑用一个临时变量代替with。\n\n```javascript\n\nwith(o1.o2.o3) {\n console.log(p1 + p2);\n}\n\n// 可以写成\n\nvar temp = o1.o2.o3;\nconsole.log(temp.p1 + temp.p2);\n\n```\n\nwith语句少数有用场合之一,就是替换模板变量。\n\n```javascript\nvar str = 'Hello <%= name %>!';\n```\n\n上面代码是一个模板字符串,为了替换其中的变量name,可以先将其分解成三部分`'Hello ', name, '!'`,然后进行模板变量替换。\n\n```javascript\n\nvar o = {\n name: 'Alice'\n};\n\nvar p = [];\nvar tmpl = '';\n\nwith(o){\n p.push('Hello ', name, '!');\n};\n\np.join('') // \"Hello Alice!\"\n```\n\n上面代码中,with区块内部,模板变量name可以被对象o的属性替换,而p依然是全局变量。事实上,这就是很多模板引擎的实现原理。\n\n## Object\n\n### 概述\n\nJavaScript原生提供一个Object对象(注意起首的O是大写),所有其他对象都继承自这个对象。Object本身也是一个构造函数,可以直接通过它来生成新对象。\nObject 的每个实例也就是Object原型 prototype 具有下列属性和方法:\n\n* `constructor`:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(`constructor`)就是Object()。\n* `hasOwnProperty(propertyName)`:用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如:o.hasOwnProperty(\"name\"))。\n* `isPrototypeOf(object)`:用于检查传入的对象是否是传入对象的原型(第5章将讨论原型)。\n* `propertyIsEnumerable(propertyName)`:用于检查给定的属性是否能够使用for-in 语句(本章后面将会讨论)来枚举。与hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定。\n `propertyIsEnumerable()`方法的返回值为Boolean类型,该值指示指定属性是否为对象的一部分以及该属性是否是可枚举的(只有这两个条件同时满足才返回`true`)。如果propertyName存在于object中且可以使用for...in循环遍历出来,则`propertyIsEnumerable()`方法将返回`true`。如果object不具有所指定名称的属性或者所指定的属性不是可枚举的,则`propertyIsEnumerable()`方法将返回`false`。\n 通常,预定义的属性不是可枚举的,而用户定义的属性总是可枚举的。不过有些属性虽然可以通过`for...in`循环遍历到,但因为它们不是自身属性,而是从原型链上继承的属性,所以该方法也会返回`false`。\n* `toLocaleString()`:返回对象的字符串表示,该字符串与执行环境的地区对应。\n* `toString()`:返回对象的字符串表示。\n* `valueOf()`:返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。\n* `__defineGetter__()`\n* `__defineSetter__()`\n* `__lookupGetter__()`\n* `__lookupSetter__()`\n* `get __proto__()` //不可被显示调用(`.`出来)\n* `set __proto__()` //不可被显示调用(`.`出来)\n\n还有一些其他方法据各个浏览器厂商的实现来定,比如firefox还有`toSource()`、`watch()`、 `unwatch()`,这些内容不做过多介绍,毕竟不是标准。后面将会对这些方法做详细介绍。\n`Object`作为构造函数使用时,可以接受一个参数。如果该参数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象。\n```\nvar o1 = {a: 1};\nvar o2 = new Object(o1);\no1 === o2 // true\n\nnew Object(123) instanceof Number\n// true\n```\n> 注意,通过`new Object()`的写法生成新对象,与字面量的写法`o = {}`是等价的,但是通过对象字面量定义对象时,实际上不会调用Object 构造函数。\n\n与其他构造函数一样,如果要在Object对象上面部署一个方法,有两种做法:\n1. 部署在Object对象本身\n比如,在Object对象上面定义一个print方法,显示其他对象的内容。\n```\nObject.print = function(o){ console.log(o) };\n\nvar o = new Object();\n\nObject.print(o)\n// Object\n```\n2. 部署在Object.prototype对象\n所有构造函数都有一个prototype属性,指向一个原型对象。凡是定义在Object.prototype对象上面的属性和方法,将被所有实例对象共享。\n```\nObject.prototype.print = function(){ console.log(this)};\n\nvar o = new Object();\n\no.print() // Object\n```\n上面代码在`Object.prototype`定义了一个print方法,然后生成一个`Object`的实例o。o直接继承了`Object.prototype`的属性和方法,可以在自身调用它们,也就是说,`o`对象的print方法实质上是调用`Object.prototype.print`方法。\n可以看到,尽管上面两种写法的print方法功能相同,但是用法是不一样的,因此必须区分“构造函数的方法”和“实例对象的方法”。\n\n### Object()\n\nObject本身当作工具方法使用时,可以将任意值转为对象。这个方法常用于保证某个值一定是对象。如果参数是原始类型的值,Object方法返回对应的包装对象的实例。\n```\nObject() // 返回一个空对象\nObject() instanceof Object // true\n\nObject(undefined) // 返回一个空对象\nObject(undefined) instanceof Object // true\n\nObject(null) // 返回一个空对象\nObject(null) instanceof Object // true\n\nObject(1) // 等同于 new Number(1)\nObject(1) instanceof Object // true\nObject(1) instanceof Number // true\n\nObject('foo') // 等同于 new String('foo')\nObject('foo') instanceof Object // true\nObject('foo') instanceof String // true\n\nObject(true) // 等同于 new Boolean(true)\nObject(true) instanceof Object // true\nObject(true) instanceof Boolean // true\n```\n如果Object方法的参数是一个对象,它总是返回原对象。\n```\nvar arr = [];\nObject(arr) // 返回原数组\nObject(arr) === arr // true\n\nvar obj = {};\nObject(obj) // 返回原对象\nObject(obj) === obj // true\n\nvar fn = function () {};\nObject(fn) // 返回原函数\nObject(fn) === fn // true\n```\n利用这一点,可以写一个判断变量是否为对象的函数。\n```\nfunction isObject(value) {\n return value === Object(value);\n}\n\nisObject([]) // true\nisObject(true) // false\n```\n### Object 对象的静态方法\n所谓“静态方法”,是指部署在Object对象自身的方法。\n#### Object.keys(),Object.getOwnPropertyNames()\n`Object.keys`方法和`Object.getOwnPropertyNames`方法很相似,一般用来遍历对象的属性。它们的参数都是一个对象,都返回一个数组,该数组的成员都是对象自身的(不包含`prototype`)所有属性名。它们的区别在于,`Object.keys`方法只返回可枚举的属性(关于可枚举性的详细解释见后文),`Object.getOwnPropertyNames`方法还返回不可枚举的属性名。\n上面的代码表示,对于一般的对象来说,这两个方法返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。\n```\nvar a = [\"Hello\", \"World\"];\n\nObject.keys(a)\n// [\"0\", \"1\"]\n\nObject.getOwnPropertyNames(a)\n// [\"0\", \"1\", \"length\"]\n```\n上面代码中,数组的`length`属性是不可枚举的属性,所以只出现在`Object.getOwnPropertyNames`方法的返回结果中。\n由于JavaScript没有提供计算对象属性个数的方法,所以可以用这两个方法代替。\n```\nObject.keys(o).length\nObject.getOwnPropertyNames(o).length\n```\n一般情况下,几乎总是使用Object.keys方法,遍历数组的属性。\n\n#### 其他方法\n除了上面提到的方法,Object还有不少其他方法,将在后文逐一详细介绍。\n1. 对象属性模型的相关方法\n ```\n Object.getOwnPropertyDescriptor():获取某个属性的attributes对象。\n Object.defineProperty():通过attributes对象,定义某个属性。\n Object.defineProperties():通过attributes对象,定义多个属性。\n Object.getOwnPropertyNames():返回直接定义在某个对象上面的全部属性的名称。\n ```\n2. 控制对象状态的方法\n ```\n Object.preventExtensions():防止对象扩展。\n Object.isExtensible():判断对象是否可扩展。\n Object.seal():禁止对象配置。\n Object.isSealed():判断一个对象是否可配置。\n Object.freeze():冻结一个对象。\n Object.isFrozen():判断一个对象是否被冻结。\n ```\n3. 原型链相关方法\n ```\n Object.create():创建一个拥有指定原型和若干个指定属性的对象。\n Object.getPrototypeOf():获取对象的Prototype对象。\n ```\n### Object对象的实例方法\n除了`Object`对象本身的方法,还有不少方法是部署在`Object.prototype`对象上的,所有`Object`的实例对象都继承了这些方法。\nObject实例对象的方法,主要有以下六个。\n 1. valueOf():返回当前对象对应的值。\n 2. toString():返回当前对象对应的字符串形式。\n 3. toLocaleString():返回当前对象对应的本地字符串形式。\n 4. hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。\n 5. isPrototypeOf():判断当前对象是否为另一个对象的原型。\n 6. propertyIsEnumerable():判断某个属性是否可枚举。\n#### Object.prototype.valueOf()\n`valueOf`方法的作用是返回一个对象的“值”,默认情况下返回对象本身。\n```\nvar o = new Object();\no.valueOf() === o // true\n```\n上面代码比较`o.valueOf()`与`o`本身,两者是一样的。`valueOf`方法的主要用途是,JavaScript自动类型转换时会默认调用这个方法。\n```\nvar o = new Object();\n1 + o // \"1[object Object]\"\n```\n上面代码将对象`o`与数字1相加,这时JavaScript就会默认调用`valueOf()`方法。所以,如果自定义`valueOf`方法,就可以得到想要的结果。\n```\nvar o = new Object();\no.valueOf = function (){\n return 2;\n};\n\n1 + o // 3\n```\n上面代码自定义了`o`对象的`valueOf`方法,于是`1 + o`就得到了`3`。这种方法就相当于用`o.valueOf`覆盖`Object.prototype.valueOf`。\n\n#### Object.prototype.toString()\n`toString`方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。\n```\nvar o1 = new Object();\no1.toString() // \"[object Object]\"\n\nvar o2 = {a:1};\no2.toString() // \"[object Object]\"\n```\n上面代码表示,对于一个对象调用`toString`方法,会返回字符串`[object Object]`,该字符串说明对象的类型。字符串`[object Object]`本身没有太大的用处,但是通过自定义`toString`方法,可以让对象在自动类型转换时,得到想要的字符串形式。\n```\nvar o = new Object();\n\no.toString = function () {\n return 'hello';\n};\n\no + ' ' + 'world' // \"hello world\"\n```\n上面代码表示,当对象用于字符串加法时,会自动调用`toString`方法。由于自定义了`toString`方法,所以返回字符串hello world。\n数组、字符串、函数、Date对象都分别部署了自己版本的`toString`方法,覆盖了`Object.prototype.toString`方法。\n```\n[1, 2, 3].toString() // \"1,2,3\"\n\n'123'.toString() // \"123\"\n\n(function () {\n return 123;\n}).toString()\n// \"function () {\n// return 123;\n// }\"\n\n(new Date()).toString()\n// \"Tue May 10 2016 09:11:31 GMT+0800 (CST)\"\n```\n#### toString()的应用:判断数据类型\n`Object.prototype.toString`方法返回对象的类型字符串,因此可以用来判断一个值的类型。\n```\nvar o = {};\no.toString() // \"[object Object]\"\n```\n上面代码调用空对象的`toString`方法,结果返回一个字符串`\"object Object\"`,其中第二个`Object`表示该值的构造函数。这是一个十分有用的判断数据类型的方法。实例对象可能会自定义`toString`方法,覆盖掉`Object.prototype.toString`方法。通过函数的`call`方法,可以在任意值上调用`Object.prototype.toString`方法,帮助我们判断这个值的类型。\n```\nObject.prototype.toString.call(value)\n```\n不同数据类型的`Object.prototype.toString`方法返回值如下。\n\n```\n数值:返回[object Number]。\n字符串:返回[object String]。\n布尔值:返回[object Boolean]。\nundefined:返回[object Undefined]。\nnull:返回[object Null]。\n数组:返回[object Array]。\narguments对象:返回[object Arguments]。\n函数:返回[object Function]。\nError对象:返回[object Error]。\nDate对象:返回[object Date]。\nRegExp对象:返回[object RegExp]。\n其他对象:返回[object \" + 构造函数的名称 + \"]。\n```\n也就是说,`Object.prototype.toString`可以得到一个实例对象的构造函数。\n```\nObject.prototype.toString.call(2) // \"[object Number]\"\nObject.prototype.toString.call('') // \"[object String]\"\nObject.prototype.toString.call(true) // \"[object Boolean]\"\nObject.prototype.toString.call(undefined) // \"[object Undefined]\"\nObject.prototype.toString.call(null) // \"[object Null]\"\nObject.prototype.toString.call(Math) // \"[object Math]\"\nObject.prototype.toString.call({}) // \"[object Object]\"\nObject.prototype.toString.call([]) // \"[object Array]\"\n```\n利用这个特性,可以写出一个比`typeof`运算符更准确的类型判断函数。\n```\nvar type = function (o){\n var s = Object.prototype.toString.call(o);\n return s.match(/\\[object (.*?)\\]/)[1].toLowerCase();\n};\n\ntype({}); // \"object\"\ntype([]); // \"array\"\ntype(5); // \"number\"\ntype(null); // \"null\"\ntype(); // \"undefined\"\ntype(/abcd/); // \"regex\"\ntype(new Date()); // \"date\"\n```\n在上面这个`type`函数的基础上,还可以加上专门判断某种类型数据的方法。\n```\n['Null',\n 'Undefined',\n 'Object',\n 'Array',\n 'String',\n 'Number',\n 'Boolean',\n 'Function',\n 'RegExp',\n 'NaN',\n 'Infinite'\n].forEach(function (t) {\n type['is' + t] = function (o) {\n return type(o) === t.toLowerCase();\n };\n});\n\ntype.isObject({}) // true\ntype.isNumber(NaN) // true\ntype.isRegExp(/abc/) // true\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n> [阮一峰](http://javascript.ruanyifeng.com/)","source":"_posts/好好学学Object-2016-02-03.md","raw":"title: 好好学学Object!\ndate: 2016-02-03 16:09:30\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\n## 概述\n\n### 定义方法\n\n对象(object)是JavaScript的核心概念,也是最重要的数据类型。JavaScript的所有数据都可以被视为对象。\n简单说,所谓对象,就是一种无序的数据集合,由若干个“键值对”(key-value)构成。\n<!--more-->\n\n```javascript\nvar o = {\n 'p': 'Hello World'\n};\n```\n\n上面代码中,大括号就定义了一个对象,它被赋值给变量`o`。这个对象内部包含一个键值对(又称为“成员”),`p`是“键名”(成员的名称),字符串“Hello World”是“键值”(成员的值)。键名与键值之间用冒号分隔。如果对象内部包含多个键值对,每个键值对之间用逗号分隔。\n\n### 键名\n\n对象的所有键名都是字符串,所以加不加引号都可以。上面的代码也可以写成下面这样。\n\n```javascript\nvar o = {\n p: 'Hello World'\n};\n```\n\n但是,如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),也不是数字,则必须加上引号。\n\n```javascript\nvar o = {\n '1p': \"Hello World\",\n 'h w': \"Hello World\",\n 'p+q': \"Hello World\"\n};\n```\n\n上面对象的三个键名,都不符合标识名的条件,所以必须加上引号。\n\n注意,JavaScript的保留字可以不加引号当作键名。\n\n```javascript\nvar obj = {\n for: 1,\n class: 2\n};\n```\n\n如果键名是数字,则会默认转为对应的字符串。\n\n```javascript\nvar obj = {\n 1e2: true,\n 1e-2: true,\n .234: true,\n 0xFF: true,\n};\n\n//Obj =\n// {\n// 100: true,\n// 255: true,\n// 0.01: true,\n// 0.234: true\n// }\n```\n\n上面代码表示,如果键名为数值,则会先转为标准形式的数值,然后再转为字符串。\n\n### 属性\n\n对象的每一个“键名”又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。\n\n```javascript\nvar o = {\n p: function(x) {\n return 2 * x;\n }\n};\n\no.p(1)\n// 2\n```\n\n上面的对象就有一个方法`p`,它就是一个函数。\n\n对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。\n\n```javascript\nvar o = {\n p: 123,\n m: function () { ... },//这个逗号不能加\n}\n```\n\n上面的代码中m属性后面的那个逗号,有或没有都不算错。但是,ECMAScript 3不允许添加逗号,所以如果要兼容老式浏览器(比如IE 8),那就不能加这个逗号。\n\n### 生成方法\n\n对象的生成方法,通常有三种方法。除了像上面那样直接使用大括号生成(`{}`),还可以用`new`命令生成一个Object对象的实例,或者使用`Object.create`方法生成。\n\n```javascript\nvar o1 = {};\nvar o2 = new Object();// 可以简写为 var o2 = new Object; 但是不推荐\nvar o3 = Object.create(null);\n```\n\n上面三行语句是等价的。一般来说,第一种采用大括号的写法比较简洁,第二种采用构造函数的写法清晰地表示了意图,第三种写法一般用在需要对象继承的场合。\n\n### 读写属性\n\n**(1)读取属性**\n\n读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。\n\n```javascript\nvar o = {\n p: 'Hello World'\n};\n\no.p // \"Hello World\"\no['p'] // \"Hello World\"\n```\n\n上面代码分别采用点运算符和方括号运算符,读取属性`p`。\n\n请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。但是,数字键可以不加引号,因为会被当作字符串处理。\n\n```javascript\nvar o = {\n 0.7: \"Hello World\"\n};\n\no.['0.7'] // \"Hello World\"\no[0.7] // \"Hello World\"\n```\n\n方括号运算符内部可以使用表达式。\n\n```javascript\no['hello' + ' world']\no[3 + 3]\n```\n\n数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。\n\n```javascript\nobj.0xFF\n// SyntaxError: Unexpected token\nobj[0xFF]\n// true\n```\n\n上面代码的第一个表达式,对数值键名0xFF使用点运算符,结果报错。第二个表达式使用方括号运算符,结果就是正确的。\n\n**(2)检查变量是否声明**\n\n如果读取一个不存在的键,会返回undefined,而不是报错。可以利用这一点,来检查一个变量是否被声明。\n\n```javascript\n// 检查a变量是否被声明\n\nif(a) {...} // 报错\n\nif(window.a) {...} // 不报错\nif(window['a']) {...} // 不报错\n```\n\n上面的后两种写法之所以不报错,是因为在浏览器环境,所有全局变量都是window对象的属性。`window.`的含义就是读取window对象的a属性,如果该属性不存在,就返回undefined,并不会报错。需要注意的是,后两种写法有漏洞,如果a属性值是一个空字符串(或其对应的布尔值为false的情况),则无法起到检查变量是否声明的作用。\n正确的写法是使用`in`运算符,或者使用Object原型的方法`hasOwnProperty()`也就是Object原型。\n\n```javascript\nif('a' in window) {\n ...\n}\n\nif(window.hasOwnProperty(\"a\")) {\n ...\n}\n```\n\n**(3)写入属性**\n\n点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。\n\n```javascript\no.p = 'abc';\no['p'] = 'abc';\n```\n\n上面代码分别使用点运算符和方括号运算符,对属性p赋值。\n\nJavaScript允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。\n\n```javascript\nvar o = { p: 1 };\n\n// 等价于\n\nvar o = {};\no.p = 1;\n```\n\n**(4)查看所有属性**\n\n`Object.keys()` 方法会返回一个由给定对象的所有**可枚举自身属性的属性名组成的数组**,数组中属性名的排列顺序和使用`for-in`循环遍历该对象时返回的顺序一致(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。\n支持:`IE9+`(包括IE9)。\n\n```javascript\nvar o = {\n key1: 1,\n key2: 2\n};\n\nObject.keys(o);\n// ['key1', 'key2']\n```\n兼容处理方案:\n```javascirpt\nif (!Object.keys) {\n Object.keys = function(o) {\n if (o !== Object(o)) {\n throw new TypeError('Object.keys called on a non-object');\n }\n var k = [],\n p;\n for (p in o) {\n if (Object.prototype.hasOwnProperty.call(o, p)) {\n k.push(p);\n }\n }\n return k;\n }\n}\n```\n\n### 属性的删除\n\n删除一个属性,需要使用`delete`命令。\n\n```javascript\nvar o = {p: 1};\nObject.keys(o) // [\"p\"]\n\ndelete o.p // true\no.p // undefined\nObject.keys(o) // []\n```\n\n上面代码表示,一旦使用`delete`命令删除某个属性,再读取该属性就会返回`undefined`,而且`Object.keys`方法返回的该对象的所有属性中,也将不再包括该属性。\n\n<span style=\"color: red;\">麻烦的是,如果删除一个不存在的属性,delete不报错,而且返回true。</span>\n\n```javascript\nvar o = {};\ndelete o.p // true\n```\n\n上面代码表示,delete命令只能用来保证某个属性的值为undefined,而无法保证该属性是否真的存在。\n\n只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。\n\n```javascript\n\nvar o = Object.defineProperty({}, \"p\", {\n value: 123,\n configurable: false\n});\n\no.p // 123\ndelete o.p // false\n\n```\n\n上面代码之中,o对象的p属性是不能删除的,所以delete命令返回false(关于Object.defineProperty方法的介绍,请看《标准库》一章的Object对象章节)。\n\n另外,需要注意的是,delete命令只能删除对象本身的属性,不能删除继承的属性(关于继承参见《面向对象编程》一节)。delete命令也不能删除var命令声明的变量,只能用来删除属性。\n\n#### 以下这些都不能delete\n* ```javascript\n function func() {\n console.log(delete arguments);//false\n console.log(Object.getOwnPropertyDescriptor(func,\"arguments\"));//{writable: false, enumerable: false, configurable: false}\n }\n func(1);\n ```\n* var 声明的变量。\n* ```javascript\n function func() {\n\n }\n delete func//false\n Object.getOwnPropertyDescriptor(window,\"func\"); // {writable: true, enumerable: true, configurable: false}\n ```\n* delete不能删除对象继承来自原型上的属性\n总结下:\n 1. 内置对象的属性及方法多数不能delete,保护该语言最核心API,这些API被delete了,基本上就废了。如delete Object.prototype。\n 2. 对象继承于原型的属性和方法不能delete是出于保护原型,否则 “类A的对象delete了原型上的属性,那么继承于A的都将丢失该属性”\n\n### 对象的引用\n\n如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。\n\n```javascript\nvar o1 = {};\nvar o2 = o1;\n\no1.a = 1;\no2.a // 1\n\no2.b = 2;\no1.b // 2\n```\n\n上面代码中,`o1`和`o2`指向同一个对象,因此为其中任何一个变量添加属性,另一个变量都可以读写该属性。\n\n此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。\n\n```javascript\nvar o1 = {};\nvar o2 = o1;\n\no1 = 1;\no2 // {}\n```\n\n上面代码中,`o1`和`o2`指向同一个对象,然后`o1`的值变为1,这时不会对`o2`产生影响,`o2`还是指向原来的那个对象。\n\n但是,这种引用只局限于对象,对于原始类型的数据则是传值引用,也就是说,都是值的拷贝。\n\n```javascript\nvar x = 1;\nvar y = x;\n\nx = 2;\ny // 1\n```\n\n上面的代码中,当`x`的值发生变化后,`y`的值并不变,这就表示`y`和`x`并不是指向同一个内存地址。\n\n### `in`运算符\n\n1. in操作符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回`true`,否则返回`false`。\n\n```javascript\nvar o = { p: 1 };\n'p' in o // true\n```\n\n2. 在JavaScript语言中,所有全局变量都是顶层对象(浏览器的顶层对象就是`window`对象)的属性,因此可以用`in`运算符判断,一个全局变量是否存在。\n\n```javascript\n// 假设变量x未定义\n\n// 写法一:报错\nif (x) { return 1; }\n\n// 写法二:不正确\nif (window.x) { return 1; }\n\n// 写法三:正确\nif ('x' in window) { return 1; }\nif ( x in window) { return 1; } // 注意x要用引号包括起来,是字符串\n\n// 写法四: 正确\nif (window.hasOwnProperty('x')) { return 1; } // 注意x要用引号包括起来,是字符串\n```\n\n上面三种写法之中,如果`x`不存在,第一种写法会报错;如果`x`的值对应布尔值`false`(比如`x`等于空字符串),第二种写法无法得到正确结果;只有第三种、第四种写法,才能正确判断变量`x`是否存在。\n\n<span style=\"color: red;\">`in`运算符的一个问题是,它不能区分对象继承的属性。</span>\n\n```javascript\nvar o = new Object();\no.hasOwnProperty('toString') // false\n\n'toString' in o // true\n```\n\n上面代码中,`toString`方法不是对象`o`自身的属性,而是继承的属性,`hasOwnProperty`方法可以说明这一点。但是,`in`运算符不能区分,对继承的属性也返回`true`。\n3. in的右边必须是一个对象\n```javascript\nvar color1 = new String(\"green\");\n\"length\" in color1 // return true\nvar color2 = \"coral\";\n\"length\" in color2 // Uncaught TypeError: Cannot use 'in' operator to search for 'length' in coral(…)\n```\n4. 检验数组指定角标是否越界。\n```javascript\nvar trees = new Array(\"redwood\", \"bay\", \"cedar\", \"oak\", \"maple\");\n0 in trees // returns true\n3 in trees // returns true\n6 in trees // returns false\n\"bay\" in trees // returns false (you must specify the index number, not the value at that index)\n\"length\" in trees // returns true (length is an Array property)\n```\n5. 如果你使用`delete`操作符删除了一个属性,再次用in检查时,会返回false,如:\n```javascript\nvar mycar = {make: \"Honda\", model: \"Accord\", year: 1998};\nmycar.make = undefined;\n\"make\" in mycar; // return true\ndelete mycar.Accord;\n\"Accord\" in mycar; // return false\n \n\nvar trees = new Array(\"redwood\", \"bay\", \"cedar\", \"oak\", \"maple\");\ntrees[3] = undefined;\n3 in trees; // return true\ndelete trees[2];\n2 in trees; // return false\n```\n6. 如果一个属性是从原型链上继承来的,in 运算符也会返回 true。\n```javascript\n\"toString\" in {}; // 返回true\n```\n### for...in循环\nfor...in 循环不遍历不可枚举属性。使用内建构造器例如 Array 和 Object 创建的对象拥有从 Object.prototype 和 String.prototype 继承的不可枚举属性,例如 String 的 indexOf() 方法或者 Object 的 toString 方法。循环将迭代对象的所有可枚举属性,包括从它的构造函数的 prototype 继承而来的(包括被覆盖的内建属性)。\n** 删除,添加或者修改属性 **\nfor...in 循环以任意序迭代一个对象的属性。通常,在迭代过程中最好不要在对象上进行添加、修改或者删除属性的操作,除非是对当前正在被访问的属性。这里并不保证是否一个被添加的属性在迭代过程中会被访问到,不保证一个修改后的属性(除非是正在被访问的)会在修改前或者修改后被访问,不保证一个被删除的属性将会在它被删除之前被访问。\n\n** Array 迭代和 for...in **\n数组索引仅是可枚举的整数名,其他方面和别的普通对象属性没有什么区别。for...in 并不能够保证返回的是按一定顺序的索引,但是它会返回所有可枚举属性,包括非整数名称的和继承的。因为迭代的顺序是依赖于执行环境的,所以数组遍历不一定按次序访问元素。 因此当迭代那些访问次序重要的 arrays 时用整数索引去进行 for 循环 (或者使用 Array.prototype.forEach() 或 for...of 循环) 。\n\n** 仅迭代自身的属性 **\n如果你只要考虑对象本身的属性,而不是它的原型,那么使用 `getOwnPropertyNames()`(自身的可枚举和不可枚举属性都能获得) 或执行 `hasOwnProperty()` 来确定某属性是否是对象本身的属性 (也能使用`propertyIsEnumerable`)。另外,如果你知道外部不存在任何的干扰代码,你可以扩展内置原型与检查方法。\n```javascript\nvar o = {a: 1, b: 2, c: 3};\n\nfor (i in o){\n console.log(o[i]);\n}\n// 1\n// 2\n// 3\n```\n\n下面是一个使用`for...in`循环,进行数组赋值的例子。\n\n```javascript\nvar props = [], i = 0;\n\nfor (props[i++] in {x: 1, y: 2});//循环执行的时候会给每一个变量(`props[i++]`)赋`{x: 1, y: 2}`的属性值。\n\nprops // ['x', 'y']\n```\n\n注意,`for...in`循环遍历的是对象所有可`enumberable`的属性(属性特性`[[enumberable]]`为`true`),其中不仅包括定义在对象本身的属性,还包括对象继承的属性。\n> 属性特性 `enumerable` 定义了对象的属性是否可以在 `for...in` 循环和 `Object.keys()` 中被枚举。\n```javascript\n// name 是 Person 本身的属性\nfunction Person(name) {\n this.name = name;\n}\n\n// describe是Person.prototype的属性\nPerson.prototype.describe = function () {\n return 'Name: '+this.name;\n};\n\nvar person = new Person('Jane');\n\n// for...in循环会遍历实例自身的属性(name),\n// 以及继承的属性(describe)\nfor (var key in person) {\n console.log(key);\n}\n// name\n// describe\n```\n\n上面代码中,`name`是对象本身的属性,`describe`是对象继承的属性,`for...in`循环的遍历会包括这两者。\n\n如果只想遍历对象本身的属性,可以使用hasOwnProperty方法,在循环内部做一个判断。\n\n```javascript\nfor (var key in person) {\n if (person.hasOwnProperty(key)) {\n console.log(key);\n }\n}\n// name\n```\n\n为了避免这一点,可以新建一个继承`null`的对象。由于`null`没有任何属性,所以新对象也就不会有继承的属性了。\n\n## with语句\n\nwith语句的格式如下:\n\n```javascript\n\nwith (object)\n statement\n\n```\n\n它的作用是操作同一个对象的多个属性时,提供一些书写的方便。\n\n```javascript\n\n// 例一\nwith (o) {\n p1 = 1;\n p2 = 2;\n}\n\n// 等同于\n\no.p1 = 1;\no.p2 = 2;\n\n// 例二\nwith (document.links[0]){\n console.log(href);\n console.log(title);\n console.log(style);\n}\n\n// 等同于\n\nconsole.log(document.links[0].href);\nconsole.log(document.links[0].title);\nconsole.log(document.links[0].style);\n\n```\n\n注意,with区块内部的变量,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。这是因为with区块没有改变作用域,它的内部依然是当前作用域。\n\n```javascript\n\nvar o = {};\n\nwith (o){\n x = \"abc\";\n}\n\no.x\n// undefined\n\nx\n// \"abc\"\n\n```\n\n上面代码中,对象o没有属性x,所以with区块内部对x的操作,等于创造了一个全局变量x。正确的写法应该是,先定义对象o的属性x,然后在with区块内操作它。\n\n```javascript\n\nvar o = {};\n\no.x = 1;\n\nwith (o){\n x = 2;\n}\n\no.x\n// 2\n\n```\n\n这是with语句的一个很大的弊病,就是绑定对象不明确。\n\n```javascript\n\nwith (o) {\n console.log(x);\n}\n\n```\n\n单纯从上面的代码块,根本无法判断x到底是全局变量,还是o对象的一个属性。这非常不利于代码的除错和模块化,编译器也无法对这段代码进行优化,只能留到运行时判断,这就拖慢了运行速度。因此,建议不要使用with语句,可以考虑用一个临时变量代替with。\n\n```javascript\n\nwith(o1.o2.o3) {\n console.log(p1 + p2);\n}\n\n// 可以写成\n\nvar temp = o1.o2.o3;\nconsole.log(temp.p1 + temp.p2);\n\n```\n\nwith语句少数有用场合之一,就是替换模板变量。\n\n```javascript\nvar str = 'Hello <%= name %>!';\n```\n\n上面代码是一个模板字符串,为了替换其中的变量name,可以先将其分解成三部分`'Hello ', name, '!'`,然后进行模板变量替换。\n\n```javascript\n\nvar o = {\n name: 'Alice'\n};\n\nvar p = [];\nvar tmpl = '';\n\nwith(o){\n p.push('Hello ', name, '!');\n};\n\np.join('') // \"Hello Alice!\"\n```\n\n上面代码中,with区块内部,模板变量name可以被对象o的属性替换,而p依然是全局变量。事实上,这就是很多模板引擎的实现原理。\n\n## Object\n\n### 概述\n\nJavaScript原生提供一个Object对象(注意起首的O是大写),所有其他对象都继承自这个对象。Object本身也是一个构造函数,可以直接通过它来生成新对象。\nObject 的每个实例也就是Object原型 prototype 具有下列属性和方法:\n\n* `constructor`:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(`constructor`)就是Object()。\n* `hasOwnProperty(propertyName)`:用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如:o.hasOwnProperty(\"name\"))。\n* `isPrototypeOf(object)`:用于检查传入的对象是否是传入对象的原型(第5章将讨论原型)。\n* `propertyIsEnumerable(propertyName)`:用于检查给定的属性是否能够使用for-in 语句(本章后面将会讨论)来枚举。与hasOwnProperty()方法一样,作为参数的属性名必须以字符串形式指定。\n `propertyIsEnumerable()`方法的返回值为Boolean类型,该值指示指定属性是否为对象的一部分以及该属性是否是可枚举的(只有这两个条件同时满足才返回`true`)。如果propertyName存在于object中且可以使用for...in循环遍历出来,则`propertyIsEnumerable()`方法将返回`true`。如果object不具有所指定名称的属性或者所指定的属性不是可枚举的,则`propertyIsEnumerable()`方法将返回`false`。\n 通常,预定义的属性不是可枚举的,而用户定义的属性总是可枚举的。不过有些属性虽然可以通过`for...in`循环遍历到,但因为它们不是自身属性,而是从原型链上继承的属性,所以该方法也会返回`false`。\n* `toLocaleString()`:返回对象的字符串表示,该字符串与执行环境的地区对应。\n* `toString()`:返回对象的字符串表示。\n* `valueOf()`:返回对象的字符串、数值或布尔值表示。通常与toString()方法的返回值相同。\n* `__defineGetter__()`\n* `__defineSetter__()`\n* `__lookupGetter__()`\n* `__lookupSetter__()`\n* `get __proto__()` //不可被显示调用(`.`出来)\n* `set __proto__()` //不可被显示调用(`.`出来)\n\n还有一些其他方法据各个浏览器厂商的实现来定,比如firefox还有`toSource()`、`watch()`、 `unwatch()`,这些内容不做过多介绍,毕竟不是标准。后面将会对这些方法做详细介绍。\n`Object`作为构造函数使用时,可以接受一个参数。如果该参数是一个对象,则直接返回这个对象;如果是一个原始类型的值,则返回该值对应的包装对象。\n```\nvar o1 = {a: 1};\nvar o2 = new Object(o1);\no1 === o2 // true\n\nnew Object(123) instanceof Number\n// true\n```\n> 注意,通过`new Object()`的写法生成新对象,与字面量的写法`o = {}`是等价的,但是通过对象字面量定义对象时,实际上不会调用Object 构造函数。\n\n与其他构造函数一样,如果要在Object对象上面部署一个方法,有两种做法:\n1. 部署在Object对象本身\n比如,在Object对象上面定义一个print方法,显示其他对象的内容。\n```\nObject.print = function(o){ console.log(o) };\n\nvar o = new Object();\n\nObject.print(o)\n// Object\n```\n2. 部署在Object.prototype对象\n所有构造函数都有一个prototype属性,指向一个原型对象。凡是定义在Object.prototype对象上面的属性和方法,将被所有实例对象共享。\n```\nObject.prototype.print = function(){ console.log(this)};\n\nvar o = new Object();\n\no.print() // Object\n```\n上面代码在`Object.prototype`定义了一个print方法,然后生成一个`Object`的实例o。o直接继承了`Object.prototype`的属性和方法,可以在自身调用它们,也就是说,`o`对象的print方法实质上是调用`Object.prototype.print`方法。\n可以看到,尽管上面两种写法的print方法功能相同,但是用法是不一样的,因此必须区分“构造函数的方法”和“实例对象的方法”。\n\n### Object()\n\nObject本身当作工具方法使用时,可以将任意值转为对象。这个方法常用于保证某个值一定是对象。如果参数是原始类型的值,Object方法返回对应的包装对象的实例。\n```\nObject() // 返回一个空对象\nObject() instanceof Object // true\n\nObject(undefined) // 返回一个空对象\nObject(undefined) instanceof Object // true\n\nObject(null) // 返回一个空对象\nObject(null) instanceof Object // true\n\nObject(1) // 等同于 new Number(1)\nObject(1) instanceof Object // true\nObject(1) instanceof Number // true\n\nObject('foo') // 等同于 new String('foo')\nObject('foo') instanceof Object // true\nObject('foo') instanceof String // true\n\nObject(true) // 等同于 new Boolean(true)\nObject(true) instanceof Object // true\nObject(true) instanceof Boolean // true\n```\n如果Object方法的参数是一个对象,它总是返回原对象。\n```\nvar arr = [];\nObject(arr) // 返回原数组\nObject(arr) === arr // true\n\nvar obj = {};\nObject(obj) // 返回原对象\nObject(obj) === obj // true\n\nvar fn = function () {};\nObject(fn) // 返回原函数\nObject(fn) === fn // true\n```\n利用这一点,可以写一个判断变量是否为对象的函数。\n```\nfunction isObject(value) {\n return value === Object(value);\n}\n\nisObject([]) // true\nisObject(true) // false\n```\n### Object 对象的静态方法\n所谓“静态方法”,是指部署在Object对象自身的方法。\n#### Object.keys(),Object.getOwnPropertyNames()\n`Object.keys`方法和`Object.getOwnPropertyNames`方法很相似,一般用来遍历对象的属性。它们的参数都是一个对象,都返回一个数组,该数组的成员都是对象自身的(不包含`prototype`)所有属性名。它们的区别在于,`Object.keys`方法只返回可枚举的属性(关于可枚举性的详细解释见后文),`Object.getOwnPropertyNames`方法还返回不可枚举的属性名。\n上面的代码表示,对于一般的对象来说,这两个方法返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。\n```\nvar a = [\"Hello\", \"World\"];\n\nObject.keys(a)\n// [\"0\", \"1\"]\n\nObject.getOwnPropertyNames(a)\n// [\"0\", \"1\", \"length\"]\n```\n上面代码中,数组的`length`属性是不可枚举的属性,所以只出现在`Object.getOwnPropertyNames`方法的返回结果中。\n由于JavaScript没有提供计算对象属性个数的方法,所以可以用这两个方法代替。\n```\nObject.keys(o).length\nObject.getOwnPropertyNames(o).length\n```\n一般情况下,几乎总是使用Object.keys方法,遍历数组的属性。\n\n#### 其他方法\n除了上面提到的方法,Object还有不少其他方法,将在后文逐一详细介绍。\n1. 对象属性模型的相关方法\n ```\n Object.getOwnPropertyDescriptor():获取某个属性的attributes对象。\n Object.defineProperty():通过attributes对象,定义某个属性。\n Object.defineProperties():通过attributes对象,定义多个属性。\n Object.getOwnPropertyNames():返回直接定义在某个对象上面的全部属性的名称。\n ```\n2. 控制对象状态的方法\n ```\n Object.preventExtensions():防止对象扩展。\n Object.isExtensible():判断对象是否可扩展。\n Object.seal():禁止对象配置。\n Object.isSealed():判断一个对象是否可配置。\n Object.freeze():冻结一个对象。\n Object.isFrozen():判断一个对象是否被冻结。\n ```\n3. 原型链相关方法\n ```\n Object.create():创建一个拥有指定原型和若干个指定属性的对象。\n Object.getPrototypeOf():获取对象的Prototype对象。\n ```\n### Object对象的实例方法\n除了`Object`对象本身的方法,还有不少方法是部署在`Object.prototype`对象上的,所有`Object`的实例对象都继承了这些方法。\nObject实例对象的方法,主要有以下六个。\n 1. valueOf():返回当前对象对应的值。\n 2. toString():返回当前对象对应的字符串形式。\n 3. toLocaleString():返回当前对象对应的本地字符串形式。\n 4. hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。\n 5. isPrototypeOf():判断当前对象是否为另一个对象的原型。\n 6. propertyIsEnumerable():判断某个属性是否可枚举。\n#### Object.prototype.valueOf()\n`valueOf`方法的作用是返回一个对象的“值”,默认情况下返回对象本身。\n```\nvar o = new Object();\no.valueOf() === o // true\n```\n上面代码比较`o.valueOf()`与`o`本身,两者是一样的。`valueOf`方法的主要用途是,JavaScript自动类型转换时会默认调用这个方法。\n```\nvar o = new Object();\n1 + o // \"1[object Object]\"\n```\n上面代码将对象`o`与数字1相加,这时JavaScript就会默认调用`valueOf()`方法。所以,如果自定义`valueOf`方法,就可以得到想要的结果。\n```\nvar o = new Object();\no.valueOf = function (){\n return 2;\n};\n\n1 + o // 3\n```\n上面代码自定义了`o`对象的`valueOf`方法,于是`1 + o`就得到了`3`。这种方法就相当于用`o.valueOf`覆盖`Object.prototype.valueOf`。\n\n#### Object.prototype.toString()\n`toString`方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。\n```\nvar o1 = new Object();\no1.toString() // \"[object Object]\"\n\nvar o2 = {a:1};\no2.toString() // \"[object Object]\"\n```\n上面代码表示,对于一个对象调用`toString`方法,会返回字符串`[object Object]`,该字符串说明对象的类型。字符串`[object Object]`本身没有太大的用处,但是通过自定义`toString`方法,可以让对象在自动类型转换时,得到想要的字符串形式。\n```\nvar o = new Object();\n\no.toString = function () {\n return 'hello';\n};\n\no + ' ' + 'world' // \"hello world\"\n```\n上面代码表示,当对象用于字符串加法时,会自动调用`toString`方法。由于自定义了`toString`方法,所以返回字符串hello world。\n数组、字符串、函数、Date对象都分别部署了自己版本的`toString`方法,覆盖了`Object.prototype.toString`方法。\n```\n[1, 2, 3].toString() // \"1,2,3\"\n\n'123'.toString() // \"123\"\n\n(function () {\n return 123;\n}).toString()\n// \"function () {\n// return 123;\n// }\"\n\n(new Date()).toString()\n// \"Tue May 10 2016 09:11:31 GMT+0800 (CST)\"\n```\n#### toString()的应用:判断数据类型\n`Object.prototype.toString`方法返回对象的类型字符串,因此可以用来判断一个值的类型。\n```\nvar o = {};\no.toString() // \"[object Object]\"\n```\n上面代码调用空对象的`toString`方法,结果返回一个字符串`\"object Object\"`,其中第二个`Object`表示该值的构造函数。这是一个十分有用的判断数据类型的方法。实例对象可能会自定义`toString`方法,覆盖掉`Object.prototype.toString`方法。通过函数的`call`方法,可以在任意值上调用`Object.prototype.toString`方法,帮助我们判断这个值的类型。\n```\nObject.prototype.toString.call(value)\n```\n不同数据类型的`Object.prototype.toString`方法返回值如下。\n\n```\n数值:返回[object Number]。\n字符串:返回[object String]。\n布尔值:返回[object Boolean]。\nundefined:返回[object Undefined]。\nnull:返回[object Null]。\n数组:返回[object Array]。\narguments对象:返回[object Arguments]。\n函数:返回[object Function]。\nError对象:返回[object Error]。\nDate对象:返回[object Date]。\nRegExp对象:返回[object RegExp]。\n其他对象:返回[object \" + 构造函数的名称 + \"]。\n```\n也就是说,`Object.prototype.toString`可以得到一个实例对象的构造函数。\n```\nObject.prototype.toString.call(2) // \"[object Number]\"\nObject.prototype.toString.call('') // \"[object String]\"\nObject.prototype.toString.call(true) // \"[object Boolean]\"\nObject.prototype.toString.call(undefined) // \"[object Undefined]\"\nObject.prototype.toString.call(null) // \"[object Null]\"\nObject.prototype.toString.call(Math) // \"[object Math]\"\nObject.prototype.toString.call({}) // \"[object Object]\"\nObject.prototype.toString.call([]) // \"[object Array]\"\n```\n利用这个特性,可以写出一个比`typeof`运算符更准确的类型判断函数。\n```\nvar type = function (o){\n var s = Object.prototype.toString.call(o);\n return s.match(/\\[object (.*?)\\]/)[1].toLowerCase();\n};\n\ntype({}); // \"object\"\ntype([]); // \"array\"\ntype(5); // \"number\"\ntype(null); // \"null\"\ntype(); // \"undefined\"\ntype(/abcd/); // \"regex\"\ntype(new Date()); // \"date\"\n```\n在上面这个`type`函数的基础上,还可以加上专门判断某种类型数据的方法。\n```\n['Null',\n 'Undefined',\n 'Object',\n 'Array',\n 'String',\n 'Number',\n 'Boolean',\n 'Function',\n 'RegExp',\n 'NaN',\n 'Infinite'\n].forEach(function (t) {\n type['is' + t] = function (o) {\n return type(o) === t.toLowerCase();\n };\n});\n\ntype.isObject({}) // true\ntype.isNumber(NaN) // true\ntype.isRegExp(/abc/) // true\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n> [阮一峰](http://javascript.ruanyifeng.com/)","slug":"好好学学Object","published":1,"updated":"2016-07-12T14:56:01.938Z","layout":"post","photos":[],"link":"","_id":"citf9om0z0010skv77qm79pbk"},{"title":"严格模式","date":"2016-01-21T05:55:51.000Z","comments":1,"_content":"# 概述\nECMAScript 5 的严格模式是JavaScript中的一种限制性更强的变种方式。严格模式不是一个子集:它在语义上与正常代码有着明显的差异。不支持严格模式的浏览器与同支持严格模式的浏览器行为上也不一样, 所以不要在未经严格模式特性测试情况下使用严格模式。严格模式可以与非严格模式共存,所以脚本可以逐渐的选择性加入严格模式。\n支持情况:IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱。(github上面好多项目都是用的严格模式)\n严格模式在语义上与正常的JavaScript有一些不同。\n* 首先,严格模式会将JavaScript陷阱直接变成明显的错误。\n* 其次,严格模式修正了一些引擎难以优化的错误:同样的代码有些时候严格模式会比非严格模式下更快。\n* 第三,严格模式禁用了一些有可能在未来版本中定义的语法。\n<!--more-->\n# 开启严格模式\n严格模式可以应用到整个script标签或某个别函数中。不要在封闭大括弧( {} )内这样做,在这样的上下文中这么做是没有效果的。\n## 为某个script标签开启严格模式\n为整个script标签开启严格模式, 需要在所有语句之前放一个特定语句 `\"use strict\";` (或 `'use strict';`)。\n\n```javascript\n// 整个语句都开启严格模式的语法\n\"use strict\";\nvar v = \"Hi! I'm a strict mode script!\";\n```\n如果你想定义一个模块或者一个小库,自然采用一个匿名函数自执行是不错的选择。同时解决了代码压缩产生的 `\"use strict\"`压缩后不在第一行的问题。\n\n```javascript\n~function() {\n \"use strict\"; \n // Define your library strictly...\n}();\n```\n`\"use strict\"` 的位置是很讲究的,必须在首部。首部指其前面没有任何有效js代码。以下都是无效的,将不会触发严格模式。\n* “use strict” 前有代码\n* “use strict” 前有个空语句都不行\n ```javascript\n ;//空语句\n 'use strict'\n globalVar = 100\n ```\n当然,“use strict”前加注释是可以的\n\n## 为某个函数开启严格模式\n function strict() {\n // 函数级别严格模式语法\n 'use strict';\n function nested() {\n return \"And so am I!\";\n }\n return \"Hi! I'm a strict mode function! \" + nested();\n }\n## Chrome中调试严格模式\n我有这么一段代码:\n```javascript\n'use strict'\nname = \"reeoo\";\nconsole.log(name)\n```\n把这段代码直接粘贴到Chrome的控制台中执行,正常情况下应该报错,但是并没有报错。\n{% asset_img stracit.png %}\n很显然,严格模式下变量不适用var声明是不合法的,但是为什么没有报错?这是什么鬼,难道Chrome不支持严格模式?开什么玩笑。。。\n网上搜了一下,原来Chrome的控制台的代码是运行在eval之中的,你没法对eval函数使用严格模式(应该也不完全对,但是具体Chrome做了什么,不得而知),总之要想在Chrome浏览器中对严格模式正常报错,需要在代码外层套一个立即执行函数,或者其它类似的措施。\n```javascript\n(function(){\n 'use strict'\n name = \"reeoo\";\n console.log(name) \n})()\n```\n\n# 严格模式有哪些不同?\n* 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;(将问题直接转化为错误,如语法错误或运行时错误)。\n* 提高编译器效率,增加运行速度。\n* 消除代码运行的一些不安全之处,保证代码运行的安全。\n* 为未来新版本的Javascript做好铺垫。\n\n# 代码在严格模式下受到的限制\n## 全局变量显式声明\n在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。\n\n \"use strict\";\n mistypedVaraible = 17; // 报错,mistypedVaraible未声明\n## 静态绑定\nJavascript语言的一个特点,就是允许\"动态绑定\",即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。\n具体来说,涉及以下几个方面。\n### 禁止使用with语句\n因为with语句无法在编译时就确定,属性到底归属哪个对象。\n\n \"use strict\";\n var x = 17;\n with (obj) // !!! 语法错误\n {\n // 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?\n // 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。\n x;\n }\n### 创设eval作用域\n正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。\n严格模式创设了第三种作用域:eval作用域。\n正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。\n严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。\n\n \"use strict\";\n eval(\"var testvar = 10\");\n console.log(testvar);//在严格模式下报错,在非严格模式下 打印 10\n## 禁止删除变量\n严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。\n\n \"use strict\";\n var x;\n delete x; // 语法错误\n\n eval(\"var x; delete x;\"); // !!! 语法错误\n\n var o = Object.create(null, {'x': {\n value: 1,\n configurable: true\n }});\n delete o.x; // 删除成功\n## 增强的安全措施\n### 禁止this关键字指向全局对象,默认值undefined\n\n function f(){\n return !this;\n }\n // 返回false,因为\"this\"指向全局对象,\"!this\"就是false\n function f(){\n \"use strict\";\n return !this;\n }\n // 返回true,因为严格模式下,this的值为undefined,所以\"!this\"为true。\n### caller/callee 被禁用\n\n function parentCheck() {\n check(\"\");\n function check() {\n subCheck();\n function subCheck() {\n console.log(arguments.caller);\n // ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是 undefined。定义这个属性是为了分清arguments.caller 和函数的caller 属性。\n console.log(arguments.callee);\n console.log(subCheck.caller);\n console.log(subCheck.caller.caller);\n }\n }\n }\n parentCheck();\n \n### 显式报错\nNaN 是一个不可写的全局变量. 在正常模式下, 给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈. 但在严格模式下, 给 NaN 赋值会抛出一个异常\n\n \"use strict\";\n NaN = 2;//报错\n正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。\n\n \"use strict\"; \n var o = {}; \n Object.defineProperty(o, \"v\", {\n value: 1,\n writable: false\n }); \n o.v = 2; // 报错\n严格模式下,对一个只有getter方法的属性进行赋值,会报错。\n\n \"use strict\";\n \n var o = {\n get v() {return 1; } \n }; \n o.v = 2; // 报错\n\n如果有setter 则不会报错。\n\n \"use strict\"; \n var o = {\n get v() {\n return this.aa; \n },\n set v(a){\n this.aa = a;\n } \n }; \n o.v = 2; // 不会报错\n console.log(o.v);\n \n严格模式下,对禁止扩展的对象添加新属性,会报错。\n\n \"use strict\"; \n var o = {}; \n Object.preventExtensions(o); \n o.v = 1; // 报错\n严格模式下,删除一个不可删除的属性,会报错。\n\n \"use strict\"; \n delete Object.prototype; // 报错\n## 重名错误\n### 对象不能有重名的属性\n正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。\n\n \"use strict\"; \n var o = { \n p: 1,\n p: 2 \n }; // 语法错误\n### 函数不能有重名的参数\n正常模式下,如果函数有多个重名的形参,不会报错,在严格模式下,这属于语法错误。\n\n ```javascript\n \"use strict\"; \n function f(a, a, b) {\n console.log(a,a,b);\n // Duplicate parameter name not allowed in this context \n //正常模式 输出 1 2 3\n }\n f(1,2,3);\n ```\n## 禁止八进制表示法\n正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。\n\n \"use strict\"; \n var n = 0100; // 语法错误\n## eval和arguments对象的限制\n### 赋值和绑定\n首先, arguments, eval这两个在严格模式下不能做标识符或属性名\n\n \"use strict\";\n eval = 17;\n arguments++;\n ++eval;\n var obj = { set p(arguments) { } };\n var eval;\n try { } catch (arguments) { }\n function x(eval) { }\n function arguments() { }\n var y = function eval() { };\n var f = new Function(\"arguments\", \"'use strict'; return 17;\");\n### arguments不再追踪参数的变化\n\n function f(a) { \n a = 2; \n return [a, arguments[0]]; \n } \n f(1); // 正常模式为[2,2]\n \n function f(a) { \n \"use strict\"; \n a = 2; \n return [a, arguments[0]]; \n } \n f(1); // 严格模式为[2,1]\n### 禁止使用arguments.callee\n这意味着,你无法在匿名函数内部调用自身了,可以用命名函数表达式解决这个问题。\n\n \"use strict\"; \n var f = function() {\n return arguments.callee;\n }; \n f(); // 报错\n## 函数必须声明在顶层\n将来Javascript的新版本会引入\"块级作用域\"。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。\n\n \"use strict\"; \n if (true) { \n function f() {} // 语法错误\n \n } \n for (var i = 0; i < 5; i++) { \n function f2() {} // 语法错误\n \n }\n\n## 保留字\n为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。\n使用这些词作为变量名将会报错。此外,ECMAscript第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的const保留字,也是不能作为变量名的。\n\n# 向严格模式过度\n## 逐步过渡\n严格模式被仔细设计过,因此可以逐渐地进行迁移。你可以分别改变各个文件,甚至以函数级的粒度迁移至严格模式。\n## 过程\n如果代码中使用\"use strict\"开启了严格模式,则下面的情况都会在脚本运行之前抛出`SyntaxError`异常:\n### 语法错误\n\n* 八进制语法:var n = 023和var s = \"\\047\"\n* with语句\n* 使用delete删除一个变量名(而不是属性名):delete myVariable\n* 使用eval或arguments作为变量名或函数名\n* 使用未来保留字(也许会在ECMAScript 6中使用):implements, interface, let, package, private, protected, public, static,和yield作为变量名或函数名\n* 在语句块中使用函数声明:if(a<b){ function f(){} }\n* 其他错误:\n* * 对象子面量中使用两个相同的属性名:{a: 1, b: 3, a: 7}\n* * 函数形参中使用两个相同的参数名:function f(a, b, b){}\n\n这些错误是有利的,因为可以揭示简陋的错误和坏的实践,这些错误会在代码运行前被抛出。\n\n### 运行时错误\nJavaScript曾经会在一些上下文的某些情况中静默的失败,严格模式会在这些情况下抛出错误。如果你的代码包含这样的场景,请务必测试以确保没有代码受到影响。再说一次,严格模式是可以设置在代码粒度下的。\n\n1. 给一个未声明的变量赋值\n2. 改变一个全局对象的值可能会造成不可预期的后果。如果你真的想设置一个全局对象的值,把他作为一个参数并且明确的把它作为一个属性。\n var global = this; // in the top-level context, \"this\" always refers the global object\n function f() {\n \"use strict\";\n var a = 12;\n global.b = a + x * 35;\n }\n f();\n·\n3. 尝试删除一个不可配置的属性。\n \"use strict\";\n delete Object.prototype; // error!\n 在非严格模式中,这样的代码只会静默失败,这样可能会导致用户误以为删除操作成功了.\n\n4. `arguments`对象和函数属性\n\n在严格模式下,访问`arguments.callee`, `arguments.caller`, `anyFunction.caller`以及`anyFunction.arguments`都会抛出异常.唯一合法的使用应该是在其中命名一个函数并且重用之\n\n### 语义差异\n1. 函数调用中的this\n在普通的函数调用f()中,this的值会指向全局对象。在严格模式中,this的值会指向undefined。当函数通过call和apply调用时,如果传入的\"thisvalue\"参数是一个null和undefined除外的原始值(字符串,数字,布尔值),则this的值会成为那个原始值对应的包装对象,如果\"thisvalue\"参数的值是undefined或null,则this的值会指向全局对象。在严格模式中,this的值就是thisvalue参数的值,没有任何类型转换。\n2. arguments对象属性不与对应的形参变量同步更新\n在非严格模式中,修改arguments对象中某个索引属性的值,和这个属性对应的形参变量的值也会同时变化,反之亦然.这会让JavaScript的代码混淆引擎让代码变得更难读和理解。在严格模式中arguments 对象会以形参变量的拷贝的形式被创建和初始化,因此 arguments 对象的改变不会影响形参。\n3. eval相关的区别\n在严格模式中,eval不会在当前的作用域内创建新的变量。另外,传入eval的字符串参数也会按照严格模式来解析。你需要全面测试来确保没有代码收到影响。另外,如果你并不是为了解决一个非常实际的解决方案中,尽量不要使用eval。\n\n## 严格中立的代码\n迁移严格代码至严格模式的一个潜在消极面是,在遗留的老版本浏览器上,由于没有实现严格模式,javascript语义可能会有所不同。在一些罕见的机会下(比如差劲的关联关系或者代码最小化),你的代码可能不能按照你书写或者测试里的模式那样运行。这里有一些让你的代码保持中立的规范:\n1. 按照严格模式书写你的代码,并且确保你的代码不会发生仅仅在严格模式下发生的错误(比如上文所说的运行时错误)。\n2. 原理语义的歧义:\n * eval: 仅仅在你知道你在干什么的情况下使用它\n * arguments: 总是通过他们的名字访问函数的参数,或者作为参数对象的拷贝来使用: `var args = Array.prototype.slice.call(arguments)`,并且这样的代码应该在你的函数第一行\n * this: 只在它指向你之前创建的对象的情况下使用 this \n\n>摘自:[MDN-严格模式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode)\n>摘自:[MDN-向严格模式过渡](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode)\n","source":"_posts/严格模式-2016-01-21.md","raw":"title: 严格模式\ndate: 2016-01-21 13:55:51\ntags:\n- jQuery\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# 概述\nECMAScript 5 的严格模式是JavaScript中的一种限制性更强的变种方式。严格模式不是一个子集:它在语义上与正常代码有着明显的差异。不支持严格模式的浏览器与同支持严格模式的浏览器行为上也不一样, 所以不要在未经严格模式特性测试情况下使用严格模式。严格模式可以与非严格模式共存,所以脚本可以逐渐的选择性加入严格模式。\n支持情况:IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱。(github上面好多项目都是用的严格模式)\n严格模式在语义上与正常的JavaScript有一些不同。\n* 首先,严格模式会将JavaScript陷阱直接变成明显的错误。\n* 其次,严格模式修正了一些引擎难以优化的错误:同样的代码有些时候严格模式会比非严格模式下更快。\n* 第三,严格模式禁用了一些有可能在未来版本中定义的语法。\n<!--more-->\n# 开启严格模式\n严格模式可以应用到整个script标签或某个别函数中。不要在封闭大括弧( {} )内这样做,在这样的上下文中这么做是没有效果的。\n## 为某个script标签开启严格模式\n为整个script标签开启严格模式, 需要在所有语句之前放一个特定语句 `\"use strict\";` (或 `'use strict';`)。\n\n```javascript\n// 整个语句都开启严格模式的语法\n\"use strict\";\nvar v = \"Hi! I'm a strict mode script!\";\n```\n如果你想定义一个模块或者一个小库,自然采用一个匿名函数自执行是不错的选择。同时解决了代码压缩产生的 `\"use strict\"`压缩后不在第一行的问题。\n\n```javascript\n~function() {\n \"use strict\"; \n // Define your library strictly...\n}();\n```\n`\"use strict\"` 的位置是很讲究的,必须在首部。首部指其前面没有任何有效js代码。以下都是无效的,将不会触发严格模式。\n* “use strict” 前有代码\n* “use strict” 前有个空语句都不行\n ```javascript\n ;//空语句\n 'use strict'\n globalVar = 100\n ```\n当然,“use strict”前加注释是可以的\n\n## 为某个函数开启严格模式\n function strict() {\n // 函数级别严格模式语法\n 'use strict';\n function nested() {\n return \"And so am I!\";\n }\n return \"Hi! I'm a strict mode function! \" + nested();\n }\n## Chrome中调试严格模式\n我有这么一段代码:\n```javascript\n'use strict'\nname = \"reeoo\";\nconsole.log(name)\n```\n把这段代码直接粘贴到Chrome的控制台中执行,正常情况下应该报错,但是并没有报错。\n{% asset_img stracit.png %}\n很显然,严格模式下变量不适用var声明是不合法的,但是为什么没有报错?这是什么鬼,难道Chrome不支持严格模式?开什么玩笑。。。\n网上搜了一下,原来Chrome的控制台的代码是运行在eval之中的,你没法对eval函数使用严格模式(应该也不完全对,但是具体Chrome做了什么,不得而知),总之要想在Chrome浏览器中对严格模式正常报错,需要在代码外层套一个立即执行函数,或者其它类似的措施。\n```javascript\n(function(){\n 'use strict'\n name = \"reeoo\";\n console.log(name) \n})()\n```\n\n# 严格模式有哪些不同?\n* 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;(将问题直接转化为错误,如语法错误或运行时错误)。\n* 提高编译器效率,增加运行速度。\n* 消除代码运行的一些不安全之处,保证代码运行的安全。\n* 为未来新版本的Javascript做好铺垫。\n\n# 代码在严格模式下受到的限制\n## 全局变量显式声明\n在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。\n\n \"use strict\";\n mistypedVaraible = 17; // 报错,mistypedVaraible未声明\n## 静态绑定\nJavascript语言的一个特点,就是允许\"动态绑定\",即某些属性和方法到底属于哪一个对象,不是在编译时确定的,而是在运行时(runtime)确定的。\n具体来说,涉及以下几个方面。\n### 禁止使用with语句\n因为with语句无法在编译时就确定,属性到底归属哪个对象。\n\n \"use strict\";\n var x = 17;\n with (obj) // !!! 语法错误\n {\n // 如果没有开启严格模式,with中的这个x会指向with上面的那个x,还是obj.x?\n // 如果不运行代码,我们无法知道,因此,这种代码让引擎无法进行优化,速度也就会变慢。\n x;\n }\n### 创设eval作用域\n正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。\n严格模式创设了第三种作用域:eval作用域。\n正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。\n严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。\n\n \"use strict\";\n eval(\"var testvar = 10\");\n console.log(testvar);//在严格模式下报错,在非严格模式下 打印 10\n## 禁止删除变量\n严格模式下无法删除变量。只有configurable设置为true的对象属性,才能被删除。\n\n \"use strict\";\n var x;\n delete x; // 语法错误\n\n eval(\"var x; delete x;\"); // !!! 语法错误\n\n var o = Object.create(null, {'x': {\n value: 1,\n configurable: true\n }});\n delete o.x; // 删除成功\n## 增强的安全措施\n### 禁止this关键字指向全局对象,默认值undefined\n\n function f(){\n return !this;\n }\n // 返回false,因为\"this\"指向全局对象,\"!this\"就是false\n function f(){\n \"use strict\";\n return !this;\n }\n // 返回true,因为严格模式下,this的值为undefined,所以\"!this\"为true。\n### caller/callee 被禁用\n\n function parentCheck() {\n check(\"\");\n function check() {\n subCheck();\n function subCheck() {\n console.log(arguments.caller);\n // ECMAScript 5 还定义了 arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是 undefined。定义这个属性是为了分清arguments.caller 和函数的caller 属性。\n console.log(arguments.callee);\n console.log(subCheck.caller);\n console.log(subCheck.caller.caller);\n }\n }\n }\n parentCheck();\n \n### 显式报错\nNaN 是一个不可写的全局变量. 在正常模式下, 给 NaN 赋值不会产生任何作用; 开发者也不会受到任何错误反馈. 但在严格模式下, 给 NaN 赋值会抛出一个异常\n\n \"use strict\";\n NaN = 2;//报错\n正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。\n\n \"use strict\"; \n var o = {}; \n Object.defineProperty(o, \"v\", {\n value: 1,\n writable: false\n }); \n o.v = 2; // 报错\n严格模式下,对一个只有getter方法的属性进行赋值,会报错。\n\n \"use strict\";\n \n var o = {\n get v() {return 1; } \n }; \n o.v = 2; // 报错\n\n如果有setter 则不会报错。\n\n \"use strict\"; \n var o = {\n get v() {\n return this.aa; \n },\n set v(a){\n this.aa = a;\n } \n }; \n o.v = 2; // 不会报错\n console.log(o.v);\n \n严格模式下,对禁止扩展的对象添加新属性,会报错。\n\n \"use strict\"; \n var o = {}; \n Object.preventExtensions(o); \n o.v = 1; // 报错\n严格模式下,删除一个不可删除的属性,会报错。\n\n \"use strict\"; \n delete Object.prototype; // 报错\n## 重名错误\n### 对象不能有重名的属性\n正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。\n\n \"use strict\"; \n var o = { \n p: 1,\n p: 2 \n }; // 语法错误\n### 函数不能有重名的参数\n正常模式下,如果函数有多个重名的形参,不会报错,在严格模式下,这属于语法错误。\n\n ```javascript\n \"use strict\"; \n function f(a, a, b) {\n console.log(a,a,b);\n // Duplicate parameter name not allowed in this context \n //正常模式 输出 1 2 3\n }\n f(1,2,3);\n ```\n## 禁止八进制表示法\n正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。\n\n \"use strict\"; \n var n = 0100; // 语法错误\n## eval和arguments对象的限制\n### 赋值和绑定\n首先, arguments, eval这两个在严格模式下不能做标识符或属性名\n\n \"use strict\";\n eval = 17;\n arguments++;\n ++eval;\n var obj = { set p(arguments) { } };\n var eval;\n try { } catch (arguments) { }\n function x(eval) { }\n function arguments() { }\n var y = function eval() { };\n var f = new Function(\"arguments\", \"'use strict'; return 17;\");\n### arguments不再追踪参数的变化\n\n function f(a) { \n a = 2; \n return [a, arguments[0]]; \n } \n f(1); // 正常模式为[2,2]\n \n function f(a) { \n \"use strict\"; \n a = 2; \n return [a, arguments[0]]; \n } \n f(1); // 严格模式为[2,1]\n### 禁止使用arguments.callee\n这意味着,你无法在匿名函数内部调用自身了,可以用命名函数表达式解决这个问题。\n\n \"use strict\"; \n var f = function() {\n return arguments.callee;\n }; \n f(); // 报错\n## 函数必须声明在顶层\n将来Javascript的新版本会引入\"块级作用域\"。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。\n\n \"use strict\"; \n if (true) { \n function f() {} // 语法错误\n \n } \n for (var i = 0; i < 5; i++) { \n function f2() {} // 语法错误\n \n }\n\n## 保留字\n为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。\n使用这些词作为变量名将会报错。此外,ECMAscript第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的const保留字,也是不能作为变量名的。\n\n# 向严格模式过度\n## 逐步过渡\n严格模式被仔细设计过,因此可以逐渐地进行迁移。你可以分别改变各个文件,甚至以函数级的粒度迁移至严格模式。\n## 过程\n如果代码中使用\"use strict\"开启了严格模式,则下面的情况都会在脚本运行之前抛出`SyntaxError`异常:\n### 语法错误\n\n* 八进制语法:var n = 023和var s = \"\\047\"\n* with语句\n* 使用delete删除一个变量名(而不是属性名):delete myVariable\n* 使用eval或arguments作为变量名或函数名\n* 使用未来保留字(也许会在ECMAScript 6中使用):implements, interface, let, package, private, protected, public, static,和yield作为变量名或函数名\n* 在语句块中使用函数声明:if(a<b){ function f(){} }\n* 其他错误:\n* * 对象子面量中使用两个相同的属性名:{a: 1, b: 3, a: 7}\n* * 函数形参中使用两个相同的参数名:function f(a, b, b){}\n\n这些错误是有利的,因为可以揭示简陋的错误和坏的实践,这些错误会在代码运行前被抛出。\n\n### 运行时错误\nJavaScript曾经会在一些上下文的某些情况中静默的失败,严格模式会在这些情况下抛出错误。如果你的代码包含这样的场景,请务必测试以确保没有代码受到影响。再说一次,严格模式是可以设置在代码粒度下的。\n\n1. 给一个未声明的变量赋值\n2. 改变一个全局对象的值可能会造成不可预期的后果。如果你真的想设置一个全局对象的值,把他作为一个参数并且明确的把它作为一个属性。\n var global = this; // in the top-level context, \"this\" always refers the global object\n function f() {\n \"use strict\";\n var a = 12;\n global.b = a + x * 35;\n }\n f();\n·\n3. 尝试删除一个不可配置的属性。\n \"use strict\";\n delete Object.prototype; // error!\n 在非严格模式中,这样的代码只会静默失败,这样可能会导致用户误以为删除操作成功了.\n\n4. `arguments`对象和函数属性\n\n在严格模式下,访问`arguments.callee`, `arguments.caller`, `anyFunction.caller`以及`anyFunction.arguments`都会抛出异常.唯一合法的使用应该是在其中命名一个函数并且重用之\n\n### 语义差异\n1. 函数调用中的this\n在普通的函数调用f()中,this的值会指向全局对象。在严格模式中,this的值会指向undefined。当函数通过call和apply调用时,如果传入的\"thisvalue\"参数是一个null和undefined除外的原始值(字符串,数字,布尔值),则this的值会成为那个原始值对应的包装对象,如果\"thisvalue\"参数的值是undefined或null,则this的值会指向全局对象。在严格模式中,this的值就是thisvalue参数的值,没有任何类型转换。\n2. arguments对象属性不与对应的形参变量同步更新\n在非严格模式中,修改arguments对象中某个索引属性的值,和这个属性对应的形参变量的值也会同时变化,反之亦然.这会让JavaScript的代码混淆引擎让代码变得更难读和理解。在严格模式中arguments 对象会以形参变量的拷贝的形式被创建和初始化,因此 arguments 对象的改变不会影响形参。\n3. eval相关的区别\n在严格模式中,eval不会在当前的作用域内创建新的变量。另外,传入eval的字符串参数也会按照严格模式来解析。你需要全面测试来确保没有代码收到影响。另外,如果你并不是为了解决一个非常实际的解决方案中,尽量不要使用eval。\n\n## 严格中立的代码\n迁移严格代码至严格模式的一个潜在消极面是,在遗留的老版本浏览器上,由于没有实现严格模式,javascript语义可能会有所不同。在一些罕见的机会下(比如差劲的关联关系或者代码最小化),你的代码可能不能按照你书写或者测试里的模式那样运行。这里有一些让你的代码保持中立的规范:\n1. 按照严格模式书写你的代码,并且确保你的代码不会发生仅仅在严格模式下发生的错误(比如上文所说的运行时错误)。\n2. 原理语义的歧义:\n * eval: 仅仅在你知道你在干什么的情况下使用它\n * arguments: 总是通过他们的名字访问函数的参数,或者作为参数对象的拷贝来使用: `var args = Array.prototype.slice.call(arguments)`,并且这样的代码应该在你的函数第一行\n * this: 只在它指向你之前创建的对象的情况下使用 this \n\n>摘自:[MDN-严格模式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode)\n>摘自:[MDN-向严格模式过渡](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode)\n","slug":"严格模式","published":1,"updated":"2016-06-07T14:48:37.016Z","layout":"post","photos":[],"link":"","_id":"citf9om1e0013skv72exofdel"},{"title":"webkit的预加载扫描器","date":"2016-01-19T12:33:09.000Z","comments":1,"_content":"在WebKit中,预加载扫描器指的是一个副解析器,当HTML主解析器被一个同步的script标签阻塞时,预加载扫描器就会启动.然后,它会马上找出接下来即将需要获取的资源(比如样式表,脚本,图片等资源)的URL,然后尽可能早的发起网络请求,而不用等到主解析器恢复运行,从而提高了整体的加载时间。那么,除了HTML文件中的依赖资源,还有样式表中的呢?幸运的是,WebKit已经有了一个叫CSS预加载扫描器的东西了。\n<!--more-->\n在WebKit实现符合HTML5标准的解析器的时候,预加载扫描器被分成了两部分。其中大部分代码分出来成为了HTML预加载扫描器,剩下的一小部分成为了独立的CSS预加载扫描器。CSS预加载扫描器的任务是扫描并尽早加载样式表(且只能是style标签中内联的样式表)中的外部资源。目前,它只能扫描到@import规则中用到的外部资源。\n\n让我们看看下面这个示例,在这个代码片段中(只为演示使用,代码并不符合最佳实践),有一个script标签和一个style标签:\n\n <p>The quick brown fox jumps over the lazy dog.</p>\n <script>\n setTimeout(function () {\n document.title = document.title\n }, 1000);\n </script>\n <p>The quick brown fox jumps over the lazy dog.</p>\n <style>\n @import url(\"another-style.css\");\n body {\n background-color: white\n }\n </style>\n\n当执行到那个同步的script标签时,WebKit解析器会就会启动CSS预加载扫描器.预加载扫描器会快速的找到@import后面的URL,然后下载这个another-style.css文件。\n\nCSS预加载扫描器是非常简单的,因为它不需要解析所有的CSS语法,其中还有一个专门的代码优化,就是如果发现没有@import,扫描器会尽快跳出这个样式表,这样就能在CSS文件很大的时候节约对CPU的消耗。\n\n很多人不推荐使用@import(比如Steve Souder的不要使用@import一文)。随着现在以及未来对浏览器引擎的不断改进,这条最佳实践应该时不时的被重新考量了。当然,我并不推荐在你的网站上到处乱用@import.使用一些调试工具来分析页面的网络性能,然后你就能得出一个明智的决定。\n\n**注:**特别感谢Google的Ilya Grigorik帮我审查这篇文章。\n\n引自:[紫云飞](http://www.cnblogs.com/ziyunfei/archive/2013/04/11/3014430.html?utm_source=tuicool&utm_medium=referral)\n","source":"_posts/webkit的预加载扫描器-2016-01-19.md","raw":"title: webkit的预加载扫描器\ndate: 2016-01-19 20:33:09\ntags:\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n在WebKit中,预加载扫描器指的是一个副解析器,当HTML主解析器被一个同步的script标签阻塞时,预加载扫描器就会启动.然后,它会马上找出接下来即将需要获取的资源(比如样式表,脚本,图片等资源)的URL,然后尽可能早的发起网络请求,而不用等到主解析器恢复运行,从而提高了整体的加载时间。那么,除了HTML文件中的依赖资源,还有样式表中的呢?幸运的是,WebKit已经有了一个叫CSS预加载扫描器的东西了。\n<!--more-->\n在WebKit实现符合HTML5标准的解析器的时候,预加载扫描器被分成了两部分。其中大部分代码分出来成为了HTML预加载扫描器,剩下的一小部分成为了独立的CSS预加载扫描器。CSS预加载扫描器的任务是扫描并尽早加载样式表(且只能是style标签中内联的样式表)中的外部资源。目前,它只能扫描到@import规则中用到的外部资源。\n\n让我们看看下面这个示例,在这个代码片段中(只为演示使用,代码并不符合最佳实践),有一个script标签和一个style标签:\n\n <p>The quick brown fox jumps over the lazy dog.</p>\n <script>\n setTimeout(function () {\n document.title = document.title\n }, 1000);\n </script>\n <p>The quick brown fox jumps over the lazy dog.</p>\n <style>\n @import url(\"another-style.css\");\n body {\n background-color: white\n }\n </style>\n\n当执行到那个同步的script标签时,WebKit解析器会就会启动CSS预加载扫描器.预加载扫描器会快速的找到@import后面的URL,然后下载这个another-style.css文件。\n\nCSS预加载扫描器是非常简单的,因为它不需要解析所有的CSS语法,其中还有一个专门的代码优化,就是如果发现没有@import,扫描器会尽快跳出这个样式表,这样就能在CSS文件很大的时候节约对CPU的消耗。\n\n很多人不推荐使用@import(比如Steve Souder的不要使用@import一文)。随着现在以及未来对浏览器引擎的不断改进,这条最佳实践应该时不时的被重新考量了。当然,我并不推荐在你的网站上到处乱用@import.使用一些调试工具来分析页面的网络性能,然后你就能得出一个明智的决定。\n\n**注:**特别感谢Google的Ilya Grigorik帮我审查这篇文章。\n\n引自:[紫云飞](http://www.cnblogs.com/ziyunfei/archive/2013/04/11/3014430.html?utm_source=tuicool&utm_medium=referral)\n","slug":"webkit的预加载扫描器","published":1,"updated":"2016-01-19T13:00:03.599Z","layout":"post","photos":[],"link":"","_id":"citf9om1z0017skv7r3rww98j"},{"title":"javascript:void(0);到底是什么鬼?","date":"2016-01-29T07:03:57.000Z","comments":1,"_content":"## 疑问?\n我们经常看见`<a href=\"javascript:void(0);\">超链接</a>`的`href`里填写`javascript:void(0);`,取消点击跳转,可是为啥这么搞?\n<!--more-->\n## 规范是这么说的\n> The void Operator\n> \n> The production UnaryExpression : void UnaryExpression is evaluated as follows:\n \n> * Let expr be the result of evaluating UnaryExpression.\n> * Call GetValue(expr).\n> * Return undefined.\n> NOTE: GetValue must be called even though its value is not used because it may have observable side-effects\n\n搬译一下:\n\n> void操作符\n\n> 产生式 UnaryExpression : void UnaryExpression 按如下流程解释:\n\n> * 令 expr 为解释执行UnaryExpression的结果。\n> * 调用 GetValue(expr).\n> * 返回 undefined.\n> 注意:GetValue一定要调用,即使它的值不会被用到,但是这个表达式可能会有副作用(side-effects)。\n\n重点在于:无论void后的表达式是什么,void操作符都会返回undefined。\n\n## 用处\n### 替换`undefined`\n\n因为undefined在javascript中不是保留字。换言之,你可以写出:\n\n {% codeblock lang:javascript %}\n function joke() {\n var undefined = \"hello world\";\n console.log(undefined); //会输出\"hello world\"\n }\n joke()\n {% endcodeblock %}\n对的,你可以在一个函数上下文内以undefined做为变量名,于是在这个上下文写的代码便只能通过从全局作用域来取到undefined,如:\n \n {% codeblock lang:javascript %}\n window.undefined //浏览器环境\n GLOBAL.undefined //Node环境\n {% endcodeblock %}\n \n但要注意的是,即便window, GLOBAL仍然可以在函数上下文被定义,故从window/GLOBAL上取undefined并不是100%可靠的做法。如:\n\n {% codeblock lang:javascript %}\n function x() {\n var undefined = 'hello world',\n f = {},\n window = {\n 'undefined': 'joke'\n };\n console.log(undefined);// hello world\n console.log(window.undefined); //joke\n console.log(f.a === undefined); //false\n console.log(f.a === void 0); //true\n }\n x();\n {% endcodeblock %}\n\n于是,采用void方式获取undefined便成了通用准则。如underscore.js里的isUndefined便是这么写的:\n\n {% codeblock lang:javascript %}\n _.isUndefined = function(obj) {\n return obj === void 0;\n }\n {% endcodeblock %}\n \n除了采用void能保证取到undefined值以外,还有其它方法吗?有的,还有一种方式是通过函数调用。如AngularJS的源码里就用这样的方式:\n \n {% codeblock lang:javascript %}\n (function(window, document, undefined) {\n //.....\n })(window, document);\n {% endcodeblock %}\n\n通过不传参数,确保了undefined参数的值是一个undefined。\n\n### 填充a标签href、image标签的src\n\n* `<a href=\"javascript:void(0)\">test</a>`\n* `<image src=\"javascript:void(0)\">` \n\n## GetValue 是什么鬼?\n\n> 注意:GetValue一定要调用,即使它的值不会被用到,但是这个表达式可能会有副作用(side-effects)。\n\n(关于js中void,既然返回永远是undefined,那么GetValue有啥用?)[https://www.zhihu.com/question/22210634]\n\n {% codeblock lang:javascript %}\n var happiness = 10;\n var girl = {\n get whenMarry() {\n happiness--;\n return 1/0; //Infinity\n },\n get happiness() {\n return happiness;\n }\n };\n \n console.log(girl.whenMarry); //调用了whenMarry的get方法\n console.log(girl.happiness); // 9\n \n void girl.whenMarry; //调用了whenMarry的get方法\n console.log(girl.happiness); // 8\n \n delete girl.whenMarry; //没有调用whenMarry的get方法\n console.log(girl.happiness); //还是8\n {% endcodeblock %}\n \n上述代码定义了一个大龄文艺女青年,每被问到什么时候结婚呀(whenMarry),happiness都会减1。从执行情况可以看出,无论是普通访问girl.whenMarry,还是void girl.whenMarry都会使她的happiness--。而如果把void换成delete操作符写成delete girl.whenMarry,她的happiness就不会减了,因为delete操作符不会对girl.whenMarry求值。\n\n## 总结\nvoid(0) 有如下作用:\n\n* 通过采用void 0取undefined比采用字面上的undefined更靠谱更安全,应该优先采用void 0这种方式。\n* 填充<a>的href确保点击时不会产生页面跳转; 填充<image>的src,确保不会向服务器发出垃圾请求。\n\n转自:[谈谈Javascript中的void操作符](http://segmentfault.com/a/1190000000474941)","source":"_posts/void-0-2016-01-29.md","raw":"title: javascript:void(0);到底是什么鬼?\ndate: 2016-01-29 15:03:57\ntags:\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n## 疑问?\n我们经常看见`<a href=\"javascript:void(0);\">超链接</a>`的`href`里填写`javascript:void(0);`,取消点击跳转,可是为啥这么搞?\n<!--more-->\n## 规范是这么说的\n> The void Operator\n> \n> The production UnaryExpression : void UnaryExpression is evaluated as follows:\n \n> * Let expr be the result of evaluating UnaryExpression.\n> * Call GetValue(expr).\n> * Return undefined.\n> NOTE: GetValue must be called even though its value is not used because it may have observable side-effects\n\n搬译一下:\n\n> void操作符\n\n> 产生式 UnaryExpression : void UnaryExpression 按如下流程解释:\n\n> * 令 expr 为解释执行UnaryExpression的结果。\n> * 调用 GetValue(expr).\n> * 返回 undefined.\n> 注意:GetValue一定要调用,即使它的值不会被用到,但是这个表达式可能会有副作用(side-effects)。\n\n重点在于:无论void后的表达式是什么,void操作符都会返回undefined。\n\n## 用处\n### 替换`undefined`\n\n因为undefined在javascript中不是保留字。换言之,你可以写出:\n\n {% codeblock lang:javascript %}\n function joke() {\n var undefined = \"hello world\";\n console.log(undefined); //会输出\"hello world\"\n }\n joke()\n {% endcodeblock %}\n对的,你可以在一个函数上下文内以undefined做为变量名,于是在这个上下文写的代码便只能通过从全局作用域来取到undefined,如:\n \n {% codeblock lang:javascript %}\n window.undefined //浏览器环境\n GLOBAL.undefined //Node环境\n {% endcodeblock %}\n \n但要注意的是,即便window, GLOBAL仍然可以在函数上下文被定义,故从window/GLOBAL上取undefined并不是100%可靠的做法。如:\n\n {% codeblock lang:javascript %}\n function x() {\n var undefined = 'hello world',\n f = {},\n window = {\n 'undefined': 'joke'\n };\n console.log(undefined);// hello world\n console.log(window.undefined); //joke\n console.log(f.a === undefined); //false\n console.log(f.a === void 0); //true\n }\n x();\n {% endcodeblock %}\n\n于是,采用void方式获取undefined便成了通用准则。如underscore.js里的isUndefined便是这么写的:\n\n {% codeblock lang:javascript %}\n _.isUndefined = function(obj) {\n return obj === void 0;\n }\n {% endcodeblock %}\n \n除了采用void能保证取到undefined值以外,还有其它方法吗?有的,还有一种方式是通过函数调用。如AngularJS的源码里就用这样的方式:\n \n {% codeblock lang:javascript %}\n (function(window, document, undefined) {\n //.....\n })(window, document);\n {% endcodeblock %}\n\n通过不传参数,确保了undefined参数的值是一个undefined。\n\n### 填充a标签href、image标签的src\n\n* `<a href=\"javascript:void(0)\">test</a>`\n* `<image src=\"javascript:void(0)\">` \n\n## GetValue 是什么鬼?\n\n> 注意:GetValue一定要调用,即使它的值不会被用到,但是这个表达式可能会有副作用(side-effects)。\n\n(关于js中void,既然返回永远是undefined,那么GetValue有啥用?)[https://www.zhihu.com/question/22210634]\n\n {% codeblock lang:javascript %}\n var happiness = 10;\n var girl = {\n get whenMarry() {\n happiness--;\n return 1/0; //Infinity\n },\n get happiness() {\n return happiness;\n }\n };\n \n console.log(girl.whenMarry); //调用了whenMarry的get方法\n console.log(girl.happiness); // 9\n \n void girl.whenMarry; //调用了whenMarry的get方法\n console.log(girl.happiness); // 8\n \n delete girl.whenMarry; //没有调用whenMarry的get方法\n console.log(girl.happiness); //还是8\n {% endcodeblock %}\n \n上述代码定义了一个大龄文艺女青年,每被问到什么时候结婚呀(whenMarry),happiness都会减1。从执行情况可以看出,无论是普通访问girl.whenMarry,还是void girl.whenMarry都会使她的happiness--。而如果把void换成delete操作符写成delete girl.whenMarry,她的happiness就不会减了,因为delete操作符不会对girl.whenMarry求值。\n\n## 总结\nvoid(0) 有如下作用:\n\n* 通过采用void 0取undefined比采用字面上的undefined更靠谱更安全,应该优先采用void 0这种方式。\n* 填充<a>的href确保点击时不会产生页面跳转; 填充<image>的src,确保不会向服务器发出垃圾请求。\n\n转自:[谈谈Javascript中的void操作符](http://segmentfault.com/a/1190000000474941)","slug":"void-0","published":1,"updated":"2016-01-29T07:40:34.743Z","layout":"post","photos":[],"link":"","_id":"citf9om2c001askv783qfs7ip"},{"title":"numberInJavaScript","date":"2016-06-19T13:51:58.000Z","comments":1,"_content":"# 思考\n先想几个问题吧:\n1. JavaScript的数字为什么有0和-0?\n2. JavaScript中的NaN为什么互不相等?\n3. JavaScript中的数字真的只有一种类型吗?\n4. JavaScript中常被诟病的0.3 - 0.2 == 0.1原因是什么?\n5. 数组的最大长度是多少?为什么是这个值?\n\n上述问题,只有在JavaScript中有吗?\n<!--more-->\n当下,计算机如此普及,我相信,即便非程序员也了解:计算机的世界只有`0`和`1`。而一个程序员应该了解:`0/1`组成的东西叫机器码,有原码, 反码, 补码等。而一个JS程序员应该了解:JS中的数字是不分类型的,也就是没有`byte/int/float/doubl`e等的差异。而一个稍微研究ES规范的JS程序员应该了解:JS的`number`是IEEE 754标准下64-bits的双精度数值,而且ES中有`ToInteger/ToInt32/ToUint32/ToUint16`等`Type Conversion`。下面,我们就尝试着讨论一下这些。\n# 码\n从硬件的角度上讲,维护两个状态是相对容易的,比如一个二极管的导通或者截止,一个电脉冲的高或者低,从而在实现集成电路时候可以更加简单高效,所以计算机普遍使用0和1来存储和计算。那么,只有`0`和`1`,如何表示1234567890呢?这就涉及到`机器码`和`真值`。\n## 机器码和真值\n* 所谓`机器码`是指,整数在计算机中二进制形式。规则很简单,机器码的最高位(左第一位)表示数字的正负,`0`表示正数,`1`表示负数,其余位按照进制转换的规则表示具体数字。\n* 所谓`真值`是指,机器码按照上述转换规则还原的带有正负的实际整数。\n\n举例而言,用8-bits表示一个整数,则十进制的整数`+6`可表示为:`00000110`;十进制的数字`-5`可表示为`10000101`。这里说的+6和-5便是真值,而表示它们的二进制数便是机器码。再次注意,最高位只用于表示正负,比如`10000101`的真值是`-5`而非`133`,以及我们关于机器码和真值的讨论是基于整数范围的,浮点数在计算机中的存储方式与整数有很大差值,将另作讨论。\n\n有了机器码,我们便可以在计算机中使用机器码存储和计算真值,那么机器码在计算机中是如何计算的呢?\n## 原码、反码、补码\n机器码分为多种,主要包括`原码、反码、补码、移`码等,今天我们主要总结一下前三个,而移码非常简单,且多用于比较,不做详细说明。另外需要补充一点,我们在此区分机器码的这么多种形式,主要是针对的有符号数,而无符号数,不需要使用最高位来表示正负,也就不需要这么多种编码方式。\n\n### 原码\n最高位表示正负,其它位表示真值的绝对值。其中,最高位为`0`表示`正数`或者`0`,为`1`表示负数。\n比如,同样以8bits长度的数串表示`+7`的原码为`0000 0111`,-7的原码为`10000111`。以后,我们会这样表示:\n```javascript\n [+7] = [00000111]原\n [-7] = [10000111]原\n```\n很明显,8-bits的原码能记录的范围为:`[-127,+127]`。\n原码的好处在于,易于理解,相对直观,方便人脑识别和计算。\n对于原码,人脑使用,可以直接计算出其真值然后可以进行后续操作。但对于计算机,\n首先,因为最高位用于表示正负,所以不能直接参与运算,需要识别然后做特殊处理;\n其次,具体计算使用绝对值进行操作,所以两个操作数正负的异同会影响操作符,比如两个异号相加实际要做减法操作,甚至异号相减还需要判断绝对值大小然后决定结果正负。\n如此,我们计算机的运算器设计将会变得异常复杂。下面,我们将了解如何使用反码和补码将符号位参与运算,从而使加减法统一简单高效地处理,这也是反码和补码出现的原因。\n### 反码\n正数的反码等于其原码,而`负数的反码则是对其原码进行符号位不变,其它位逐一取反的结果`。\n比如,同样以8-bits长度的数串表示`+7`,那么有如下:\n```javascript\n [+7] = [00000111]原 = [00000111]反\n [-7] = [10000111]原 = [11111000]反\n```\n同样,8-bits的反码能记录的范围为:`[-127,+127]`。\n在按位取反之后,我们可以有下面的操作:\n```javascript\n 2 - 3 = 2 + (-3) \n = [00000010]原 + [10000011]原 \n = [00000010]反 + [11111100]反 \n = [11111110]反 \n = [10000001]原 \n = -1\n```\n上面,我们将减法通过反码转化为了加法,如此,我们的运算将会简单很多,但是反码的方式同样存在一些问题:\n```javascript\n 3 - 3 = 3 + (-3) \n = [00000011]原 + [10000011]原 \n = [00000011]反 + [11111100]反 \n = [11111111]反 \n = [10000000]原 \n = -0\n```\n出现了`-0`,这个值是没有意义的。另外,按照`反码加法法则,如果最高位有进位,需要在最低位上+1`,那么会出现:\n```javascript\n 3 - 2 = 3 + (-2) \n = [00000011]原 + [10000010]原 \n = [00000011]反 + [11111101]反 (这里最高位有进位,需要在最低位+1) \n = [00000001]反 \n = [00000001]原 \n = 1\n```\n这种情况,又增加了反码运算的复杂性,影响效率,为解决上面的问题,出现了补码。\n### 补码\n正数的补码等于其原码,而负数的补码则是对其反码进行末位加1的结果。\n```javascript\n [+7] = [00000111]原 = [00000111]反 = [00000111]补\n [-7] = [10000111]原 = [11111000]反 = [11111001]补\n```\n使用补码,继续做之前的操作:\n```javascript\n 2 - 3 = 2 + (-3) \n = [00000010]原 + [10000011]原 \n = [00000010]反 + [11111100]反 \n = [00000010]补 + [11111101]补 \n = [11111111]补 \n = [11111110]反 \n = [10000001]原 \n = -1\n```\n那么,如果是`3-3`呢?\n```javascript\n 3 - 3 = 3 + (-3) \n = [00000011]原 + [10000011]原 \n = [00000011]反 + [11111100]反 \n = [00000011]补 + [11111101]补 \n = [00000000]补 \n = [00000000]原 \n = 0\n```\n是否还需要做额外的加法操作?\n```javascript\n 3 - 2 = 3 + (-2) \n = [00000011]原 + [10000010]原 \n = [00000011]反 + [11111101]反 \n = [00000011]补 + [11111110]补 \n = [00000001]补 \n = [00000001]原 \n = 1\n```\n这样,我们便可以完美的将减法统一到加法之上,而且不需要繁琐的正负判断,进位控制,甚至可以节约一个位置。\n那么,这个位置,也就是`10000000`如何处理呢?\n`按照规定,10000000用来表示-128`,正数的补码/反码/原码相同,而负数的补码只是占用了-0的`[10000000]原`和`[11111111]反`转换后得到的`[10000000]补`表示-128,但是这个只是帮助理解,不能反向回推得到`-128`的原码和补码。\n所以,8bits的补码能记录的范围为:`[-128,+127]`。\n至此,我们已经了解了,计算机中主要使用的存储和计算整数的方式,鉴于现代计算机主要使用补码方式,自然能很容易理解各种数字类型的表示范围,比如32bits的int范围为:`[-231,231-1]`。这对于我们后面理解一些JavaScript中的极端情况至关重要。\n\n#### 补码10000000为什么可以表示-128?\n有知乎大神这么理解:\n作者:fhylhl 链接:http://www.zhihu.com/question/28685048/answer/41735701 来源:知乎\n> 很多人并不理解补码。补码就是同余啊。1000000是正128你知道吧,正负128模256是同余的。加减乘可以直接算也是同余的定理决定的,而不是凑出来的巧合,哪可能凑出这种东西?\n 8位只能表示256个数,0到255,但我还想表示一些负数怎么办呢?就用与该负数同余的正数来表示呗。-1=255,-2=254,等等。\n 建议脱离算数的思维方式,这其实就是一个环。模任何一个正整数(如256),可以把所有整数分类,比如模256可分256类,0 256 -256...是一类(余0类),1 257 -255...是一类(余1类),等等,这256类可看作环的元素,你看-128和128是同一个类里的(余128类),用一个代表另一个罢了。补码和普通的unsigned integers都是在每类中选一个数,unsigned integers选0到255,补码表示的有符号整数选-128到127,都是一个数恰好对应一个类。当你明白这一切后,补码就是顺理成章的事。\n\n>> 同余:数论中的重要概念。给定一个正整数m,如果两个整数a和b满足(a-b)能够整除m,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。对模m同余是整数的一个等价关系。\n\n## IEEE 754标准\n作为一个JavaScript程序员,我们只有一个Number,所以我们从一开始就习惯了:\n```javascript\n var num1 = 123;\n var num2 = 1.23;\n```\n但是,你知道JS的number是`IEEE 754`标准的64-bits的双精度数值吗?这是一个什么样的标准?使用这个标准的64-bits双精度意味着什么?所以,要掌握JavaScript中的数字,我们首先得了解`IEEE 754`标准。下面,我将尝试说明一下这个标准,为我们最后学习JavaScript中的数字做铺垫。\n\n## 标准的基本原理:\n我们知道,对于计算机而言,数字没有小数和整数的差别,也就是计算机中没有小数点的存在。通过前文的讨论,我们已经找到了很完美的整数存储计算的方案,但是当涉及到小数,我们很容易发现,现有的方案无法解决我们的需求。然后,计算机科学家们便尝试了多种方案,主要便是`定点数`和`浮点数`两种。\n* 所谓定点数,是指小数点位置固定在数串中间的某个特定位置,点两侧分别为数字的整数和小数部分。比如用8-bits字长的数串,小数点固定在正中间位置,那么`11001001`和`00110101`分别表示`1100.1001`和`11.0101`两个数字。这种方案简单直观易理解,但是存在严重的空间浪费,以及容易溢出的问题。\n* 所谓浮点数,是指小数点的位置是不固定的,通过科学计数法(这个应该不需要解释吧)的方式控制小数点的位置,表示不同的数字。这个表示方案便是IEEE 754标准使用的方案。IEEE 754标准是目前使用最广泛的浮点数运算标准。下面我们将主要讨论一下此方案。\n\n现在,让我们想一下小时候学习的科学计数法,比如`-123.456`这个数字,转换成科学计数法应该是:`-1.23456 × 10^2`。这里面已经包含了IEEE 754标准的主要元素。我们梳理一下:第一个,自然是正负号的问题,需要一个标志;然后,需要一个具体的数字,表示有效数字或者精度,如上例的`1.23456`;再然后,需要一个控制小数点位置的数字,如上例的`10^2`,回忆一下,我们学习科学计数法的时候,要求前面的数字的绝对值大于1而小于10,也就是小于10^2中的底数(Base),进制固定之后,底数应该是固定的,所以这里起决定作用的是指数,也就是上例中的2。那么,有了这三个元素,我们便可以很轻松的表示出一个数字,并且灵活的调节小数点位置从而控制数字正负、精度和大小。\n\n上面的要素,转换成标准语言描述,我们称表示正负的标志叫`符号(Sign)`,表示精度的数字为`尾数(Mantissa)`或者`有效数字(Significand)`,而控制小数点位置的指数就叫`指数(Exponent)`,指数和`基数(Base)`共同作用参与计算。下图取自wikipedia,我们直观地感受下这三个要素在一个数串中的相对关系(fraction区域即等同于前面说的有效数字区域):\n{% asset_img 123.png %}\n了解最基本的原理后,我们来大致看一下`IEEE 754`标准做了什么。\n首先做的事情就是规定这三个要素在一个数串中占有的位数,试想一下,如果各个实现的位数不确定,那么我们是不是很难正确的还原出原始数字?`IEEE 754`标准规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80比特实做)。只有32位模式有强制要求,其他都是选择性的。而现在主流的语言,多提供了单精度和双精度的实现,我们在此主要比较一下这两者,如图是它们各个部分对应上图,所使用的位数如下:\n{% asset_img 456.png %}\n{% asset_img 789.png %}\n补充一点的是,无论是科学计数法还是标准的规定,都要求有效数字(不考虑符号位)必须`>=1 && <Base`。所以,有效数字其实是一个定点数,小数点的位置固定在有效数字域的最高位和次高位之间。那么,按照上述规定,在二进制中,最高位只能是`1`,所以标准要求省略其最高位,于是精度提高一位。比如,`32-bits`的单精度有效数字区域只有23位,但是精度却是24位;`64-bits`的双精度,拥有52位的有效数字域却是53位精度的。\n\n然后,还有一个问题,如果按照先有的约定,`是不是无法表示小于1的实数?`因为,`指数一定>=0,有效数字一定>1`。于是,`IEEE 754`标准提出了一个很重要的指数偏移值。它是说明指数域(Exponent占用的区域)的编码值为指数的实际值加上某个固定的值,换言之便是,如果我们根据指数域计算出的指数是`N`,那么参与计算实际浮点数的指数应该是`N-指数偏移值`。根据IEEE 754标准的规定,该固定值为`2^(e-1) - 1`,其中的`e`为存储指数的比特的长度。比如,从上图中我们看到,`32-bits`的单精度是以`8-bits表示一个指数域`,那么偏移值应该是`2^(8-1) - 1 = 128−1 = 127`。所以,容易得出,单精度浮点数的指数部分实际取值是`全零00000000 0-127=-127`到`全零11111111 256-127=128`就是`[-127,128]`。比如,某个`32-bits`单精度的指数为十进制的`1`,那么指数域的编码应该是`10000001`,某个32-bits单精度的指数域编码是`00000001`,那么该指数的实际值应该是十进制的`-126`。这样,我们就能通过偏移值将正指数转换为负指数,从而`使浮点数能逼近0`。浮点数的指数计算跟前面讨论的机器码恰好相反,`正数的最高位都是1,而负数的最高位都是0`。\n\n以上的描述,便是IEEE 754标准最需要我们了解的原理部分,但是,作为一个广泛使用的工业标准,规定这些还是远远不够的。\nwikipedia(维基百科)对IEEE 754标准有如下描述:\n>这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。\n\n下面,补充几个,我认为与本文后续讨论相关的或者可以帮助大家理解极端现象的定义:\n`规约形式的浮点数`:如果浮点数中指数部分的编码值在`0 < exponent < 2^(e-1)`之间,且尾数部分最高有效位(即整数)是1,那么这个浮点数将被称为规约形式的浮点数。也就是,严格按照我们上文描述编码的数字。\n`非规约形式的浮点数`:如果浮点数的指数部分的编码值是`0`,尾数为非零,那么这个浮点数将被称为非规约形式的浮点数。`IEEE 754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值大1`。例如,最小的规约形式的单精度浮点数的指数部分编码值为`1`,指数的实际值为`-126`;而非规约的单精度浮点数的指数域编码值为`0`,对应的指数实际值也是`-126`而不是`-127`。实际上非规约形式的浮点数仍然是有效可以使用的,只是它们的绝对值已经小于所有的规约浮点数的绝对值;即所有的非规约浮点数比规约浮点数更接近0。规约浮点数的尾数大于等于`1`且小于`2`,而非规约浮点数的尾数小于`1`且大于`0`。\n上面的两个概念,几乎是直接从wikipedia上扒下来的,·非规约形式的浮点数·出现的意义是避免突然式下溢出(abrupt underflow),而采用渐进式下溢出。这已经是上世纪70年代的事情了,差不多是我的年龄的两倍了。这个是一些非常极端的情况,在此我尝试最简单地描述一下`非规约形式的浮点数`出现的意义,知道有这么回事便可:下面,以单精度为例,如果没有`非规约形式的浮点数`,那么绝对值最小的两个相邻的浮点数之间的差值将是绝对值最小的浮点数的2^23分之一,大家想一下,绝对值次小的浮点数减去绝对值最小的浮点数的值是多少?\n\n```javascript\n 1.00...01 × 2^(-126) - 1.00...00 × 2^(-126) = 0.00..01 × 2^(-126) \n = 1 × 2^(-126-23)\n = 2^(-149)\n```\n很明显,绝对值最小的`规约数`无法表达其和次小的`规约数`的差值,所以很容易导致有若干数字之间的差值下溢,可能会触发意料之外的后果。而如果采用非规约形式的浮点数,指数全0,偏移值比规约数偏移值大1(`-126`比`-127`大1),尾数小于`1`,那么非规约数能表达的最小值便是:\n```javascript\n 0.00..01 × 2^(-126) = 1 × 2^(-126-23)\n = 2^(-149)\n```\n所以,`非规约形式的浮点`数解决了前述的`突然式下溢出(abrupt underflow)`而被标准采纳。\n`IEEE 754`标准还规定了三个特殊值:\n* 指数全0且尾数小数部分全0,则这个数字为±0。(符号位决定正负)\n* 指数为2^e - 1且尾数的小数部分全0,这个数字是±∞。(符号位决定正负)\n* 指数为2^e - 1且尾数的小数部分非0,这个数字是NaN。\n结合前面的规约数,非规约数以及三个特殊值,可以得到如下总结:\n{% asset_img 10.png %}\n现在,让我们回忆一下,各种语言中普遍描述的双精度浮点数的范围:`[-1.7 × 10^(-308),1.7 × 10^308]`。打个岔,想象一个有300多位的十进制数字的适用情形,私以为远超过普通人想象力的边界。这个范围为什么是这个范围呢?我觉得,通过上面的讨论,大家应该能清晰,1.7/308这些数字出现的必然原因。\n首先,我们应该很容易根据偏移量得出双精度浮点数的计算公式:\n{% asset_img 11.png %}\n然后,以正数为例,按照上述特殊值中`±∞`和`NaN`的约定,指数的最大值应该满足指数取规约数的指数范围的最大值,然后小数部分取小数部分的最大值,可以得出这个二进制的数字应该是:\n 0 11111111110 11..11(52个)\n转换为16进制表示:\n0x7fef ffff ffff ffff\n那么,根据前述规约数的原理,反编码便得到十进制的:`1.7976931348623157 x 10^308`。类似的道理,Sign位取反,便是范围的下限。\n到此为止吧,我对`IEEE 754`标准也是最近几天稍加学习,再说多了就误导大家了。通过这几天的学习,我感觉,我们在理解的IEEE 754标准及浮点数的时候,要特别注意将精度和范围两个概念分别开来。范围只是一个模糊的界限,精度才是能准确表达的数字。\n\n## 回到JavaScript\n在上面的讨论中,我们很少提及JavaScript,似乎有点背离今天的主题了,但是,在了解了前述的原理之后,我们对JavaScript中数字的把握将”水到渠成”。这终将是一次,铺垫多于正文,开胃菜多于正餐的讨论。嗯,快喊小伙伴,正餐开始了!\n### ES的”The Number Type”:\n现在,我们打开ES规范的[The Number Type](http://es5.github.io/#x8.5) 是不是基本通读下来了?\n比如:\n> The Number type has exactly 18437736874454810627 (that is, 2^64 − 2^53 + 3) values…\n为什么是这个数字?因为,我们说JavaScript中的数字是`64-bits`的双精度,所以首先有`2^64`中可能的组合,然后,按照前述的`IEEE 754`标准的标准中的特殊值中的部分,`NaN`和`±∞`占用了`2^53`个数值,但是表示了三个直观的量,所以,加减一下,自然就是`18437736874454810627` `(that is, 2^64 − 2^53 + 3)` values。\n> …the 9007199254740990 (that is, 2^53−2) distinct “Not-a-Number” values…\n为什么这么多`NaN`?同样,按照前述的`IEEE 754`标准的标准中的特殊值中的部分,`NaN`使用了`Significand非零`、`指数是特定2^e-1且Sign无要求的所有可能`,即`2^53`减去`±∞`两种情况。\n>…e is an integer ranging from −1074 to 971…\n为什么指数的范围是这个呢?而不是`-1022到+1022`呢?因为,ES演化了一下公式,对比一下我们之前演示`64-bits`的公式,关于参与计算的`mantissa`,我们按照`IEEE 754`标准在演示的时候中使用的是`1.m`,而ES规范中使用的是`m`,当然会有尾数域bit长度的差异了。\n\n到这里,关于数字,大概就可以结束了。开篇的几个问题,相信读到这里的同学,都能有答案了。但是,还有一个问题,JavaScript中的数字真的只有一种类型吗?,而且貌似到现在与我们的初衷,理解`>>>`有点偏离了。不过,世界上很多事情往往都是这样,解释原理需要到口干舌燥,而用原理去解释现象却只需要三言两语。\n\n### JavaScript不是只有64-bits的双精度\n是的,小标题已经回答了我们的问题,JavaScript不是只有`64-bits`的双精度。我们通篇都在说JavaScript中数字的各种,一直按照`64-bits`的双精度来描述,但是,如之前所说,ES中有`ToInteger/ToInt32/ToUint32/ToUint16`等Type Conversion。这些Type Conversion不是我们直接调用的API,而是语言引擎在进行某些特定操作的时候,替我们做的。这种“隐形的操作”,只有在一些极端的情况下,会表现出来。现在,我们可以到`“ToInt32”/“ToUint32”/“ToInt16”`三个地方看一下,稍作比较便能发现,他们的差异很小,只是在特定的步骤中存在差异。比如,`ToUint32和ToUint16`的差异仅仅操作的最后一步存在差异,按顺序列出比较一下:\n\n> Let int32bit be posInt modulo 2^32; that is, a finite integer value k of Number type with positive sign and less than 2^32 in magnitude such that the > mathematical difference of posInt and k is mathematically an integer multiple of 2^32.\n> \n>Return int32bit.\nvs\n> Let int16bit be posInt modulo 2^16; that is, a finite integer value k of Number type with positive sign and less than 2^16 in magnitude such that the mathematical difference of posInt and k is mathematically an integer multiple of 2^16.\n>\n>Return int16bit.\n\n比较一下,不难发现,仅仅是`2^32`和`2^16`的差异,而关键点恰是`modulo`操作的时候,按照我们之前讨论的原理,很容易理解这个操作决定了可能出现的最大数。这样的比较,有一好处,能提高我们阅读标准的速度,而且加深理解,对掌握标准很有帮助。\n总结一下这三个操作的范围:\n\n* ToInt32的范围便是其它强类型语言中的[-2^31, -2^31 - 1]。\n\n* ToUint32的范围便是其它强类型语言中的[0, -2^32 - 1]。\n\n* ToUint16的范围便是其它强类型语言中的[0, -2^16 - 1]。\n通过搜索,很容易能找到,JavaScript中那些操作中使用了上述相关的操作。其中,`ToUint16`仅仅在`String.fromCharCode`中有使用,我们不做讨论了。`ToInt32`有在多个位运算符中使用,比如`~` `/` `<<` `/` `>>`,以及在`parseIn`t也有使用。而`ToUint32`的使用则出现在了大量的地方,主要分布在`,数组相关的操作,位运算的操作`两个区域。\n我们就借`ToUint32`的这些使用,回到开篇讨论的那个地方吧:\n首先,来到这里[>>>](http://es5.github.io/#x11.7.3),看到操作如下:\n> 1. Let lref be the result of evaluating ShiftExpression.\n> \n> 2. Let lval be GetValue(lref).\n> \n> 3. Let rref be the result of evaluating AdditiveExpression.\n>\n> 4. Let rval be GetValue(rref).\n>\n> 5. Let lnum be ToUint32(lval).\n> \n> 6. Let rnum be ToUint32(rval).\n> \n> 7. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.\nReturn the result of performing a zero-filling right shift of lnum by shiftCount bits. Vacated bits are filled with zero. The result is an unsigned 32-bit integer.\n>\n再看new Array (len),有一句:\n> If the argument len is a Number and ToUint32(len) is equal to len, then the length property of the newly constructed object is set to ToUint32(len). If the argument len is a Number and ToUint32(len) is not equal to len, a RangeError exception is thrown.\n对比不难发现,`>>>`的返回值和`array.length`的取值范围,无差异,经过`>>>`操作后的数字,一定是一个合法的`array.length`。解释原理总是那么复杂,可是用原理解释现象总是那么简单。\n\n## 将实数转换成浮点数\n\n### 浮点数的规范化\n同样的数值可以有多种浮点数表达方式,比如上面例子中的 123.45 可以表达为 12.345 × 101,0.12345 × 103 或者 1.2345 × 102。因为这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式:`±d.dd...d × βe , (0 ≤ d i < β)`。\n其中 d.dd...d 即尾数,β 为基数,e 为指数。尾数中数字的个数称为精度,在本文中用 p 来表示。每个数字 d 介于 0 和基数之间,包括 0。小数点左侧的数字不为 0。\n基于规范表达的浮点数对应的具体值可由下面的表达式计算而得:`±(d 0 + d 1β-1 + ... + d p-1β-(p-1))βe , (0 ≤ d i < β)`\n对于十进制的浮点数,即基数 β 等于 10 的浮点数而言,上面的表达式非常容易理解,也很直白。计算机内部的数值表达是基于二进制的。从上面的表达式,我们可以知道,二进制数同样可以有小数点,也同样具有类似于十进制的表达方式。只是此时 β 等于 2,而每个数字 d 只能在 0 和 1 之间取值。比如二进制数 1001.101 相当于 1 × 2<sup>3</sup> + 0 × 2<sup>2</sup> + 0 × 2<sup>1</sup> + 1 × 2<sup>0</sup> + 1 × 2<sup>-1</sup> + 0 × 2<sup>-2</sup> + 1 × 2<sup>-3</sup>,对应于十进制的 9.625。其规范浮点数表达为 1.001101 × 2<sup>3</sup>。\n\n### 根据精度表示浮点数\n\n问:要把小数装入计算机,总共分几步?你猜对了,3 步。\n\n* 第一步:转换成二进制。\n* 第二步:用二进制科学计算法表示。\n* 第三步:表示成 IEEE 754 形式。\n\n#### 如何把十进制小数转换成二进制小数。\n十进制小数转换成二进制小数采用\"乘2取整,顺序排列\"法。\n具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数 部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。\n* 如:25.7=(11001.10110011001100110011001100110011...)B\n * 整数部分:\n 25%2 = 1 ======== 1\n 12%2 = 0 ======== 0\n 6%2 = 3 ======== 0\n 3%2 = 1 ======== 1\n 1%2 = 1 ======== 1\n * 小数部分:\n 0.7*2=1.4========取出整数部分1 \n 0.4*2=0.8========取出整数部分0 \n 0.8*2=1.6========取出整数部分1 \n 0.6*2=1.2========取出整数部分1 \n 0.2*2=0.4========取出整数部分0 \n 0.4*2=0.8========取出整数部分0 \n 0.8*2=1.6========取出整数部分1 \n 0.6*2=1.2========取出整数部分1 \n 0.2*2=0.4========取出整数部分0 \n * 最后结果就是: \n 11001.1 0110 0110 0110 0110 0110 0110 0110 0110 0110...\n \n#### 用二进制科学计算法表示\n 11001.101100110 == 1.1001101100110 * 2<sup>4</sup>\n#### 表示成 IEEE 754 形式\n1. 正数 固符号位为 0 \n2. 尾数 由于第一位使用是1,固取(首位1干掉了).10011 0110 0110 0110 0110 0110 0110 0110 0110 0110\n3. 指数 4 + 1023(偏移量),转换为 二进制就是 10000000011\n组合在一起就是 0 10000000011 10011 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 011\n\n> 单精度32位的 偏移量是 Math.pow(2,8)/2 -1 == 127 ,双精度64位的偏移量是 Math.pow(2,11)/2 -1 == 1023 \n\n### 哪些数能精确表示?\n那么 0.1 在计算机中可以精确表示吗?答案是出人意料的, 不能。\n\n在此之前,先思考个问题: `在 0.1 到 0.9 的 9 个小数中,有多少可以用二进制精确表示呢?`\n\n我们按照乘以 2 取整数位的方法,把 0.1 表示为二进制(我假设那些不会进制转换的同学已经补习完了):\n\n(1) 0.1 x 2 = 0.2 取整数位 0 得 0.0\n(2) 0.2 x 2 = 0.4 取整数位 0 得 0.00\n(3) 0.4 x 2 = 0.8 取整数位 0 得 0.000\n(4) 0.8 x 2 = 1.6 取整数位 1 得 0.0001\n(5) 0.6 x 2 = 0.2 取整数位 1 得 0.00011\n(6) 0.2 x 2 = 0.4 取整数位 0 得 0.000110\n(7) 0.4 x 2 = 0.8 取整数位 0 得 0.0001100\n(8) 0.8 x 2 = 1.6 取整数位 1 得 0.00011001\n(9) 0.6 x 2 = 1.2 取整数位 1 得 0.000110011\n(n) ...\n我们得到一个无限循环的二进制小数 0.000110011…\n\n我为什么要把这个计算过程这么详细的写出来呢?就是为了让你看,多看几遍,再多看几遍,继续看… 还没看出来,好吧,把眼睛揉一下,我提示你,把第一行去掉,从 (2) 开始看,看到 (6),对比一下 (2) 和 (6)。 然后把前两行去掉,从 (3) 开始看…\n明白了吧,0.2、0.4、0.6、0.8 都不能精确的表示为二进制小数。 难以置信,这可是所有的偶数啊!那奇数呢? 答案就是:\n0.1 到 0.9 的 9 个小数中,只有 0.5 可以用二进制精确的表示。\n**如果把 0.0 再算上,那么就有两个数可以精确表示,一个奇数 0.5,一个偶数 0.0。** \n\n那么到底怎么确定一个数能否精确表示呢?还是回到我们熟悉的十进制分数。\n\n1/2、5/9、34/25 哪些可以写成有限小数?把一个分数化到最简(分子分母无公约数),如果分母的因式分解只有 2 和 5,那么就可以写成有限小数,否则就是无限循环小数。为什么是 2 和 5 呢?因为他们是 10 的因子 10 = 2 x 5。二进制和十六进制呢?他们的因子只有 2,所以十六进制只是二进制的一种简写形式,它的精度和二进制一样。\n**如果一个十进制数可以用二进制精确表示,那么它的最后一位肯定是 5。备注:这是个必要条件,而不是充分条件。**\n\n### 为啥 0.2+0.4 不等于0.6\n```javascript\n0.2 + 0.4 //0.6000000000000001\n```\n1.6 + 2.8 = 4.4 \n四舍五入得到 4。我们用另一种方法\n先把 1.6 四舍五入为 2\n再把 2.8 四舍五入为 3\n最后求和 2 + 3 = 5\n通过两种运算,我们得到了两个结果 4 和 5。同理,在我们的浮点数运算中,参与运算的两个数 0.2 和 0.4 精度已经丢失了,所以他们求和的结果已经不是 0.6 了。\n\n## 特殊值\n\n* 指数域不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数的计算值减去127(或1023),得到真实值,再将尾数前加上第一位的1。\n* 指数域全为0。这时,浮点数的指数等于1-127(或者1-1023),尾数不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。\n* 指数域全为1。这时,如果尾数全为0,表示±无穷大(正负取决于符号位s);如果尾数不全为0,表示这个数不是一个数(NaN)。\n\n### NaN(Not a Number)\nNaN 用于处理计算中出现的错误情况,比如 0.0 除以 0.0 或者求负数的平方根。\nNaN 指数域全为 1,且尾数域不等于零的浮点数。IEEE 标准没有要求具体的尾数域,所以 NaN 实际上不是一个,而是一族。不同的实现可以自由选择尾数域的值来表达 NaN。\n任何有 NaN 作为操作数的操作也将产生 NaN。用特殊的 NaN 来表达上述运算错误的意义在于避免了因这些错误而导致运算的不必要的终止。回顾我们对 NaN 的介绍,当零除以零时得到的结果不是无穷而是 NaN 。原因不难理解,当除数和被除数都逼近于零时,其商可能为任何值,所以 IEEE 标准决定此时用 NaN 作为商比较合适。\n### 有符号的零\n因为 IEEE 标准的浮点数格式中,小数点左侧的 1 是隐藏的,而零显然需要尾数必须是零。所以,零也就无法直接用这种格式表达而只能特殊处理。\n实际上,零保存为尾数域为全为 0,指数域为 emin - 1 = -127,也就是说指数域也全为 0。考虑到符号域的作用,所以存在着两个零,即 +0 和 -0。不同于正负无穷之间是有序的,IEEE 标准规定正负零是相等的。\n零有正负之分,的确非常容易让人困惑。这一点是基于数值分析的多种考虑,经利弊权衡后形成的结果。有符号的零可以避免运算中,特别是涉及无穷的运算中,符号信息的丢失。举例而言,如果零无符号,则等式 1/(1/x) = x 当x = ±∞ 时不再成立。原因是如果零无符号,1 和正负无穷的比值为同一个零,然后 1 与 0 的比值为正无穷,符号没有了。解决这个问题,除非无穷也没有符号。但是无穷的符号表达了上溢发生在数轴的哪一侧,这个信息显然是不能不要的。零有符号也造成了其它问题,比如当 x=y 时,等式1/x = 1/y 在 x 和 y 分别为 +0 和 -0 时,两端分别为正无穷和负无穷而不再成立。当然,解决这个问题的另一个思路是和无穷一样,规定零也是有序的。但是,如果零是有序的,则即使 if (x==0) 这样简单的判断也由于 x 可能是 ±0 而变得不确定了。两害取其轻者,零还是无序的好。\n\n注: 本文转自 [随心小筑](http://jser.it/blog/2014/07/07/numbers-in-javascript/) 我自己做了本分填充和整理","source":"_posts/numberInJavaScript-2016-06-19.md","raw":"title: numberInJavaScript\ndate: 2016-06-19 21:51:58\ntags:\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# 思考\n先想几个问题吧:\n1. JavaScript的数字为什么有0和-0?\n2. JavaScript中的NaN为什么互不相等?\n3. JavaScript中的数字真的只有一种类型吗?\n4. JavaScript中常被诟病的0.3 - 0.2 == 0.1原因是什么?\n5. 数组的最大长度是多少?为什么是这个值?\n\n上述问题,只有在JavaScript中有吗?\n<!--more-->\n当下,计算机如此普及,我相信,即便非程序员也了解:计算机的世界只有`0`和`1`。而一个程序员应该了解:`0/1`组成的东西叫机器码,有原码, 反码, 补码等。而一个JS程序员应该了解:JS中的数字是不分类型的,也就是没有`byte/int/float/doubl`e等的差异。而一个稍微研究ES规范的JS程序员应该了解:JS的`number`是IEEE 754标准下64-bits的双精度数值,而且ES中有`ToInteger/ToInt32/ToUint32/ToUint16`等`Type Conversion`。下面,我们就尝试着讨论一下这些。\n# 码\n从硬件的角度上讲,维护两个状态是相对容易的,比如一个二极管的导通或者截止,一个电脉冲的高或者低,从而在实现集成电路时候可以更加简单高效,所以计算机普遍使用0和1来存储和计算。那么,只有`0`和`1`,如何表示1234567890呢?这就涉及到`机器码`和`真值`。\n## 机器码和真值\n* 所谓`机器码`是指,整数在计算机中二进制形式。规则很简单,机器码的最高位(左第一位)表示数字的正负,`0`表示正数,`1`表示负数,其余位按照进制转换的规则表示具体数字。\n* 所谓`真值`是指,机器码按照上述转换规则还原的带有正负的实际整数。\n\n举例而言,用8-bits表示一个整数,则十进制的整数`+6`可表示为:`00000110`;十进制的数字`-5`可表示为`10000101`。这里说的+6和-5便是真值,而表示它们的二进制数便是机器码。再次注意,最高位只用于表示正负,比如`10000101`的真值是`-5`而非`133`,以及我们关于机器码和真值的讨论是基于整数范围的,浮点数在计算机中的存储方式与整数有很大差值,将另作讨论。\n\n有了机器码,我们便可以在计算机中使用机器码存储和计算真值,那么机器码在计算机中是如何计算的呢?\n## 原码、反码、补码\n机器码分为多种,主要包括`原码、反码、补码、移`码等,今天我们主要总结一下前三个,而移码非常简单,且多用于比较,不做详细说明。另外需要补充一点,我们在此区分机器码的这么多种形式,主要是针对的有符号数,而无符号数,不需要使用最高位来表示正负,也就不需要这么多种编码方式。\n\n### 原码\n最高位表示正负,其它位表示真值的绝对值。其中,最高位为`0`表示`正数`或者`0`,为`1`表示负数。\n比如,同样以8bits长度的数串表示`+7`的原码为`0000 0111`,-7的原码为`10000111`。以后,我们会这样表示:\n```javascript\n [+7] = [00000111]原\n [-7] = [10000111]原\n```\n很明显,8-bits的原码能记录的范围为:`[-127,+127]`。\n原码的好处在于,易于理解,相对直观,方便人脑识别和计算。\n对于原码,人脑使用,可以直接计算出其真值然后可以进行后续操作。但对于计算机,\n首先,因为最高位用于表示正负,所以不能直接参与运算,需要识别然后做特殊处理;\n其次,具体计算使用绝对值进行操作,所以两个操作数正负的异同会影响操作符,比如两个异号相加实际要做减法操作,甚至异号相减还需要判断绝对值大小然后决定结果正负。\n如此,我们计算机的运算器设计将会变得异常复杂。下面,我们将了解如何使用反码和补码将符号位参与运算,从而使加减法统一简单高效地处理,这也是反码和补码出现的原因。\n### 反码\n正数的反码等于其原码,而`负数的反码则是对其原码进行符号位不变,其它位逐一取反的结果`。\n比如,同样以8-bits长度的数串表示`+7`,那么有如下:\n```javascript\n [+7] = [00000111]原 = [00000111]反\n [-7] = [10000111]原 = [11111000]反\n```\n同样,8-bits的反码能记录的范围为:`[-127,+127]`。\n在按位取反之后,我们可以有下面的操作:\n```javascript\n 2 - 3 = 2 + (-3) \n = [00000010]原 + [10000011]原 \n = [00000010]反 + [11111100]反 \n = [11111110]反 \n = [10000001]原 \n = -1\n```\n上面,我们将减法通过反码转化为了加法,如此,我们的运算将会简单很多,但是反码的方式同样存在一些问题:\n```javascript\n 3 - 3 = 3 + (-3) \n = [00000011]原 + [10000011]原 \n = [00000011]反 + [11111100]反 \n = [11111111]反 \n = [10000000]原 \n = -0\n```\n出现了`-0`,这个值是没有意义的。另外,按照`反码加法法则,如果最高位有进位,需要在最低位上+1`,那么会出现:\n```javascript\n 3 - 2 = 3 + (-2) \n = [00000011]原 + [10000010]原 \n = [00000011]反 + [11111101]反 (这里最高位有进位,需要在最低位+1) \n = [00000001]反 \n = [00000001]原 \n = 1\n```\n这种情况,又增加了反码运算的复杂性,影响效率,为解决上面的问题,出现了补码。\n### 补码\n正数的补码等于其原码,而负数的补码则是对其反码进行末位加1的结果。\n```javascript\n [+7] = [00000111]原 = [00000111]反 = [00000111]补\n [-7] = [10000111]原 = [11111000]反 = [11111001]补\n```\n使用补码,继续做之前的操作:\n```javascript\n 2 - 3 = 2 + (-3) \n = [00000010]原 + [10000011]原 \n = [00000010]反 + [11111100]反 \n = [00000010]补 + [11111101]补 \n = [11111111]补 \n = [11111110]反 \n = [10000001]原 \n = -1\n```\n那么,如果是`3-3`呢?\n```javascript\n 3 - 3 = 3 + (-3) \n = [00000011]原 + [10000011]原 \n = [00000011]反 + [11111100]反 \n = [00000011]补 + [11111101]补 \n = [00000000]补 \n = [00000000]原 \n = 0\n```\n是否还需要做额外的加法操作?\n```javascript\n 3 - 2 = 3 + (-2) \n = [00000011]原 + [10000010]原 \n = [00000011]反 + [11111101]反 \n = [00000011]补 + [11111110]补 \n = [00000001]补 \n = [00000001]原 \n = 1\n```\n这样,我们便可以完美的将减法统一到加法之上,而且不需要繁琐的正负判断,进位控制,甚至可以节约一个位置。\n那么,这个位置,也就是`10000000`如何处理呢?\n`按照规定,10000000用来表示-128`,正数的补码/反码/原码相同,而负数的补码只是占用了-0的`[10000000]原`和`[11111111]反`转换后得到的`[10000000]补`表示-128,但是这个只是帮助理解,不能反向回推得到`-128`的原码和补码。\n所以,8bits的补码能记录的范围为:`[-128,+127]`。\n至此,我们已经了解了,计算机中主要使用的存储和计算整数的方式,鉴于现代计算机主要使用补码方式,自然能很容易理解各种数字类型的表示范围,比如32bits的int范围为:`[-231,231-1]`。这对于我们后面理解一些JavaScript中的极端情况至关重要。\n\n#### 补码10000000为什么可以表示-128?\n有知乎大神这么理解:\n作者:fhylhl 链接:http://www.zhihu.com/question/28685048/answer/41735701 来源:知乎\n> 很多人并不理解补码。补码就是同余啊。1000000是正128你知道吧,正负128模256是同余的。加减乘可以直接算也是同余的定理决定的,而不是凑出来的巧合,哪可能凑出这种东西?\n 8位只能表示256个数,0到255,但我还想表示一些负数怎么办呢?就用与该负数同余的正数来表示呗。-1=255,-2=254,等等。\n 建议脱离算数的思维方式,这其实就是一个环。模任何一个正整数(如256),可以把所有整数分类,比如模256可分256类,0 256 -256...是一类(余0类),1 257 -255...是一类(余1类),等等,这256类可看作环的元素,你看-128和128是同一个类里的(余128类),用一个代表另一个罢了。补码和普通的unsigned integers都是在每类中选一个数,unsigned integers选0到255,补码表示的有符号整数选-128到127,都是一个数恰好对应一个类。当你明白这一切后,补码就是顺理成章的事。\n\n>> 同余:数论中的重要概念。给定一个正整数m,如果两个整数a和b满足(a-b)能够整除m,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。对模m同余是整数的一个等价关系。\n\n## IEEE 754标准\n作为一个JavaScript程序员,我们只有一个Number,所以我们从一开始就习惯了:\n```javascript\n var num1 = 123;\n var num2 = 1.23;\n```\n但是,你知道JS的number是`IEEE 754`标准的64-bits的双精度数值吗?这是一个什么样的标准?使用这个标准的64-bits双精度意味着什么?所以,要掌握JavaScript中的数字,我们首先得了解`IEEE 754`标准。下面,我将尝试说明一下这个标准,为我们最后学习JavaScript中的数字做铺垫。\n\n## 标准的基本原理:\n我们知道,对于计算机而言,数字没有小数和整数的差别,也就是计算机中没有小数点的存在。通过前文的讨论,我们已经找到了很完美的整数存储计算的方案,但是当涉及到小数,我们很容易发现,现有的方案无法解决我们的需求。然后,计算机科学家们便尝试了多种方案,主要便是`定点数`和`浮点数`两种。\n* 所谓定点数,是指小数点位置固定在数串中间的某个特定位置,点两侧分别为数字的整数和小数部分。比如用8-bits字长的数串,小数点固定在正中间位置,那么`11001001`和`00110101`分别表示`1100.1001`和`11.0101`两个数字。这种方案简单直观易理解,但是存在严重的空间浪费,以及容易溢出的问题。\n* 所谓浮点数,是指小数点的位置是不固定的,通过科学计数法(这个应该不需要解释吧)的方式控制小数点的位置,表示不同的数字。这个表示方案便是IEEE 754标准使用的方案。IEEE 754标准是目前使用最广泛的浮点数运算标准。下面我们将主要讨论一下此方案。\n\n现在,让我们想一下小时候学习的科学计数法,比如`-123.456`这个数字,转换成科学计数法应该是:`-1.23456 × 10^2`。这里面已经包含了IEEE 754标准的主要元素。我们梳理一下:第一个,自然是正负号的问题,需要一个标志;然后,需要一个具体的数字,表示有效数字或者精度,如上例的`1.23456`;再然后,需要一个控制小数点位置的数字,如上例的`10^2`,回忆一下,我们学习科学计数法的时候,要求前面的数字的绝对值大于1而小于10,也就是小于10^2中的底数(Base),进制固定之后,底数应该是固定的,所以这里起决定作用的是指数,也就是上例中的2。那么,有了这三个元素,我们便可以很轻松的表示出一个数字,并且灵活的调节小数点位置从而控制数字正负、精度和大小。\n\n上面的要素,转换成标准语言描述,我们称表示正负的标志叫`符号(Sign)`,表示精度的数字为`尾数(Mantissa)`或者`有效数字(Significand)`,而控制小数点位置的指数就叫`指数(Exponent)`,指数和`基数(Base)`共同作用参与计算。下图取自wikipedia,我们直观地感受下这三个要素在一个数串中的相对关系(fraction区域即等同于前面说的有效数字区域):\n{% asset_img 123.png %}\n了解最基本的原理后,我们来大致看一下`IEEE 754`标准做了什么。\n首先做的事情就是规定这三个要素在一个数串中占有的位数,试想一下,如果各个实现的位数不确定,那么我们是不是很难正确的还原出原始数字?`IEEE 754`标准规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80比特实做)。只有32位模式有强制要求,其他都是选择性的。而现在主流的语言,多提供了单精度和双精度的实现,我们在此主要比较一下这两者,如图是它们各个部分对应上图,所使用的位数如下:\n{% asset_img 456.png %}\n{% asset_img 789.png %}\n补充一点的是,无论是科学计数法还是标准的规定,都要求有效数字(不考虑符号位)必须`>=1 && <Base`。所以,有效数字其实是一个定点数,小数点的位置固定在有效数字域的最高位和次高位之间。那么,按照上述规定,在二进制中,最高位只能是`1`,所以标准要求省略其最高位,于是精度提高一位。比如,`32-bits`的单精度有效数字区域只有23位,但是精度却是24位;`64-bits`的双精度,拥有52位的有效数字域却是53位精度的。\n\n然后,还有一个问题,如果按照先有的约定,`是不是无法表示小于1的实数?`因为,`指数一定>=0,有效数字一定>1`。于是,`IEEE 754`标准提出了一个很重要的指数偏移值。它是说明指数域(Exponent占用的区域)的编码值为指数的实际值加上某个固定的值,换言之便是,如果我们根据指数域计算出的指数是`N`,那么参与计算实际浮点数的指数应该是`N-指数偏移值`。根据IEEE 754标准的规定,该固定值为`2^(e-1) - 1`,其中的`e`为存储指数的比特的长度。比如,从上图中我们看到,`32-bits`的单精度是以`8-bits表示一个指数域`,那么偏移值应该是`2^(8-1) - 1 = 128−1 = 127`。所以,容易得出,单精度浮点数的指数部分实际取值是`全零00000000 0-127=-127`到`全零11111111 256-127=128`就是`[-127,128]`。比如,某个`32-bits`单精度的指数为十进制的`1`,那么指数域的编码应该是`10000001`,某个32-bits单精度的指数域编码是`00000001`,那么该指数的实际值应该是十进制的`-126`。这样,我们就能通过偏移值将正指数转换为负指数,从而`使浮点数能逼近0`。浮点数的指数计算跟前面讨论的机器码恰好相反,`正数的最高位都是1,而负数的最高位都是0`。\n\n以上的描述,便是IEEE 754标准最需要我们了解的原理部分,但是,作为一个广泛使用的工业标准,规定这些还是远远不够的。\nwikipedia(维基百科)对IEEE 754标准有如下描述:\n>这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。\n\n下面,补充几个,我认为与本文后续讨论相关的或者可以帮助大家理解极端现象的定义:\n`规约形式的浮点数`:如果浮点数中指数部分的编码值在`0 < exponent < 2^(e-1)`之间,且尾数部分最高有效位(即整数)是1,那么这个浮点数将被称为规约形式的浮点数。也就是,严格按照我们上文描述编码的数字。\n`非规约形式的浮点数`:如果浮点数的指数部分的编码值是`0`,尾数为非零,那么这个浮点数将被称为非规约形式的浮点数。`IEEE 754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值大1`。例如,最小的规约形式的单精度浮点数的指数部分编码值为`1`,指数的实际值为`-126`;而非规约的单精度浮点数的指数域编码值为`0`,对应的指数实际值也是`-126`而不是`-127`。实际上非规约形式的浮点数仍然是有效可以使用的,只是它们的绝对值已经小于所有的规约浮点数的绝对值;即所有的非规约浮点数比规约浮点数更接近0。规约浮点数的尾数大于等于`1`且小于`2`,而非规约浮点数的尾数小于`1`且大于`0`。\n上面的两个概念,几乎是直接从wikipedia上扒下来的,·非规约形式的浮点数·出现的意义是避免突然式下溢出(abrupt underflow),而采用渐进式下溢出。这已经是上世纪70年代的事情了,差不多是我的年龄的两倍了。这个是一些非常极端的情况,在此我尝试最简单地描述一下`非规约形式的浮点数`出现的意义,知道有这么回事便可:下面,以单精度为例,如果没有`非规约形式的浮点数`,那么绝对值最小的两个相邻的浮点数之间的差值将是绝对值最小的浮点数的2^23分之一,大家想一下,绝对值次小的浮点数减去绝对值最小的浮点数的值是多少?\n\n```javascript\n 1.00...01 × 2^(-126) - 1.00...00 × 2^(-126) = 0.00..01 × 2^(-126) \n = 1 × 2^(-126-23)\n = 2^(-149)\n```\n很明显,绝对值最小的`规约数`无法表达其和次小的`规约数`的差值,所以很容易导致有若干数字之间的差值下溢,可能会触发意料之外的后果。而如果采用非规约形式的浮点数,指数全0,偏移值比规约数偏移值大1(`-126`比`-127`大1),尾数小于`1`,那么非规约数能表达的最小值便是:\n```javascript\n 0.00..01 × 2^(-126) = 1 × 2^(-126-23)\n = 2^(-149)\n```\n所以,`非规约形式的浮点`数解决了前述的`突然式下溢出(abrupt underflow)`而被标准采纳。\n`IEEE 754`标准还规定了三个特殊值:\n* 指数全0且尾数小数部分全0,则这个数字为±0。(符号位决定正负)\n* 指数为2^e - 1且尾数的小数部分全0,这个数字是±∞。(符号位决定正负)\n* 指数为2^e - 1且尾数的小数部分非0,这个数字是NaN。\n结合前面的规约数,非规约数以及三个特殊值,可以得到如下总结:\n{% asset_img 10.png %}\n现在,让我们回忆一下,各种语言中普遍描述的双精度浮点数的范围:`[-1.7 × 10^(-308),1.7 × 10^308]`。打个岔,想象一个有300多位的十进制数字的适用情形,私以为远超过普通人想象力的边界。这个范围为什么是这个范围呢?我觉得,通过上面的讨论,大家应该能清晰,1.7/308这些数字出现的必然原因。\n首先,我们应该很容易根据偏移量得出双精度浮点数的计算公式:\n{% asset_img 11.png %}\n然后,以正数为例,按照上述特殊值中`±∞`和`NaN`的约定,指数的最大值应该满足指数取规约数的指数范围的最大值,然后小数部分取小数部分的最大值,可以得出这个二进制的数字应该是:\n 0 11111111110 11..11(52个)\n转换为16进制表示:\n0x7fef ffff ffff ffff\n那么,根据前述规约数的原理,反编码便得到十进制的:`1.7976931348623157 x 10^308`。类似的道理,Sign位取反,便是范围的下限。\n到此为止吧,我对`IEEE 754`标准也是最近几天稍加学习,再说多了就误导大家了。通过这几天的学习,我感觉,我们在理解的IEEE 754标准及浮点数的时候,要特别注意将精度和范围两个概念分别开来。范围只是一个模糊的界限,精度才是能准确表达的数字。\n\n## 回到JavaScript\n在上面的讨论中,我们很少提及JavaScript,似乎有点背离今天的主题了,但是,在了解了前述的原理之后,我们对JavaScript中数字的把握将”水到渠成”。这终将是一次,铺垫多于正文,开胃菜多于正餐的讨论。嗯,快喊小伙伴,正餐开始了!\n### ES的”The Number Type”:\n现在,我们打开ES规范的[The Number Type](http://es5.github.io/#x8.5) 是不是基本通读下来了?\n比如:\n> The Number type has exactly 18437736874454810627 (that is, 2^64 − 2^53 + 3) values…\n为什么是这个数字?因为,我们说JavaScript中的数字是`64-bits`的双精度,所以首先有`2^64`中可能的组合,然后,按照前述的`IEEE 754`标准的标准中的特殊值中的部分,`NaN`和`±∞`占用了`2^53`个数值,但是表示了三个直观的量,所以,加减一下,自然就是`18437736874454810627` `(that is, 2^64 − 2^53 + 3)` values。\n> …the 9007199254740990 (that is, 2^53−2) distinct “Not-a-Number” values…\n为什么这么多`NaN`?同样,按照前述的`IEEE 754`标准的标准中的特殊值中的部分,`NaN`使用了`Significand非零`、`指数是特定2^e-1且Sign无要求的所有可能`,即`2^53`减去`±∞`两种情况。\n>…e is an integer ranging from −1074 to 971…\n为什么指数的范围是这个呢?而不是`-1022到+1022`呢?因为,ES演化了一下公式,对比一下我们之前演示`64-bits`的公式,关于参与计算的`mantissa`,我们按照`IEEE 754`标准在演示的时候中使用的是`1.m`,而ES规范中使用的是`m`,当然会有尾数域bit长度的差异了。\n\n到这里,关于数字,大概就可以结束了。开篇的几个问题,相信读到这里的同学,都能有答案了。但是,还有一个问题,JavaScript中的数字真的只有一种类型吗?,而且貌似到现在与我们的初衷,理解`>>>`有点偏离了。不过,世界上很多事情往往都是这样,解释原理需要到口干舌燥,而用原理去解释现象却只需要三言两语。\n\n### JavaScript不是只有64-bits的双精度\n是的,小标题已经回答了我们的问题,JavaScript不是只有`64-bits`的双精度。我们通篇都在说JavaScript中数字的各种,一直按照`64-bits`的双精度来描述,但是,如之前所说,ES中有`ToInteger/ToInt32/ToUint32/ToUint16`等Type Conversion。这些Type Conversion不是我们直接调用的API,而是语言引擎在进行某些特定操作的时候,替我们做的。这种“隐形的操作”,只有在一些极端的情况下,会表现出来。现在,我们可以到`“ToInt32”/“ToUint32”/“ToInt16”`三个地方看一下,稍作比较便能发现,他们的差异很小,只是在特定的步骤中存在差异。比如,`ToUint32和ToUint16`的差异仅仅操作的最后一步存在差异,按顺序列出比较一下:\n\n> Let int32bit be posInt modulo 2^32; that is, a finite integer value k of Number type with positive sign and less than 2^32 in magnitude such that the > mathematical difference of posInt and k is mathematically an integer multiple of 2^32.\n> \n>Return int32bit.\nvs\n> Let int16bit be posInt modulo 2^16; that is, a finite integer value k of Number type with positive sign and less than 2^16 in magnitude such that the mathematical difference of posInt and k is mathematically an integer multiple of 2^16.\n>\n>Return int16bit.\n\n比较一下,不难发现,仅仅是`2^32`和`2^16`的差异,而关键点恰是`modulo`操作的时候,按照我们之前讨论的原理,很容易理解这个操作决定了可能出现的最大数。这样的比较,有一好处,能提高我们阅读标准的速度,而且加深理解,对掌握标准很有帮助。\n总结一下这三个操作的范围:\n\n* ToInt32的范围便是其它强类型语言中的[-2^31, -2^31 - 1]。\n\n* ToUint32的范围便是其它强类型语言中的[0, -2^32 - 1]。\n\n* ToUint16的范围便是其它强类型语言中的[0, -2^16 - 1]。\n通过搜索,很容易能找到,JavaScript中那些操作中使用了上述相关的操作。其中,`ToUint16`仅仅在`String.fromCharCode`中有使用,我们不做讨论了。`ToInt32`有在多个位运算符中使用,比如`~` `/` `<<` `/` `>>`,以及在`parseIn`t也有使用。而`ToUint32`的使用则出现在了大量的地方,主要分布在`,数组相关的操作,位运算的操作`两个区域。\n我们就借`ToUint32`的这些使用,回到开篇讨论的那个地方吧:\n首先,来到这里[>>>](http://es5.github.io/#x11.7.3),看到操作如下:\n> 1. Let lref be the result of evaluating ShiftExpression.\n> \n> 2. Let lval be GetValue(lref).\n> \n> 3. Let rref be the result of evaluating AdditiveExpression.\n>\n> 4. Let rval be GetValue(rref).\n>\n> 5. Let lnum be ToUint32(lval).\n> \n> 6. Let rnum be ToUint32(rval).\n> \n> 7. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.\nReturn the result of performing a zero-filling right shift of lnum by shiftCount bits. Vacated bits are filled with zero. The result is an unsigned 32-bit integer.\n>\n再看new Array (len),有一句:\n> If the argument len is a Number and ToUint32(len) is equal to len, then the length property of the newly constructed object is set to ToUint32(len). If the argument len is a Number and ToUint32(len) is not equal to len, a RangeError exception is thrown.\n对比不难发现,`>>>`的返回值和`array.length`的取值范围,无差异,经过`>>>`操作后的数字,一定是一个合法的`array.length`。解释原理总是那么复杂,可是用原理解释现象总是那么简单。\n\n## 将实数转换成浮点数\n\n### 浮点数的规范化\n同样的数值可以有多种浮点数表达方式,比如上面例子中的 123.45 可以表达为 12.345 × 101,0.12345 × 103 或者 1.2345 × 102。因为这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式:`±d.dd...d × βe , (0 ≤ d i < β)`。\n其中 d.dd...d 即尾数,β 为基数,e 为指数。尾数中数字的个数称为精度,在本文中用 p 来表示。每个数字 d 介于 0 和基数之间,包括 0。小数点左侧的数字不为 0。\n基于规范表达的浮点数对应的具体值可由下面的表达式计算而得:`±(d 0 + d 1β-1 + ... + d p-1β-(p-1))βe , (0 ≤ d i < β)`\n对于十进制的浮点数,即基数 β 等于 10 的浮点数而言,上面的表达式非常容易理解,也很直白。计算机内部的数值表达是基于二进制的。从上面的表达式,我们可以知道,二进制数同样可以有小数点,也同样具有类似于十进制的表达方式。只是此时 β 等于 2,而每个数字 d 只能在 0 和 1 之间取值。比如二进制数 1001.101 相当于 1 × 2<sup>3</sup> + 0 × 2<sup>2</sup> + 0 × 2<sup>1</sup> + 1 × 2<sup>0</sup> + 1 × 2<sup>-1</sup> + 0 × 2<sup>-2</sup> + 1 × 2<sup>-3</sup>,对应于十进制的 9.625。其规范浮点数表达为 1.001101 × 2<sup>3</sup>。\n\n### 根据精度表示浮点数\n\n问:要把小数装入计算机,总共分几步?你猜对了,3 步。\n\n* 第一步:转换成二进制。\n* 第二步:用二进制科学计算法表示。\n* 第三步:表示成 IEEE 754 形式。\n\n#### 如何把十进制小数转换成二进制小数。\n十进制小数转换成二进制小数采用\"乘2取整,顺序排列\"法。\n具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数 部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。\n* 如:25.7=(11001.10110011001100110011001100110011...)B\n * 整数部分:\n 25%2 = 1 ======== 1\n 12%2 = 0 ======== 0\n 6%2 = 3 ======== 0\n 3%2 = 1 ======== 1\n 1%2 = 1 ======== 1\n * 小数部分:\n 0.7*2=1.4========取出整数部分1 \n 0.4*2=0.8========取出整数部分0 \n 0.8*2=1.6========取出整数部分1 \n 0.6*2=1.2========取出整数部分1 \n 0.2*2=0.4========取出整数部分0 \n 0.4*2=0.8========取出整数部分0 \n 0.8*2=1.6========取出整数部分1 \n 0.6*2=1.2========取出整数部分1 \n 0.2*2=0.4========取出整数部分0 \n * 最后结果就是: \n 11001.1 0110 0110 0110 0110 0110 0110 0110 0110 0110...\n \n#### 用二进制科学计算法表示\n 11001.101100110 == 1.1001101100110 * 2<sup>4</sup>\n#### 表示成 IEEE 754 形式\n1. 正数 固符号位为 0 \n2. 尾数 由于第一位使用是1,固取(首位1干掉了).10011 0110 0110 0110 0110 0110 0110 0110 0110 0110\n3. 指数 4 + 1023(偏移量),转换为 二进制就是 10000000011\n组合在一起就是 0 10000000011 10011 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 011\n\n> 单精度32位的 偏移量是 Math.pow(2,8)/2 -1 == 127 ,双精度64位的偏移量是 Math.pow(2,11)/2 -1 == 1023 \n\n### 哪些数能精确表示?\n那么 0.1 在计算机中可以精确表示吗?答案是出人意料的, 不能。\n\n在此之前,先思考个问题: `在 0.1 到 0.9 的 9 个小数中,有多少可以用二进制精确表示呢?`\n\n我们按照乘以 2 取整数位的方法,把 0.1 表示为二进制(我假设那些不会进制转换的同学已经补习完了):\n\n(1) 0.1 x 2 = 0.2 取整数位 0 得 0.0\n(2) 0.2 x 2 = 0.4 取整数位 0 得 0.00\n(3) 0.4 x 2 = 0.8 取整数位 0 得 0.000\n(4) 0.8 x 2 = 1.6 取整数位 1 得 0.0001\n(5) 0.6 x 2 = 0.2 取整数位 1 得 0.00011\n(6) 0.2 x 2 = 0.4 取整数位 0 得 0.000110\n(7) 0.4 x 2 = 0.8 取整数位 0 得 0.0001100\n(8) 0.8 x 2 = 1.6 取整数位 1 得 0.00011001\n(9) 0.6 x 2 = 1.2 取整数位 1 得 0.000110011\n(n) ...\n我们得到一个无限循环的二进制小数 0.000110011…\n\n我为什么要把这个计算过程这么详细的写出来呢?就是为了让你看,多看几遍,再多看几遍,继续看… 还没看出来,好吧,把眼睛揉一下,我提示你,把第一行去掉,从 (2) 开始看,看到 (6),对比一下 (2) 和 (6)。 然后把前两行去掉,从 (3) 开始看…\n明白了吧,0.2、0.4、0.6、0.8 都不能精确的表示为二进制小数。 难以置信,这可是所有的偶数啊!那奇数呢? 答案就是:\n0.1 到 0.9 的 9 个小数中,只有 0.5 可以用二进制精确的表示。\n**如果把 0.0 再算上,那么就有两个数可以精确表示,一个奇数 0.5,一个偶数 0.0。** \n\n那么到底怎么确定一个数能否精确表示呢?还是回到我们熟悉的十进制分数。\n\n1/2、5/9、34/25 哪些可以写成有限小数?把一个分数化到最简(分子分母无公约数),如果分母的因式分解只有 2 和 5,那么就可以写成有限小数,否则就是无限循环小数。为什么是 2 和 5 呢?因为他们是 10 的因子 10 = 2 x 5。二进制和十六进制呢?他们的因子只有 2,所以十六进制只是二进制的一种简写形式,它的精度和二进制一样。\n**如果一个十进制数可以用二进制精确表示,那么它的最后一位肯定是 5。备注:这是个必要条件,而不是充分条件。**\n\n### 为啥 0.2+0.4 不等于0.6\n```javascript\n0.2 + 0.4 //0.6000000000000001\n```\n1.6 + 2.8 = 4.4 \n四舍五入得到 4。我们用另一种方法\n先把 1.6 四舍五入为 2\n再把 2.8 四舍五入为 3\n最后求和 2 + 3 = 5\n通过两种运算,我们得到了两个结果 4 和 5。同理,在我们的浮点数运算中,参与运算的两个数 0.2 和 0.4 精度已经丢失了,所以他们求和的结果已经不是 0.6 了。\n\n## 特殊值\n\n* 指数域不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数的计算值减去127(或1023),得到真实值,再将尾数前加上第一位的1。\n* 指数域全为0。这时,浮点数的指数等于1-127(或者1-1023),尾数不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。\n* 指数域全为1。这时,如果尾数全为0,表示±无穷大(正负取决于符号位s);如果尾数不全为0,表示这个数不是一个数(NaN)。\n\n### NaN(Not a Number)\nNaN 用于处理计算中出现的错误情况,比如 0.0 除以 0.0 或者求负数的平方根。\nNaN 指数域全为 1,且尾数域不等于零的浮点数。IEEE 标准没有要求具体的尾数域,所以 NaN 实际上不是一个,而是一族。不同的实现可以自由选择尾数域的值来表达 NaN。\n任何有 NaN 作为操作数的操作也将产生 NaN。用特殊的 NaN 来表达上述运算错误的意义在于避免了因这些错误而导致运算的不必要的终止。回顾我们对 NaN 的介绍,当零除以零时得到的结果不是无穷而是 NaN 。原因不难理解,当除数和被除数都逼近于零时,其商可能为任何值,所以 IEEE 标准决定此时用 NaN 作为商比较合适。\n### 有符号的零\n因为 IEEE 标准的浮点数格式中,小数点左侧的 1 是隐藏的,而零显然需要尾数必须是零。所以,零也就无法直接用这种格式表达而只能特殊处理。\n实际上,零保存为尾数域为全为 0,指数域为 emin - 1 = -127,也就是说指数域也全为 0。考虑到符号域的作用,所以存在着两个零,即 +0 和 -0。不同于正负无穷之间是有序的,IEEE 标准规定正负零是相等的。\n零有正负之分,的确非常容易让人困惑。这一点是基于数值分析的多种考虑,经利弊权衡后形成的结果。有符号的零可以避免运算中,特别是涉及无穷的运算中,符号信息的丢失。举例而言,如果零无符号,则等式 1/(1/x) = x 当x = ±∞ 时不再成立。原因是如果零无符号,1 和正负无穷的比值为同一个零,然后 1 与 0 的比值为正无穷,符号没有了。解决这个问题,除非无穷也没有符号。但是无穷的符号表达了上溢发生在数轴的哪一侧,这个信息显然是不能不要的。零有符号也造成了其它问题,比如当 x=y 时,等式1/x = 1/y 在 x 和 y 分别为 +0 和 -0 时,两端分别为正无穷和负无穷而不再成立。当然,解决这个问题的另一个思路是和无穷一样,规定零也是有序的。但是,如果零是有序的,则即使 if (x==0) 这样简单的判断也由于 x 可能是 ±0 而变得不确定了。两害取其轻者,零还是无序的好。\n\n注: 本文转自 [随心小筑](http://jser.it/blog/2014/07/07/numbers-in-javascript/) 我自己做了本分填充和整理","slug":"numberInJavaScript","published":1,"updated":"2016-06-19T13:52:33.493Z","layout":"post","photos":[],"link":"","_id":"citf9om2q001dskv7vmxgrmzn"},{"title":"js中函数参数都是按值传递的","date":"2016-02-22T02:17:58.000Z","comments":1,"_content":"## 问题的起源\n其实这个问题来源于C和C++,因为C或C++里都有一个特殊的数据类型----指针,那时候所谓的传值和传引用是对指针来说的。那么针对指针来说,什么是传值,又什么是传引用呢?首先,指针作为一种数据类型,其本身肯定是占用一定的内存空间,而且指针同时还要指向另一块内存空间。\n<!--more-->\n{% asset_img image5.png %}\n针对指针来说,指针作为一种数据类型,指针的标识符也就是其在内存中所在的地址,指针的值就是其所指向的地址。也就是说,指针值是地址。\n那么一个指针在作为函数的参数的时候,传给参数的到底是指针的地址还是指针的值呢? ----这就是传值和传引用问题的起源。\n## 数据类型\n在 javascript 中数据类型可以分为两类:\n* 原始数据类型 primitive type,比如Undefined、Null、Boolean、Number、String(除外)。\n* 引用类型 Object type,比如Object、Array、Function、Date等。\n\nPS:本文所说的原始数据类型也称为简单数据类型,引用类型也称为复杂数据类型。\n## 声明变量时不同的内存分配\n** 原始值:** 存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 - 栈中。这样存储便于迅速查寻变量的值。String类型除外。\n** 引用值** :存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。\n{% asset_img image1.jpg %}\n## 不同的内存分配机制也带来了不同的访问机制\n在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。而原始类型的值则是可以直接访问到的。\n\n## 复制变量时的不同\n** 原始值:**在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。\n{% asset_img image2.jpg %}\n** 引用值:**在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)\n{% asset_img image3.jpg %}\n\n## 函数参数传递的不同\n首先我们应该明确一点:** ECMAScript中所有函数的参数都是按值来传递的。**但是为什么涉及到原始类型与引用类型的值时仍然有区别呢,还不就是因为内存分配时的差别。\n** 原始值:**只是把变量里的值传递给参数,之后参数和这个变量互不影响。\n** 引用值:**对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象呀。或许我这么说了以后你对书上的例子还是有点不太理解,那么请看图吧:\n{% asset_img image4.jpg %}\n{% asset_img image13.png %}\n所以,如果是按引用传递的话,是把第二格中的内容(也就是变量本身)整个传递进去(就不会有第四格的存在了)。但事实是变量把它里面的值传递(复制)给了参数,让这个参数也指向原对象。因此如果在函数内部给这个参数赋值另一个对象时,这个参数就会更改它的值为新对象的内存地址指向新的对象,但此时原来的变量仍然指向原来的对象,这时候他们是相互独立的;但如果这个参数是改变对象内部的属性的话,这个改变会体现在外部,因为他们共同指向的这个对象被修改了呀!\n{% asset_img image14.png %}\n对于传引用来说,形参的值是指针的地址,那么每次对形参的改变,其实改变的都是指针所指向的地址。而如果向取得结构体的地址,也需要通过实参的地址。\n## 特殊的类型---String\n字符串也是一种数据类型,那么字符串的值保存在哪里呢? 其作为函数参数的时候,传值是如何进行的呢?\n字符串虽然也是一种基本数据类型,但因为其大小不固定,所以,其一般其更像与引用数据类型。但其又有其特殊性----不可变性。\n ### 什么是字符串的不可变?\n ```javascript\nvar str = \"\";\nfor (var i = 0; i < 3; i++) {\n str += i;\n}\nconsole.log(str); //012\n```\n看上图程序,字符串是可以改变的。那为什么还要说,字符串不可变呢,先别急,我们来分析一下程序运行时内存结构图。\n{% asset_img image7.png %}\n{% asset_img image8.png %} \n{% asset_img image9.png %} \n{% asset_img image10.png %}\n看到没有,每一次字符串值的改变,其所指向的地址都会跟着改变一次。其所说的不可变,是跟引用数据类型相比来说,引用数据类型值的改变一般是对象本身的改变,而其指向是不变的,而字符串值的改变是其指向地址的改变。所以,字符串的每一次改变都会产生垃圾,此垃圾过一段时间会被垃圾回收机制回收。\n\n了解了上述的情况,所以字符传在作为传值的时候,为了节省空间,只是复制了字符串所指向的地址给形参,而形参的值如果改变了的话,因为字符串具有不可变特性,所以会重新开辟一份空间给形参。如下:\n```javascript\nvar str = '你是谁?';\nfunction change(str) {\n str = '我就是我了';\n}\nchange(str);\nconsole.log(str); //你是谁\n```\n{% asset_img image11.png %}\n{% asset_img image12.png %}\n字符串在Java C++中均作为比较特殊的一种类型,JS中虽然将其当作一种基本数据类型,但是其使用时更偏向于引用数据类型,但是其又具有不可变型,才促使我们可以将其当作基本数据类型使用。\n> 转自:http://www.th7.cn/web/js/201503/90277.shtml\n> 转自:http://fehacker.com/2014/12/19/call-by-sharing/\n","source":"_posts/js中函数参数都是按值传递的-2016-02-22.md","raw":"title: js中函数参数都是按值传递的\ndate: 2016-02-22 10:17:58\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\n## 问题的起源\n其实这个问题来源于C和C++,因为C或C++里都有一个特殊的数据类型----指针,那时候所谓的传值和传引用是对指针来说的。那么针对指针来说,什么是传值,又什么是传引用呢?首先,指针作为一种数据类型,其本身肯定是占用一定的内存空间,而且指针同时还要指向另一块内存空间。\n<!--more-->\n{% asset_img image5.png %}\n针对指针来说,指针作为一种数据类型,指针的标识符也就是其在内存中所在的地址,指针的值就是其所指向的地址。也就是说,指针值是地址。\n那么一个指针在作为函数的参数的时候,传给参数的到底是指针的地址还是指针的值呢? ----这就是传值和传引用问题的起源。\n## 数据类型\n在 javascript 中数据类型可以分为两类:\n* 原始数据类型 primitive type,比如Undefined、Null、Boolean、Number、String(除外)。\n* 引用类型 Object type,比如Object、Array、Function、Date等。\n\nPS:本文所说的原始数据类型也称为简单数据类型,引用类型也称为复杂数据类型。\n## 声明变量时不同的内存分配\n** 原始值:** 存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 - 栈中。这样存储便于迅速查寻变量的值。String类型除外。\n** 引用值** :存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。\n{% asset_img image1.jpg %}\n## 不同的内存分配机制也带来了不同的访问机制\n在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问。而原始类型的值则是可以直接访问到的。\n\n## 复制变量时的不同\n** 原始值:**在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。\n{% asset_img image2.jpg %}\n** 引用值:**在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)\n{% asset_img image3.jpg %}\n\n## 函数参数传递的不同\n首先我们应该明确一点:** ECMAScript中所有函数的参数都是按值来传递的。**但是为什么涉及到原始类型与引用类型的值时仍然有区别呢,还不就是因为内存分配时的差别。\n** 原始值:**只是把变量里的值传递给参数,之后参数和这个变量互不影响。\n** 引用值:**对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象呀。或许我这么说了以后你对书上的例子还是有点不太理解,那么请看图吧:\n{% asset_img image4.jpg %}\n{% asset_img image13.png %}\n所以,如果是按引用传递的话,是把第二格中的内容(也就是变量本身)整个传递进去(就不会有第四格的存在了)。但事实是变量把它里面的值传递(复制)给了参数,让这个参数也指向原对象。因此如果在函数内部给这个参数赋值另一个对象时,这个参数就会更改它的值为新对象的内存地址指向新的对象,但此时原来的变量仍然指向原来的对象,这时候他们是相互独立的;但如果这个参数是改变对象内部的属性的话,这个改变会体现在外部,因为他们共同指向的这个对象被修改了呀!\n{% asset_img image14.png %}\n对于传引用来说,形参的值是指针的地址,那么每次对形参的改变,其实改变的都是指针所指向的地址。而如果向取得结构体的地址,也需要通过实参的地址。\n## 特殊的类型---String\n字符串也是一种数据类型,那么字符串的值保存在哪里呢? 其作为函数参数的时候,传值是如何进行的呢?\n字符串虽然也是一种基本数据类型,但因为其大小不固定,所以,其一般其更像与引用数据类型。但其又有其特殊性----不可变性。\n ### 什么是字符串的不可变?\n ```javascript\nvar str = \"\";\nfor (var i = 0; i < 3; i++) {\n str += i;\n}\nconsole.log(str); //012\n```\n看上图程序,字符串是可以改变的。那为什么还要说,字符串不可变呢,先别急,我们来分析一下程序运行时内存结构图。\n{% asset_img image7.png %}\n{% asset_img image8.png %} \n{% asset_img image9.png %} \n{% asset_img image10.png %}\n看到没有,每一次字符串值的改变,其所指向的地址都会跟着改变一次。其所说的不可变,是跟引用数据类型相比来说,引用数据类型值的改变一般是对象本身的改变,而其指向是不变的,而字符串值的改变是其指向地址的改变。所以,字符串的每一次改变都会产生垃圾,此垃圾过一段时间会被垃圾回收机制回收。\n\n了解了上述的情况,所以字符传在作为传值的时候,为了节省空间,只是复制了字符串所指向的地址给形参,而形参的值如果改变了的话,因为字符串具有不可变特性,所以会重新开辟一份空间给形参。如下:\n```javascript\nvar str = '你是谁?';\nfunction change(str) {\n str = '我就是我了';\n}\nchange(str);\nconsole.log(str); //你是谁\n```\n{% asset_img image11.png %}\n{% asset_img image12.png %}\n字符串在Java C++中均作为比较特殊的一种类型,JS中虽然将其当作一种基本数据类型,但是其使用时更偏向于引用数据类型,但是其又具有不可变型,才促使我们可以将其当作基本数据类型使用。\n> 转自:http://www.th7.cn/web/js/201503/90277.shtml\n> 转自:http://fehacker.com/2014/12/19/call-by-sharing/\n","slug":"js中函数参数都是按值传递的","published":1,"updated":"2016-02-22T03:35:12.354Z","layout":"post","photos":[],"link":"","_id":"citf9om4e001gskv7413vmrx5"},{"title":"javascript模块化","date":"2016-07-10T04:15:40.000Z","comments":1,"_content":"# 模块化\n随着网站逐渐变成\"互联网应用程序\",嵌入网页的Javascript代码越来越庞大,越来越复杂。\n网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。\nJavascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。但是,Javascript不是一种模块化编程语言,它不支持\"类\"(class),更遑论\"模块\"module)了。(正在制定中的ECMAScript标准第六版,将正式支持\"类\"和\"模块\",但还需要很长时间才能投入实用。)\nJavascript社区做了很多努力,在现有的运行环境中,实现\"模块\"的效果。本文总结了当前"Javascript模块化编程"的最佳实践,说明如何投入实用。\n<!--more-->\n## 实现模块化的方法\n\n### 原始写法\n模块就是实现特定功能的一组方法。只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。\n```\nfunction m1(){\n //...\n }\nfunction m2(){\n //...\n}\n```\n上面的函数`m1()`和`m2()`,组成一个模块。使用的时候,直接调用就行了。这种做法的缺点很明显:\"污染\"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。\n\n### 对象写法\n为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。\n```\nvar module1 = new Object({\n _count : 0,\n m1 : function (){\n //...\n },\n m2 : function (){\n //...\n }\n});\n```\n上面的函数`m1()`和`m2()`,都封装在module1对象里。使用的时候,就是调用这个对象的属性 `module1.m1();`。\n但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值 `module1._count = 5;`。\n\n### 立即执行函数写法\n使用\"立即执行函数\"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。\n```\nvar module1 = (function(){\n var _count = 0;\n var m1 = function(){\n //...\n };\n var m2 = function(){\n //...\n };\n return {\n m1 : m1,\n m2 : m2\n 34};\n})();\n```\n使用上面的写法,外部代码无法读取内部的_count变量。\nmodule1就是Javascript模块的基本写法。下面,再对这种写法进行加工。\n\n### 立即执行函数放大模式\n如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用\"放大模式\"(augmentation)。\n```\nvar module1 = (function (mod){\n mod.m3 = function () {\n //...\n };\n return mod;\n })(module1);\n```\n在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用\"宽放大模式\"。\n\n### 输入全局变量\n独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。为了在模块内部调用全局变量,必须显式地将其他变量输入模块。\n```\nvar module1 = (function ($, YAHOO) {\n //...\n})(jQuery, YAHOO);\n```\n上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。\n\n## CommonJS&AMD&CMD\n先想一想,为什么模块很重要?\n因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。\n但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了。目前,通行的Javascript模块规范共有两种:`CommonJS`和`AMD`\n\n### CommonJS规范(Node.js)\n```\nvar x = 5;\nvar addX = function(value) {\n return value + x;\n};\n\nmodule.exports.x = x;\nmodule.exports.addX = addX;\n```\n2009年,美国程序员Ryan Dahl创造了`node.js`项目,将javascript语言用于服务器端编程。\n这标志\"Javascript模块化编程\"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。\nnode.js的[模块系统](https://nodejs.org/docs/latest/api/modules.html),就是参照[CommonJS](http://wiki.commonjs.org/wiki/CommonJS)规范实现的。在CommonJS中,有一个全局性方法`require()`,用于加载模块。\n假定有一个数学模块`math.js`,就可以像下面这样加载。\n```\nvar math = require('math');\nmath.add(2,3); // 5\n```\n{% asset_img guanxi.png %}\n\n### AMD规范(RequireJS)\n有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。\n但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?\n第二行`math.add(2, 3)`,在第一行`require('math')`之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。\n这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于\"假死\"状态。\n因此,浏览器端的模块,不能采用\"同步加载\"(synchronous),只能采用\"异步加载\"(asynchronous)。这就是AMD规范诞生的背景。\n[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)是\"Asynchronous Module Definition\"的缩写,意思就是\"异步模块定义\"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。\nAMD也采用`require()`语句加载模块,但是不同于CommonJS,它要求两个参数:\n```\nrequire([module], callback);\n```\n第一个参数`[module]`,是一个数组,里面的成员就是要加载的模块;第二个参数`callback`,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:\n```\nrequire(['math'], function (math) {\n math.add(2, 3);\n});\n```\n`math.add()`与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:[require.js](http://requirejs.org/)和[curl.js](https://github.com/cujojs/curl)。\n```\n//规范\ndefine(id?, dependencies?, factory);\ndefine.amd = {};\n\n//写法1\ndefine(function(require, exports, module) {\n var $ = require('jquery');\n //code here\n});\n\n//写法2\ndefine(['jquery'], function($) {\n //code here\n});\n\n//写法3\ndefine(['require', 'jquery'], function(require) {\n var $ = require('jquery');\n //code here\n});\n```\n### CMD规范(SeaJS)\nCMD是国内玉伯大神在开发SeaJS的时候提出来的,[与AMD规范的区别](https://github.com/seajs/seajs/issues/277);\nCMD规范和AMD类似,都主要运行于浏览器端,写法上看起来也很类似。主要是区别在于模块初始化时机,AMD中只要模块作为依赖时,就会加载并初始化,而CMD中,模块作为依赖且被引用时才会初始化,否则只会加载。如下规范定义及一般写法:\n```\n//规范\ndefine(factory);\ndefine.cmd = {};\n\n//写法1\ndefine(function(require, exports, module) {\n var $ = require('jquery');\n //code here\n});\n\n//写法2\ndefine(['jquery'], function(require, exports, module) {\n var $ = require('jquery');\n //code here\n});\n```\n\n## 模块化开发上线部署\n\n1. 压缩\n2. 合并\n3. 更新版本\n\n* 不能直接压缩:因为模块加载器在分析模块的依赖时,会先将模块的工厂函数`factory.toString()`,然后通过正则匹配`require`局部变量,这样意味着不能直接通过压缩工具进行压缩,若require这个变量被替换,加载器与自动化工具将无法获取模块的依赖。\n* 不能直接合并:我们在开发时,通过是省略模块的ID的,如果多个模块直接合并成一个文件,这样加载器无法区分不同模块了。\n所以采用模块化开发上线部署时,压缩前通常通过工具先提取依赖,这样require就可以当做普通变量直接压缩了,同时也不再需要加载器分析提取依赖,对于提升性能也是蛮有好处的。合并前同样也需要借助工具先提取各个模块的ID,然后才能按照合并配置进行合并。整个过程如下:\n{% asset_img process.png %}\nSeaJS和RequireJS官方都提供了构建工具,如SeaJS的spm,RequireJS的r.js,当然也很多grunt和glup插件可使用,区别于普通压缩合并就是要有提取依赖及模块ID的能力。\n对比构建工具使用感受,spm的整个上手并不是那么顺畅,配置太复杂。r.js使用相对简单,只需要配置好合并规范的配置文件即可,其它grunt或glup提供的插件相对灵活,可根据自身业务灵活配置。\n完成压缩合并后,最后一步就是更新版本号上线了,通常是需要手动更新版本号,或是修改控制版本号的配置参数。这一步基本上还是需要人力手动干预一下。当然如果合并是通过后端的combo服务就不需要了。不管怎么说,通过这些工具的组合使用,整个开发上线流程基本实现自动化了,比较方便。\n\n## require.js的用法\n### 为什么要用require.js?\n最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。\n```\n<script src=\"1.js\"></script>\n<script src=\"2.js\"></script>\n<script src=\"3.js\"></script>\n<script src=\"4.js\"></script>\n<script src=\"5.js\"></script>\n<script src=\"6.js\"></script>\n```\n这段代码依次加载多个js文件。\n这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。\nrequire.js的诞生,就是为了解决这两个问题:\n* 实现js文件的异步加载,避免网页失去响应;\n* 管理模块之间的依赖性,便于代码的编写和维护。\n### require.js的加载\n使用require.js的第一步,是先去官方网站下载最新版本。下载后,假定把它放在js子目录下面,就可以加载了。\n```\n<script src=\"js/require.js\" defer async=\"true\" ></script>\n```\n`async`属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持`defer`,所以把`defer`也写上。\n加载`require.js`以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是main.js,也放在js目录下面。那么,只需要写成下面这样就行了:\n```\n<script src=\"js/require.js\" data-main=\"js/main\"></script>\n```\n`data-main`属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的`main.js`,这个文件会第一个被`require.js`加载。由于`require.js`默认的文件后缀名是js,所以可以把main.js简写成main。\n\n### 主模块的写法\n上一节的main.js,我把它称为\"主模块\",意思是整个网页的入口代码。它有点像C语言的main()函数,所有代码都从这儿开始运行。下面就来看,怎么写main.js。\n```\n// main.js\nrequire(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){\n // some code here\n});\n```\n`require()`函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是`['moduleA', 'moduleB', 'moduleC']`,即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。\n`require()`异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。\n下面,我们看一个实际的例子。\n假定主模块依赖`jquery、underscore和backbone`这三个模块,main.js就可以这样写:\n```\nrequire(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){\n // some code here\n});\n```\nrequire.js会先加载jQuery、underscore和backbone,然后再运行回调函数。主模块的代码就写在回调函数中。\n### 模块的加载\n上一节最后的示例中,主模块的依赖模块是`['jquery', 'underscore', 'backbone']`。默认情况下,require.js假定这三个模块与main.js在同一个目录,文件名分别为jquery.js,underscore.js和backbone.js,然后自动加载。\n使用`require.config()`方法,我们可以对模块的加载行为进行自定义。`require.config()`就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。\n```\nrequire.config({\n paths: {\n \"jquery\": \"jquery.min\",\n \"underscore\": \"underscore.min\",\n \"backbone\": \"backbone.min\"\n }\n});\n```\n上面的代码给出了三个模块的文件名,路径默认与main.js在同一个目录(js子目录)。如果这些模块在其他目录,比如js/lib目录,则有两种写法。一种是逐一指定路径。\n```\nrequire.config({\n paths: {\n \"jquery\": \"lib/jquery.min\",\n \"underscore\": \"lib/underscore.min\",\n \"backbone\": \"lib/backbone.min\"\n }\n});\n```\n另一种则是直接改变基目录(baseUrl)。\n```\nrequire.config({\n baseUrl: \"js/lib\",\n paths: {\n \"jquery\": \"jquery.min\",\n \"underscore\": \"underscore.min\",\n \"backbone\": \"backbone.min\"\n }\n});\n```\n如果某个模块在另一台主机上,也可以直接指定它的网址,比如:\n```\nrequire.config({\n paths: {\n \"jquery\": \"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min\"\n }\n});\n```\nrequire.js要求,每个模块是一个单独的js文件。这样的话,如果加载多个模块,就会发出多次HTTP请求,会影响网页的加载速度。因此,require.js提供了一个[优化工具](http://requirejs.org/docs/optimization.html),当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少HTTP请求数。\n### AMD模块的写法\nrequire.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。\n具体来说,就是模块必须采用特定的`define()`函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在`define()`函数之中。\n假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:\n```\n// math.js\ndefine(function (){\n var add = function (x,y){\n return x+y;\n };\n return {\n add: add\n };\n});\n```\n加载方法如下:\n```\n// main.js\nrequire(['math'], function (math){\n alert(math.add(1,1));\n});\n```\n如果这个模块还依赖其他模块,那么`define()`函数的第一个参数,必须是一个数组,指明该模块的依赖性。\n```\ndefine(['myLib'], function(myLib){\n function foo(){\n myLib.doSomething();\n }\n return {\n foo : foo\n };\n});\n```\n当`require()`函数加载上面这个模块的时候,就会先加载myLib.js文件。\n### 加载非规范的模块\n理论上,require.js加载的模块,必须是按照AMD规范、用`define()`函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如jQuery)符合AMD规范,更多的库并不符合。那么,require.js是否能够加载非规范的模块呢?回答是可以的。\n这样的模块在用`require()`加载之前,要先用`require.config()`方法,定义它们的一些特征。\n举例来说,`underscore`和`backbone`这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。\n```\nrequire.config({\n shim: {\n 'underscore':{\n exports: '_'\n },\n 'backbone': {\n deps: ['underscore', 'jquery'],\n exports: 'Backbone'\n }\n }\n});\n```\n`require.config()`接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义:\n1. `exports`值(输出的变量名),表明这个模块外部调用时的名称;\n2. `deps`数组,表明该模块的依赖性。\n比如,jQuery的插件可以这样定义:\n```\nshim: {\n 'jquery.scroll': {\n deps: ['jquery'],\n exports: 'jQuery.fn.scroll'\n }\n}\n```\n### require.js插件\nrequire.js还提供一系列插件,实现一些特定的功能。\ndomready插件,可以让回调函数在页面DOM结构加载完成后再运行。\n```\nrequire(['domready!'], function (doc){\n // called once the DOM is ready\n});\n```\ntext和image插件,则是允许require.js加载文本和图片文件。\n```\ndefine([\n 'text!review.txt',\n 'image!cat.jpg'\n ],\n function(review,cat){\n console.log(review);\n document.body.appendChild(cat);\n }\n);\n```\n类似的插件还有json和mdown,用于加载json文件和markdown文件。\n> http://www.ruanyifeng.com/blog/2012/10/javascript_module.html\n> http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html\n> http://www.ruanyifeng.com/blog/2012/11/require_js.html","source":"_posts/javascript模块化-2016-07-10.md","raw":"title: javascript模块化\ndate: 2016-07-10 12:15:40\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 模块化\n---\n# 模块化\n随着网站逐渐变成\"互联网应用程序\",嵌入网页的Javascript代码越来越庞大,越来越复杂。\n网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。\nJavascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。但是,Javascript不是一种模块化编程语言,它不支持\"类\"(class),更遑论\"模块\"module)了。(正在制定中的ECMAScript标准第六版,将正式支持\"类\"和\"模块\",但还需要很长时间才能投入实用。)\nJavascript社区做了很多努力,在现有的运行环境中,实现\"模块\"的效果。本文总结了当前"Javascript模块化编程"的最佳实践,说明如何投入实用。\n<!--more-->\n## 实现模块化的方法\n\n### 原始写法\n模块就是实现特定功能的一组方法。只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。\n```\nfunction m1(){\n //...\n }\nfunction m2(){\n //...\n}\n```\n上面的函数`m1()`和`m2()`,组成一个模块。使用的时候,直接调用就行了。这种做法的缺点很明显:\"污染\"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。\n\n### 对象写法\n为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。\n```\nvar module1 = new Object({\n _count : 0,\n m1 : function (){\n //...\n },\n m2 : function (){\n //...\n }\n});\n```\n上面的函数`m1()`和`m2()`,都封装在module1对象里。使用的时候,就是调用这个对象的属性 `module1.m1();`。\n但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值 `module1._count = 5;`。\n\n### 立即执行函数写法\n使用\"立即执行函数\"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。\n```\nvar module1 = (function(){\n var _count = 0;\n var m1 = function(){\n //...\n };\n var m2 = function(){\n //...\n };\n return {\n m1 : m1,\n m2 : m2\n 34};\n})();\n```\n使用上面的写法,外部代码无法读取内部的_count变量。\nmodule1就是Javascript模块的基本写法。下面,再对这种写法进行加工。\n\n### 立即执行函数放大模式\n如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用\"放大模式\"(augmentation)。\n```\nvar module1 = (function (mod){\n mod.m3 = function () {\n //...\n };\n return mod;\n })(module1);\n```\n在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用\"宽放大模式\"。\n\n### 输入全局变量\n独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。为了在模块内部调用全局变量,必须显式地将其他变量输入模块。\n```\nvar module1 = (function ($, YAHOO) {\n //...\n})(jQuery, YAHOO);\n```\n上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。\n\n## CommonJS&AMD&CMD\n先想一想,为什么模块很重要?\n因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。\n但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了。目前,通行的Javascript模块规范共有两种:`CommonJS`和`AMD`\n\n### CommonJS规范(Node.js)\n```\nvar x = 5;\nvar addX = function(value) {\n return value + x;\n};\n\nmodule.exports.x = x;\nmodule.exports.addX = addX;\n```\n2009年,美国程序员Ryan Dahl创造了`node.js`项目,将javascript语言用于服务器端编程。\n这标志\"Javascript模块化编程\"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。\nnode.js的[模块系统](https://nodejs.org/docs/latest/api/modules.html),就是参照[CommonJS](http://wiki.commonjs.org/wiki/CommonJS)规范实现的。在CommonJS中,有一个全局性方法`require()`,用于加载模块。\n假定有一个数学模块`math.js`,就可以像下面这样加载。\n```\nvar math = require('math');\nmath.add(2,3); // 5\n```\n{% asset_img guanxi.png %}\n\n### AMD规范(RequireJS)\n有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。\n但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?\n第二行`math.add(2, 3)`,在第一行`require('math')`之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。\n这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于\"假死\"状态。\n因此,浏览器端的模块,不能采用\"同步加载\"(synchronous),只能采用\"异步加载\"(asynchronous)。这就是AMD规范诞生的背景。\n[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)是\"Asynchronous Module Definition\"的缩写,意思就是\"异步模块定义\"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。\nAMD也采用`require()`语句加载模块,但是不同于CommonJS,它要求两个参数:\n```\nrequire([module], callback);\n```\n第一个参数`[module]`,是一个数组,里面的成员就是要加载的模块;第二个参数`callback`,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:\n```\nrequire(['math'], function (math) {\n math.add(2, 3);\n});\n```\n`math.add()`与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:[require.js](http://requirejs.org/)和[curl.js](https://github.com/cujojs/curl)。\n```\n//规范\ndefine(id?, dependencies?, factory);\ndefine.amd = {};\n\n//写法1\ndefine(function(require, exports, module) {\n var $ = require('jquery');\n //code here\n});\n\n//写法2\ndefine(['jquery'], function($) {\n //code here\n});\n\n//写法3\ndefine(['require', 'jquery'], function(require) {\n var $ = require('jquery');\n //code here\n});\n```\n### CMD规范(SeaJS)\nCMD是国内玉伯大神在开发SeaJS的时候提出来的,[与AMD规范的区别](https://github.com/seajs/seajs/issues/277);\nCMD规范和AMD类似,都主要运行于浏览器端,写法上看起来也很类似。主要是区别在于模块初始化时机,AMD中只要模块作为依赖时,就会加载并初始化,而CMD中,模块作为依赖且被引用时才会初始化,否则只会加载。如下规范定义及一般写法:\n```\n//规范\ndefine(factory);\ndefine.cmd = {};\n\n//写法1\ndefine(function(require, exports, module) {\n var $ = require('jquery');\n //code here\n});\n\n//写法2\ndefine(['jquery'], function(require, exports, module) {\n var $ = require('jquery');\n //code here\n});\n```\n\n## 模块化开发上线部署\n\n1. 压缩\n2. 合并\n3. 更新版本\n\n* 不能直接压缩:因为模块加载器在分析模块的依赖时,会先将模块的工厂函数`factory.toString()`,然后通过正则匹配`require`局部变量,这样意味着不能直接通过压缩工具进行压缩,若require这个变量被替换,加载器与自动化工具将无法获取模块的依赖。\n* 不能直接合并:我们在开发时,通过是省略模块的ID的,如果多个模块直接合并成一个文件,这样加载器无法区分不同模块了。\n所以采用模块化开发上线部署时,压缩前通常通过工具先提取依赖,这样require就可以当做普通变量直接压缩了,同时也不再需要加载器分析提取依赖,对于提升性能也是蛮有好处的。合并前同样也需要借助工具先提取各个模块的ID,然后才能按照合并配置进行合并。整个过程如下:\n{% asset_img process.png %}\nSeaJS和RequireJS官方都提供了构建工具,如SeaJS的spm,RequireJS的r.js,当然也很多grunt和glup插件可使用,区别于普通压缩合并就是要有提取依赖及模块ID的能力。\n对比构建工具使用感受,spm的整个上手并不是那么顺畅,配置太复杂。r.js使用相对简单,只需要配置好合并规范的配置文件即可,其它grunt或glup提供的插件相对灵活,可根据自身业务灵活配置。\n完成压缩合并后,最后一步就是更新版本号上线了,通常是需要手动更新版本号,或是修改控制版本号的配置参数。这一步基本上还是需要人力手动干预一下。当然如果合并是通过后端的combo服务就不需要了。不管怎么说,通过这些工具的组合使用,整个开发上线流程基本实现自动化了,比较方便。\n\n## require.js的用法\n### 为什么要用require.js?\n最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。\n```\n<script src=\"1.js\"></script>\n<script src=\"2.js\"></script>\n<script src=\"3.js\"></script>\n<script src=\"4.js\"></script>\n<script src=\"5.js\"></script>\n<script src=\"6.js\"></script>\n```\n这段代码依次加载多个js文件。\n这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。\nrequire.js的诞生,就是为了解决这两个问题:\n* 实现js文件的异步加载,避免网页失去响应;\n* 管理模块之间的依赖性,便于代码的编写和维护。\n### require.js的加载\n使用require.js的第一步,是先去官方网站下载最新版本。下载后,假定把它放在js子目录下面,就可以加载了。\n```\n<script src=\"js/require.js\" defer async=\"true\" ></script>\n```\n`async`属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持`defer`,所以把`defer`也写上。\n加载`require.js`以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是main.js,也放在js目录下面。那么,只需要写成下面这样就行了:\n```\n<script src=\"js/require.js\" data-main=\"js/main\"></script>\n```\n`data-main`属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的`main.js`,这个文件会第一个被`require.js`加载。由于`require.js`默认的文件后缀名是js,所以可以把main.js简写成main。\n\n### 主模块的写法\n上一节的main.js,我把它称为\"主模块\",意思是整个网页的入口代码。它有点像C语言的main()函数,所有代码都从这儿开始运行。下面就来看,怎么写main.js。\n```\n// main.js\nrequire(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){\n // some code here\n});\n```\n`require()`函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是`['moduleA', 'moduleB', 'moduleC']`,即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。\n`require()`异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。\n下面,我们看一个实际的例子。\n假定主模块依赖`jquery、underscore和backbone`这三个模块,main.js就可以这样写:\n```\nrequire(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){\n // some code here\n});\n```\nrequire.js会先加载jQuery、underscore和backbone,然后再运行回调函数。主模块的代码就写在回调函数中。\n### 模块的加载\n上一节最后的示例中,主模块的依赖模块是`['jquery', 'underscore', 'backbone']`。默认情况下,require.js假定这三个模块与main.js在同一个目录,文件名分别为jquery.js,underscore.js和backbone.js,然后自动加载。\n使用`require.config()`方法,我们可以对模块的加载行为进行自定义。`require.config()`就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。\n```\nrequire.config({\n paths: {\n \"jquery\": \"jquery.min\",\n \"underscore\": \"underscore.min\",\n \"backbone\": \"backbone.min\"\n }\n});\n```\n上面的代码给出了三个模块的文件名,路径默认与main.js在同一个目录(js子目录)。如果这些模块在其他目录,比如js/lib目录,则有两种写法。一种是逐一指定路径。\n```\nrequire.config({\n paths: {\n \"jquery\": \"lib/jquery.min\",\n \"underscore\": \"lib/underscore.min\",\n \"backbone\": \"lib/backbone.min\"\n }\n});\n```\n另一种则是直接改变基目录(baseUrl)。\n```\nrequire.config({\n baseUrl: \"js/lib\",\n paths: {\n \"jquery\": \"jquery.min\",\n \"underscore\": \"underscore.min\",\n \"backbone\": \"backbone.min\"\n }\n});\n```\n如果某个模块在另一台主机上,也可以直接指定它的网址,比如:\n```\nrequire.config({\n paths: {\n \"jquery\": \"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min\"\n }\n});\n```\nrequire.js要求,每个模块是一个单独的js文件。这样的话,如果加载多个模块,就会发出多次HTTP请求,会影响网页的加载速度。因此,require.js提供了一个[优化工具](http://requirejs.org/docs/optimization.html),当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少HTTP请求数。\n### AMD模块的写法\nrequire.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。\n具体来说,就是模块必须采用特定的`define()`函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在`define()`函数之中。\n假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:\n```\n// math.js\ndefine(function (){\n var add = function (x,y){\n return x+y;\n };\n return {\n add: add\n };\n});\n```\n加载方法如下:\n```\n// main.js\nrequire(['math'], function (math){\n alert(math.add(1,1));\n});\n```\n如果这个模块还依赖其他模块,那么`define()`函数的第一个参数,必须是一个数组,指明该模块的依赖性。\n```\ndefine(['myLib'], function(myLib){\n function foo(){\n myLib.doSomething();\n }\n return {\n foo : foo\n };\n});\n```\n当`require()`函数加载上面这个模块的时候,就会先加载myLib.js文件。\n### 加载非规范的模块\n理论上,require.js加载的模块,必须是按照AMD规范、用`define()`函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如jQuery)符合AMD规范,更多的库并不符合。那么,require.js是否能够加载非规范的模块呢?回答是可以的。\n这样的模块在用`require()`加载之前,要先用`require.config()`方法,定义它们的一些特征。\n举例来说,`underscore`和`backbone`这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。\n```\nrequire.config({\n shim: {\n 'underscore':{\n exports: '_'\n },\n 'backbone': {\n deps: ['underscore', 'jquery'],\n exports: 'Backbone'\n }\n }\n});\n```\n`require.config()`接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义:\n1. `exports`值(输出的变量名),表明这个模块外部调用时的名称;\n2. `deps`数组,表明该模块的依赖性。\n比如,jQuery的插件可以这样定义:\n```\nshim: {\n 'jquery.scroll': {\n deps: ['jquery'],\n exports: 'jQuery.fn.scroll'\n }\n}\n```\n### require.js插件\nrequire.js还提供一系列插件,实现一些特定的功能。\ndomready插件,可以让回调函数在页面DOM结构加载完成后再运行。\n```\nrequire(['domready!'], function (doc){\n // called once the DOM is ready\n});\n```\ntext和image插件,则是允许require.js加载文本和图片文件。\n```\ndefine([\n 'text!review.txt',\n 'image!cat.jpg'\n ],\n function(review,cat){\n console.log(review);\n document.body.appendChild(cat);\n }\n);\n```\n类似的插件还有json和mdown,用于加载json文件和markdown文件。\n> http://www.ruanyifeng.com/blog/2012/10/javascript_module.html\n> http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html\n> http://www.ruanyifeng.com/blog/2012/11/require_js.html","slug":"javascript模块化","published":1,"updated":"2016-07-10T09:51:46.342Z","layout":"post","photos":[],"link":"","_id":"citf9om83001jskv7j6u3ynqj"},{"title":"function","date":"2016-07-02T06:56:25.000Z","comments":1,"_content":"# 概述\n函数就是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。\n\nJavaScript有三种方法,可以声明一个函数。\n<!--more-->\n## 函数的声明\n\n### function命令\n`function`命令声明的代码区块,就是一个函数。`function`命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。\n```\nfunction print(s) {\n console.log(s);\n}\n```\n上面的代码命名了一个`print`函数,以后使用`print()`这种形式,就可以调用相应的代码。这叫做函数的声明(`Function Declaration`)。\n### 函数表达式\n除了用`function`命令声明函数,还可以采用变量赋值的写法。\n```\nvar print = function(s) {\n console.log(s);\n};\n```\n这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(`Function Expression`),因为赋值语句的等号右侧只能放表达式。\n采用函数表达式声明函数时,`function`命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。\n```\nvar print = function x(){\n console.log(typeof x);\n};\n\nx\n// ReferenceError: x is not defined\n\nprint()\n// function\n```\n上面代码在函数表达式中,加入了函数名`x`。这个x只在函数体内部可用,指代函数表达式本身,其他地方都不可用。这种写法的用处有两个,一是可以在函数体内部调用自身,二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。因此,下面的形式声明函数也非常常见。\n```\nvar f = function f() {};\n```\n需要注意的是,函数的表达式需要在语句的结尾加上分号,表示语句结束。而函数的声明在结尾的大括号后面不用加分号。总的来说,这两种声明函数的方式,差别很细微(参阅后文《变量提升》一节),这里可以近似认为是等价的。\n### Function构造函数\n还有第三种声明函数的方式:`Function`构造函数。\n```\nvar add = new Function(\n 'x',\n 'y',\n 'return (x + y)'\n);\n\n// 等同于\n\nfunction add(x, y) {\n return (x + y);\n}\n```\n在上面代码中,`Function`构造函数接受三个参数,除了最后一个参数是`add函数`的“函数体”,其他参数都是`add函数`的参数。`如果只有一个参数,该参数就是函数体`。\n```\nvar foo = new Function(\n 'return \"hello world\"'\n);\n\n// 等同于\n\nfunction foo() {\n return 'hello world';\n}\n```\n`Function`构造函数可以不使用`new`命令,返回结果完全一样。\n\n总的来说,这种声明函数的方式非常不直观,几乎无人使用,但是我在一个地方用的比较多就是字符串代码的执行可以使用`new Function()`。\n## 函数的重复声明\n如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。\n```\nfunction f() {\n console.log(1);\n}\nf() // 2\n\nfunction f() {\n console.log(2);\n}\nf() // 2\n```\n上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升(参见下文),前一次声明在任何时候都是无效的,这一点要特别注意。\n## 圆括号运算符,return语句和递归\n调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。\n```\nfunction add(x, y) {\n return x + y;\n}\n\nadd(1, 1) // 2\n```\n上面代码中,函数名后面紧跟一对圆括号,就会调用这个函数。\n\n函数体内部的`return`语句,表示返回。JavaScript引擎遇到`return`语句,就直接返回`return`后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,`return`语句所带的那个表达式,就是函数的返回值。`return`语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回`undefined`。\n\n函数可以调用自身,这就是递归(`recursion`)。下面就是通过递归,计算斐波那契数列的代码。\n```\nfunction fib(num) {\n if (num > 2) {\n return fib(num - 2) + fib(num - 1);\n } else {\n return 1;\n }\n}\n\nfib(6) // 8\n```\n上面代码中,`fib`函数内部又调用了`fib`,计算得到斐波那契数列的第6个元素是8。\n\n## 第一等公民\nJavaScript语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。\n\n由于函数与其他数据类型地位平等,所以在JavaScript语言中又称函数为第一等公民。\n```\nfunction add(x, y) {\n return x + y;\n}\n\n// 将函数赋值给一个变量\nvar operator = add;\n\n// 将函数作为参数和返回值\nfunction a(op){\n return op;\n}\na(add)(1, 1)\n// 2\n```\n## 函数名的提升\nJavaScript引擎将函数名视同变量名,所以采用`function`命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。\n```\nf();\n\nfunction f() {}\n```\n表面上,上面代码好像在声明之前就调用了函数`f`。但是实际上,由于“变量提升”,函数`f`被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错。\n```\nf();\nvar f = function (){};\n// TypeError: undefined is not a function\n```\n上面的代码等同于下面的形式。\n```\nvar f;\nf();\nf = function () {};\n```\n上面代码第二行,调用`f`的时候,`f`只是被声明了,还没有被赋值,等于`undefined`,所以会报错。因此,如果同时采用`function`命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。\n```\nvar f = function() {\n console.log('1');\n}\n\nfunction f() {\n console.log('2');\n}\n\nf() // 1\n```\n## 不能在条件语句中声明函数\n根据ECMAScript的规范,不得在非函数的代码块中声明函数,最常见的情况就是`if`和`try`语句。\n```\nif (foo) {\n function x() {}\n}\n\ntry {\n function x() {}\n} catch(e) {\n console.log(e);\n}\n```\n上面代码分别在`if`代码块和`try`代码块中声明了两个函数,按照语言规范,这是不合法的。但是,实际情况是各家浏览器往往并不报错,能够运行。\n\n但是由于存在函数名的提升,所以在条件语句中声明函数,可能是无效的,这是非常容易出错的地方。\n```\nif (false){\n function f() {}\n}\n\nf() // 不报错\n```\n上面代码的原始意图是不声明函数`f`,但是由于`f`的提升,导致`if`语句无效,所以上面的代码不会报错。要达到在条件语句中定义函数的目的,只有使用函数表达式。\n```\nif (false) {\n var f = function () {};\n}\n\nf() // undefined\n```\n# 函数的属性和方法\n## name属性\n`name`属性返回紧跟在`function`关键字之后的那个函数名。\n```\nfunction f1() {}\nf1.name // 'f1'\n\nvar f2 = function () {};\nf2.name // ''\n\nvar f3 = function myName() {};\nf3.name // 'myName'\n```\n上面代码中,函数的`name`属性总是返回紧跟在`function`关键字之后的那个函数名。对于`f2`来说,返回空字符串,匿名函数的`name`属性总是为`空字符串`;对于`f3`来说,返回函数表达式的名字(真正的函数名还是`f3`,`myName`这个名字只在函数体内部可用)。\n## length属性\n`length`属性返回函数预期传入的参数个数,即函数定义之中的参数个数。\n```\nfunction f(a, b) {}\nf.length // 2\n```\n上面代码定义了空函数`f`,它的`length`属性就是定义时的参数个数。不管调用时输入了多少个参数,`length`属性始终等于`2`。\n\n`length`属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(`overload`)。\n## toString()\n函数的`toString`方法返回函数的源码。\n```\nfunction f() {\n a();\n b();\n c();\n}\n\nf.toString()\n// function f() {\n// a();\n// b();\n// c();\n// }\n```\n函数内部的注释也可以返回。\n```\nfunction f() {/*\n 这是一个\n 多行注释\n*/}\n\nf.toString()\n// \"function f(){/*\n// 这是一个\n// 多行注释\n// */}\"\n```\n利用这一点,可以变相实现多行字符串。\n```\nvar multiline = function (fn) {\n var arr = fn.toString().split('\\n');\n return arr.slice(1, arr.length - 1).join('\\n');\n};\n\nfunction f() {/*\n 这是一个\n 多行注释\n*/}\n\nmultiline(f.toString())\n// \" 这是一个\n// 多行注释\"\n```\n## 函数作用域\n### 定义\n作用域(`scope`)指的是变量存在的范围。Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。\n\n在函数外部声明的变量就是全局变量(`global variable`),它可以在函数内部读取。\n```\nvar v = 1;\n\nfunction f(){\n console.log(v);\n}\n\nf()\n// 1\n```\n上面的代码表明,函数`f`内部可以读取全局变量v。\n\n在函数内部定义的变量,外部无法读取,称为“局部变量”(`local variable`)。\n```\nfunction f(){\n var v = 1;\n}\n\nv // ReferenceError: v is not defined\n```\n上面代码中,变量`v`在函数内部定义,所以是一个局部变量,函数之外就无法读取。\n\n函数内部定义的变量,会在该作用域内覆盖同名全局变量。\n```\nvar v = 1;\n\nfunction f(){\n var v = 2;\n console.log(v);\n}\n\nf() // 2\nv // 1\n```\n上面代码中,变量`v`同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v覆盖了全局变量`v`。\n\n注意,对于`var`命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。\n```\nif (true) {\n var x = 5;\n}\nconsole.log(x); // 5\n```\n上面代码中,变量`x`在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。\n### 函数内部的变量提升\n与全局作用域一样,函数作用域内部也会产生“变量提升”现象。`var`命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。\n```\nfunction foo(x) {\n if (x > 100) {\n var tmp = x - 100;\n }\n}\n```\n上面的代码等同于\n```\nfunction foo(x) {\n var tmp;\n if (x > 100) {\n tmp = x - 100;\n };\n}\n```\n### 函数本身的作用域\n函数本身也是一个值,也有自己的作用域。它的作用域绑定其声明时所在的作用域。\n```\nvar a = 1;\nvar x = function () {\n console.log(a);\n};\n\nfunction f() {\n var a = 2;\n x();\n}\n\nf() // 1\n```\n上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。\n\n很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。\n```\nvar x = function (){\n console.log(a);\n};\n\nfunction y(f){\n var a = 2;\n f();\n}\n\ny(x)\n// ReferenceError: a is not defined\n```\n上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。\n# 参数\n## 概述\n函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。\n```\nfunction square(x) {\n return x * x;\n}\n\nsquare(2) // 4\nsquare(3) // 9\n```\n上式的`x`就是`square`函数的参数。每次运行的时候,需要提供这个值,否则得不到结果。\n## 参数的省略\n函数参数不是必需的,Javascript允许省略参数。\n```\nfunction f(a, b) {\n return a;\n}\n\nf(1, 2, 3) // 1\nf(1) // 1\nf() // undefined\n\nf.length // 2\n```\n上面代码的函数`f`定义了两个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript都不会报错。\n\n被省略的参数的值就变为`undefined`。需要注意的是,函数的`length`属性与实际传入的参数个数无关,只反映函数预期传入的参数个数。\n\n但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入`undefined`。\n```\nfunction f(a, b) {\n return a;\n}\n\nf( , 1) // SyntaxError: Unexpected token ,(…)\nf(undefined, 1) // undefined\n```\n上面代码中,如果省略第一个参数,就会报错。\n## 默认值\n通过下面的方法,可以为函数的参数设置默认值。\n```\nfunction f(a){\n a = a || 1;\n return a;\n}\n\nf('') // 1\nf(0) // 1\n```\n上面代码的`||`表示“或运算”,即如果`a`有值,则返回`a`,否则返回事先设定的默认值(上例为1)。\n\n这种写法会对a进行一次布尔运算,只有为`true`时,才会返回a。可是,除了`undefined`以外,`0`、`空字符`、`null`等的布尔值也是`false`。也就是说,在上面的函数中,不能让`a`等于`0`或`空字符串`,否则在明明有参数的情况下,也会返回默认值。\n\n为了避免这个问题,可以采用下面更精确的写法。\n```\nfunction f(a) {\n (a !== undefined && a !== null) ? a = a : a = 1;\n return a;\n}\n\nf() // 1\nf('') // \"\"\nf(0) // 0\n```\n上面代码中,函数`f`的参数是`空字符`或`0`,都不会触发参数的默认值。\n## 传递方式\n函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。\n```\nvar p = 2;\n\nfunction f(p) {\n p = 3;\n}\nf(p);\n\np // 2\n```\n上面代码中,变量p是一个原始类型的值,传入函数`f`的方式是传值传递。因此,在函数内部,`p`的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。\n\n但是,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。\n```\nvar obj = {p: 1};\n\nfunction f(o) {\n o.p = 2;\n}\nf(obj);\n\nobj.p // 2\n```\n上面代码中,传入函数f的是参数对象`obj`的地址。因此,在函数内部修改`obj`的属性`p`,会影响到原始值。\n\n注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。\n```\nvar obj = [1, 2, 3];\n\nfunction f(o){\n o = [2, 3, 4];\n}\nf(obj);\n\nobj // [1, 2, 3]\n```\n上面代码中,在函数`f`内部,参数对象`obj`被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o)与实际参数`obj`存在一个赋值关系。\n```\n// 函数f内部\no = obj;\n```\n上面代码中,对`o`的修改都会反映在`obj`身上。但是,如果对`o`赋予一个新的值,就等于切断了`o`与`obj`的联系,导致此后的修改都不会影响到`obj`了。\n\n某些情况下,如果需要对某个原始类型的变量,获取传址传递的效果,可以将它写成全局对象的属性。\n```\nvar a = 1;\n\nfunction f(p) {\n window[p] = 2;\n}\nf('a');\n\na // 2\n```\n上面代码中,变量`a`本来是传值传递,但是写成`window`对象的属性,就达到了传址传递的效果。\n## 同名参数\n如果有同名的参数,则取最后出现的那个值。\n```\nfunction f(a, a) {\n console.log(a);\n}\n\nf(1, 2) // 2\n```\n上面的函数`f`有两个参数,且参数名都是`a`。取值的时候,以后面的`a`为准。即使后面的`a`没有值或被省略,也是以其为准。\n```\nfunction f(a, a){\n console.log(a);\n}\n\nf(1) // undefined\n```\n调用函数`f`的时候,没有提供第二个参数,`a`的取值就变成了`undefined`。这时,如果要获得第一个`a`的值,可以使用`arguments`对象。\n```\nfunction f(a, a){\n console.log(arguments[0]);\n}\n\nf(1) // 1\n```\n## arguments对象\n### 定义\n由于JavaScript允许函数有不定数目的参数,所以我们需要一种机制,可以在函数体内部读取所有参数。这就是`arguments`对象的由来。\n\n`arguments`对象包含了函数运行时的所有参数,`arguments[0]`就是第一个参数,`arguments[1]`就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。\n```\nvar f = function(one) {\n console.log(arguments[0]);\n console.log(arguments[1]);\n console.log(arguments[2]);\n}\n\nf(1, 2, 3)\n// 1\n// 2\n// 3\n```\n`arguments`对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)。\n```\nvar f = function(a, b) {\n arguments[0] = 3;\n arguments[1] = 2;\n return a + b;\n}\n\nf(1, 1)\n// 5\n```\n可以通过`arguments`对象的`length`属性,判断函数调用时到底带几个参数。\n```\nfunction f() {\n return arguments.length;\n}\n\nf(1, 2, 3) // 3\nf(1) // 1\nf() // 0\n```\n### 与数组的关系\n需要注意的是,虽然`arguments`很像数组,但它是一个对象。数组专有的方法(比如`slice`和`forEach`),不能在`arguments`对象上直接使用。\n\n但是,可以通过`apply`方法,把`arguments`作为参数传进去,这样就可以让`arguments`使用数组方法了。\n```\n// 用于apply方法\nmyfunction.apply(obj, arguments).\n\n// 使用与另一个数组合并\nArray.prototype.concat.apply([1,2,3], arguments)\n```\n要让`arguments`对象使用数组方法,真正的解决方法是将`arguments`转为真正的数组。下面是两种常用的转换方法:`slice`方法和逐一填入新数组。\n```\nvar args = Array.prototype.slice.call(arguments);\n\n// or\n\nvar args = [];\nfor (var i = 0; i < arguments.length; i++) {\n args.push(arguments[i]);\n}\n```\n### callee属性\n`arguments`对象带有一个`callee`属性,返回它所对应的原函数。\n```\nvar f = function(one) {\n console.log(arguments.callee === f);\n}\n\nf() // true\n```\n可以通过`arguments.callee`,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。\n# 函数的其他知识点\n## 闭包\n闭包(`closure`)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。\n\n要理解闭包,首先必须理解变量作用域。前面提到,JavaScript有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。\n```\nvar n = 999;\n\nfunction f1() {\n console.log(n);\n}\nf1() // 999\n```\n上面代码中,函数`f1`可以读取全局变量`n`。\n\n但是,在函数外部无法读取函数内部声明的变量。\n```\nfunction f1() {\n var n = 999;\n}\n\nconsole.log(n)\n// Uncaught ReferenceError: n is not defined(\n```\n上面代码中,函数`f1`内部声明的变量`n`,函数外是无法读取的。\n\n如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。\n```\nfunction f1() {\n var n = 999;\n function f2() {\n console.log(n); // 999\n }\n}\n```\n上面代码中,函数`f2`就在函数`f1`内部,这时`f1`内部的所有局部变量,对`f2`都是可见的。但是反过来就不行,`f2`内部的局部变量,对f1就是不可见的。这就是JavaScript语言特有的”链式作用域”结构(`chain scope`),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。\n\n既然`f2`可以读取`f1`的局部变量,那么只要把`f2`作为返回值,我们不就可以在`f1`外部读取它的内部变量了吗!\n```\nfunction f1() {\n var n = 999;\n function f2() {\n console.log(n);\n }\n return f2;\n}\n\nvar result = f1();\nresult(); // 999\n```\n上面代码中,函数`f1`的返回值就是函数`f2`,由于`f2`可以读取`f1`的内部变量,所以就可以在外部获得`f1`的内部变量了。\n\n闭包就是函数`f2`,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如`f2`记住了它诞生的环境`f1`,所以从`f2`可以得到`f1`的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。\n\n闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。\n```\nfunction createIncrementor(start) {\n return function () {\n return start++;\n };\n}\n\nvar inc = createIncrementor(5);\n\ninc() // 5\ninc() // 6\ninc() // 7\n```\n上面代码中,`start`是函数`createIncrementor`的内部变量。通过闭包,`start`的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包`inc`使得函数`createIncrementor`的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。\n\n为什么会这样呢?原因就在于`inc`始终在内存中,而inc的存在依赖于`createIncrementor`,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。\n\n闭包的另一个用处,是封装对象的私有属性和私有方法。\n```\nfunction Person(name) {\n var _age;\n function setAge(n) {\n _age = n;\n }\n function getAge() {\n return _age;\n }\n\n return {\n name: name,\n getAge: getAge,\n setAge: setAge\n };\n}\n\nvar p1 = person('张三');\np1.setAge(25);\np1.getAge() // 25\n```\n上面代码中,函数`Person`的内部变量`_age`,通过闭包`getAge`和`setAge`,变成了返回对象`p1`的私有变量。\n\n注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。\n## 立即调用的函数表达式(IIFE)\n在Javascript中,一对圆括号`()`是一种运算符,跟在函数名之后,表示调用该函数。比如,`print()`就表示调用`print`函数。\n\n有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。\n```\nfunction(){ /* code */ }();\n// SyntaxError: Unexpected token (\n```\n产生这个错误的原因是,`function`这个关键字即可以当作语句,也可以当作表达式。\n```\n// 语句\nfunction f() {}\n\n// 表达式\nvar f = function f() {}\n```\n为了避免解析上的歧义,JavaScript引擎规定,如果`function`关键字出现在行首,一律解释成语句。因此,JavaScript引擎看到行首是`function`关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。\n\n解决方法就是不要让`function`出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。\n```\n(function(){ /* code */ }());\n// 或者\n(function(){ /* code */ })();\n```\n上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称IIFE。\n\n注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个IIFE,可能就会报错。\n```\n// 报错\n(function(){ /* code */ }())\n(function(){ /* code */ }())\n```\n上面代码的两行之间没有分号,JavaScript会将它们连在一起解释,将第二行解释为第一行的参数。\n\n推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。\n```\nvar i = function(){ return 10; }();\ntrue && function(){ /* code */ }();\n0, function(){ /* code */ }();\n```\n甚至像下面这样写,也是可以的。\n```\n!function(){ /* code */ }();\n~function(){ /* code */ }();\n-function(){ /* code */ }();\n+function(){ /* code */ }();\n```\n`new`关键字也能达到这个效果。\n```\nnew function(){ /* code */ }\n\nnew function(){ /* code */ }()\n// 只有传递参数时,才需要最后那个圆括号\n```\n通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。\n```\n// 写法一\nvar tmp = newData;\nprocessData(tmp);\nstoreData(tmp);\n\n// 写法二\n(function (){\n var tmp = newData;\n processData(tmp);\n storeData(tmp);\n}());\n```\n上面代码中,写法二比写法一更好,因为完全避免了污染全局变量。\n# eval 命令\n`eval`命令的作用是,将字符串当作语句执行。\n```\neval('var a = 1;');\na // 1\n```\n上面代码将字符串当作语句运行,生成了变量`a`。\n\n放在`eval`中的字符串,应该有独自存在的意义,不能用来与`eval`以外的命令配合使用。举例来说,下面的代码将会报错。\n```\neval('return;');\n```\n由于`eval`没有自己的作用域,都在当前作用域内执行,因此可能会修改其他外部变量的值,造成安全问题。\n```\nvar a = 1;\neval('a = 2');\n\na // 2\n```\n上面代码中,`eval`命令修改了外部变量`a`的值。由于这个原因,所以`eval`有安全风险,如果无法做到作用域隔离,最好不要使用。此外,`eval`的命令字符串不会得到JavaScript引擎的优化,运行速度较慢,也是另一个不应该使用它的理由。通常情况下,`eval`最常见的场合是解析`JSON`数据字符串,正确的做法是这时应该使用浏览器提供的`JSON.parse`方法。\n\nECMAScript 5将`eval`的使用分成两种情况,像上面这样的调用,就叫做“直接使用”,这种情况下`eval`的作用域就是当前作用域(即全局作用域或函数作用域)。另一种情况是,`eval`不是直接调用,而是“间接调用”,此时`eval`的作用域总是全局作用域。\n```\nvar a = 1;\n\nfunction f(){\n var a = 2;\n var e = eval;\n e('console.log(a)');\n}\n\nf() // 1\n```\n上面代码中,`eval`是间接调用,所以即使它是在函数中,它的作用域还是全局作用域,因此输出的`a`为全局变量。\n\n`eval`的间接调用的形式五花八门,只要不是直接调用,几乎都属于间接调用。\n```\neval.call(null, '...')\nwindow.eval('...')\n(1, eval)('...')\n(eval, eval)('...')\n(1 ? eval : 0)('...')\n(__ = eval)('...')\nvar e = eval; e('...')\n(function(e) { e('...') })(eval)\n(function(e) { return e })(eval)('...')\n(function() { arguments[0]('...') })(eval)\nthis.eval('...')\nthis['eval']('...')\n[eval][0]('...')\neval.call(this, '...')\neval('eval')('...')\n```\n上面这些形式都是`eval`的间接调用,因此它们的作用域都是全局作用域。\n\n# new Function()\n\n与`eval`作用类似的还有`Function`构造函数。利用它生成一个函数,然后调用该函数,也能将字符串当作命令执行。\n```\nvar jsonp = 'foo({\"id\":42})';\n\nvar f = new Function( \"foo\", jsonp );\n// 相当于定义了如下函数\n// function f(foo) {\n// foo({\"id\":42});\n// }\n\nf(function(json){\n console.log( json.id ); // 42\n})\n```\n`new Function生成的function始终存在于全局作用域中。`\n```\nvar a = 123;\n(function() {\n\tvar a = 1;\n\tfunction f() {\n\t\tvar a = 2;\n\t\tvar bc = new Function('console.log(a)');\n\t\tbc();\n\t}\n\tf();//输出 123,证明 new Function生成的function始终存在于全局作用域中\n}());\n```\n\n上面代码中,`jsonp`是一个字符串,`Function`构造函数将这个字符串,变成了函数体。调用该函数的时候,`jsonp`就会执行。这种写法的实质是将代码放到函数作用域执行,避免对全局作用域造成影响。\n转自: [阮一峰教程](http://javascript.ruanyifeng.com/grammar/function.html#toc15)","source":"_posts/function-2016-07-02.md","raw":"title: function\ndate: 2016-07-02 14:56:25\ntags:\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# 概述\n函数就是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。\n\nJavaScript有三种方法,可以声明一个函数。\n<!--more-->\n## 函数的声明\n\n### function命令\n`function`命令声明的代码区块,就是一个函数。`function`命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。\n```\nfunction print(s) {\n console.log(s);\n}\n```\n上面的代码命名了一个`print`函数,以后使用`print()`这种形式,就可以调用相应的代码。这叫做函数的声明(`Function Declaration`)。\n### 函数表达式\n除了用`function`命令声明函数,还可以采用变量赋值的写法。\n```\nvar print = function(s) {\n console.log(s);\n};\n```\n这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(`Function Expression`),因为赋值语句的等号右侧只能放表达式。\n采用函数表达式声明函数时,`function`命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。\n```\nvar print = function x(){\n console.log(typeof x);\n};\n\nx\n// ReferenceError: x is not defined\n\nprint()\n// function\n```\n上面代码在函数表达式中,加入了函数名`x`。这个x只在函数体内部可用,指代函数表达式本身,其他地方都不可用。这种写法的用处有两个,一是可以在函数体内部调用自身,二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。因此,下面的形式声明函数也非常常见。\n```\nvar f = function f() {};\n```\n需要注意的是,函数的表达式需要在语句的结尾加上分号,表示语句结束。而函数的声明在结尾的大括号后面不用加分号。总的来说,这两种声明函数的方式,差别很细微(参阅后文《变量提升》一节),这里可以近似认为是等价的。\n### Function构造函数\n还有第三种声明函数的方式:`Function`构造函数。\n```\nvar add = new Function(\n 'x',\n 'y',\n 'return (x + y)'\n);\n\n// 等同于\n\nfunction add(x, y) {\n return (x + y);\n}\n```\n在上面代码中,`Function`构造函数接受三个参数,除了最后一个参数是`add函数`的“函数体”,其他参数都是`add函数`的参数。`如果只有一个参数,该参数就是函数体`。\n```\nvar foo = new Function(\n 'return \"hello world\"'\n);\n\n// 等同于\n\nfunction foo() {\n return 'hello world';\n}\n```\n`Function`构造函数可以不使用`new`命令,返回结果完全一样。\n\n总的来说,这种声明函数的方式非常不直观,几乎无人使用,但是我在一个地方用的比较多就是字符串代码的执行可以使用`new Function()`。\n## 函数的重复声明\n如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。\n```\nfunction f() {\n console.log(1);\n}\nf() // 2\n\nfunction f() {\n console.log(2);\n}\nf() // 2\n```\n上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升(参见下文),前一次声明在任何时候都是无效的,这一点要特别注意。\n## 圆括号运算符,return语句和递归\n调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。\n```\nfunction add(x, y) {\n return x + y;\n}\n\nadd(1, 1) // 2\n```\n上面代码中,函数名后面紧跟一对圆括号,就会调用这个函数。\n\n函数体内部的`return`语句,表示返回。JavaScript引擎遇到`return`语句,就直接返回`return`后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,`return`语句所带的那个表达式,就是函数的返回值。`return`语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回`undefined`。\n\n函数可以调用自身,这就是递归(`recursion`)。下面就是通过递归,计算斐波那契数列的代码。\n```\nfunction fib(num) {\n if (num > 2) {\n return fib(num - 2) + fib(num - 1);\n } else {\n return 1;\n }\n}\n\nfib(6) // 8\n```\n上面代码中,`fib`函数内部又调用了`fib`,计算得到斐波那契数列的第6个元素是8。\n\n## 第一等公民\nJavaScript语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。\n\n由于函数与其他数据类型地位平等,所以在JavaScript语言中又称函数为第一等公民。\n```\nfunction add(x, y) {\n return x + y;\n}\n\n// 将函数赋值给一个变量\nvar operator = add;\n\n// 将函数作为参数和返回值\nfunction a(op){\n return op;\n}\na(add)(1, 1)\n// 2\n```\n## 函数名的提升\nJavaScript引擎将函数名视同变量名,所以采用`function`命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。\n```\nf();\n\nfunction f() {}\n```\n表面上,上面代码好像在声明之前就调用了函数`f`。但是实际上,由于“变量提升”,函数`f`被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错。\n```\nf();\nvar f = function (){};\n// TypeError: undefined is not a function\n```\n上面的代码等同于下面的形式。\n```\nvar f;\nf();\nf = function () {};\n```\n上面代码第二行,调用`f`的时候,`f`只是被声明了,还没有被赋值,等于`undefined`,所以会报错。因此,如果同时采用`function`命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。\n```\nvar f = function() {\n console.log('1');\n}\n\nfunction f() {\n console.log('2');\n}\n\nf() // 1\n```\n## 不能在条件语句中声明函数\n根据ECMAScript的规范,不得在非函数的代码块中声明函数,最常见的情况就是`if`和`try`语句。\n```\nif (foo) {\n function x() {}\n}\n\ntry {\n function x() {}\n} catch(e) {\n console.log(e);\n}\n```\n上面代码分别在`if`代码块和`try`代码块中声明了两个函数,按照语言规范,这是不合法的。但是,实际情况是各家浏览器往往并不报错,能够运行。\n\n但是由于存在函数名的提升,所以在条件语句中声明函数,可能是无效的,这是非常容易出错的地方。\n```\nif (false){\n function f() {}\n}\n\nf() // 不报错\n```\n上面代码的原始意图是不声明函数`f`,但是由于`f`的提升,导致`if`语句无效,所以上面的代码不会报错。要达到在条件语句中定义函数的目的,只有使用函数表达式。\n```\nif (false) {\n var f = function () {};\n}\n\nf() // undefined\n```\n# 函数的属性和方法\n## name属性\n`name`属性返回紧跟在`function`关键字之后的那个函数名。\n```\nfunction f1() {}\nf1.name // 'f1'\n\nvar f2 = function () {};\nf2.name // ''\n\nvar f3 = function myName() {};\nf3.name // 'myName'\n```\n上面代码中,函数的`name`属性总是返回紧跟在`function`关键字之后的那个函数名。对于`f2`来说,返回空字符串,匿名函数的`name`属性总是为`空字符串`;对于`f3`来说,返回函数表达式的名字(真正的函数名还是`f3`,`myName`这个名字只在函数体内部可用)。\n## length属性\n`length`属性返回函数预期传入的参数个数,即函数定义之中的参数个数。\n```\nfunction f(a, b) {}\nf.length // 2\n```\n上面代码定义了空函数`f`,它的`length`属性就是定义时的参数个数。不管调用时输入了多少个参数,`length`属性始终等于`2`。\n\n`length`属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(`overload`)。\n## toString()\n函数的`toString`方法返回函数的源码。\n```\nfunction f() {\n a();\n b();\n c();\n}\n\nf.toString()\n// function f() {\n// a();\n// b();\n// c();\n// }\n```\n函数内部的注释也可以返回。\n```\nfunction f() {/*\n 这是一个\n 多行注释\n*/}\n\nf.toString()\n// \"function f(){/*\n// 这是一个\n// 多行注释\n// */}\"\n```\n利用这一点,可以变相实现多行字符串。\n```\nvar multiline = function (fn) {\n var arr = fn.toString().split('\\n');\n return arr.slice(1, arr.length - 1).join('\\n');\n};\n\nfunction f() {/*\n 这是一个\n 多行注释\n*/}\n\nmultiline(f.toString())\n// \" 这是一个\n// 多行注释\"\n```\n## 函数作用域\n### 定义\n作用域(`scope`)指的是变量存在的范围。Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。\n\n在函数外部声明的变量就是全局变量(`global variable`),它可以在函数内部读取。\n```\nvar v = 1;\n\nfunction f(){\n console.log(v);\n}\n\nf()\n// 1\n```\n上面的代码表明,函数`f`内部可以读取全局变量v。\n\n在函数内部定义的变量,外部无法读取,称为“局部变量”(`local variable`)。\n```\nfunction f(){\n var v = 1;\n}\n\nv // ReferenceError: v is not defined\n```\n上面代码中,变量`v`在函数内部定义,所以是一个局部变量,函数之外就无法读取。\n\n函数内部定义的变量,会在该作用域内覆盖同名全局变量。\n```\nvar v = 1;\n\nfunction f(){\n var v = 2;\n console.log(v);\n}\n\nf() // 2\nv // 1\n```\n上面代码中,变量`v`同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量v覆盖了全局变量`v`。\n\n注意,对于`var`命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。\n```\nif (true) {\n var x = 5;\n}\nconsole.log(x); // 5\n```\n上面代码中,变量`x`在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。\n### 函数内部的变量提升\n与全局作用域一样,函数作用域内部也会产生“变量提升”现象。`var`命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。\n```\nfunction foo(x) {\n if (x > 100) {\n var tmp = x - 100;\n }\n}\n```\n上面的代码等同于\n```\nfunction foo(x) {\n var tmp;\n if (x > 100) {\n tmp = x - 100;\n };\n}\n```\n### 函数本身的作用域\n函数本身也是一个值,也有自己的作用域。它的作用域绑定其声明时所在的作用域。\n```\nvar a = 1;\nvar x = function () {\n console.log(a);\n};\n\nfunction f() {\n var a = 2;\n x();\n}\n\nf() // 1\n```\n上面代码中,函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。\n\n很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。\n```\nvar x = function (){\n console.log(a);\n};\n\nfunction y(f){\n var a = 2;\n f();\n}\n\ny(x)\n// ReferenceError: a is not defined\n```\n上面代码将函数x作为参数,传入函数y。但是,函数x是在函数y体外声明的,作用域绑定外层,因此找不到函数y的内部变量a,导致报错。\n# 参数\n## 概述\n函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。\n```\nfunction square(x) {\n return x * x;\n}\n\nsquare(2) // 4\nsquare(3) // 9\n```\n上式的`x`就是`square`函数的参数。每次运行的时候,需要提供这个值,否则得不到结果。\n## 参数的省略\n函数参数不是必需的,Javascript允许省略参数。\n```\nfunction f(a, b) {\n return a;\n}\n\nf(1, 2, 3) // 1\nf(1) // 1\nf() // undefined\n\nf.length // 2\n```\n上面代码的函数`f`定义了两个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript都不会报错。\n\n被省略的参数的值就变为`undefined`。需要注意的是,函数的`length`属性与实际传入的参数个数无关,只反映函数预期传入的参数个数。\n\n但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入`undefined`。\n```\nfunction f(a, b) {\n return a;\n}\n\nf( , 1) // SyntaxError: Unexpected token ,(…)\nf(undefined, 1) // undefined\n```\n上面代码中,如果省略第一个参数,就会报错。\n## 默认值\n通过下面的方法,可以为函数的参数设置默认值。\n```\nfunction f(a){\n a = a || 1;\n return a;\n}\n\nf('') // 1\nf(0) // 1\n```\n上面代码的`||`表示“或运算”,即如果`a`有值,则返回`a`,否则返回事先设定的默认值(上例为1)。\n\n这种写法会对a进行一次布尔运算,只有为`true`时,才会返回a。可是,除了`undefined`以外,`0`、`空字符`、`null`等的布尔值也是`false`。也就是说,在上面的函数中,不能让`a`等于`0`或`空字符串`,否则在明明有参数的情况下,也会返回默认值。\n\n为了避免这个问题,可以采用下面更精确的写法。\n```\nfunction f(a) {\n (a !== undefined && a !== null) ? a = a : a = 1;\n return a;\n}\n\nf() // 1\nf('') // \"\"\nf(0) // 0\n```\n上面代码中,函数`f`的参数是`空字符`或`0`,都不会触发参数的默认值。\n## 传递方式\n函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。\n```\nvar p = 2;\n\nfunction f(p) {\n p = 3;\n}\nf(p);\n\np // 2\n```\n上面代码中,变量p是一个原始类型的值,传入函数`f`的方式是传值传递。因此,在函数内部,`p`的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。\n\n但是,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。\n```\nvar obj = {p: 1};\n\nfunction f(o) {\n o.p = 2;\n}\nf(obj);\n\nobj.p // 2\n```\n上面代码中,传入函数f的是参数对象`obj`的地址。因此,在函数内部修改`obj`的属性`p`,会影响到原始值。\n\n注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。\n```\nvar obj = [1, 2, 3];\n\nfunction f(o){\n o = [2, 3, 4];\n}\nf(obj);\n\nobj // [1, 2, 3]\n```\n上面代码中,在函数`f`内部,参数对象`obj`被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o)与实际参数`obj`存在一个赋值关系。\n```\n// 函数f内部\no = obj;\n```\n上面代码中,对`o`的修改都会反映在`obj`身上。但是,如果对`o`赋予一个新的值,就等于切断了`o`与`obj`的联系,导致此后的修改都不会影响到`obj`了。\n\n某些情况下,如果需要对某个原始类型的变量,获取传址传递的效果,可以将它写成全局对象的属性。\n```\nvar a = 1;\n\nfunction f(p) {\n window[p] = 2;\n}\nf('a');\n\na // 2\n```\n上面代码中,变量`a`本来是传值传递,但是写成`window`对象的属性,就达到了传址传递的效果。\n## 同名参数\n如果有同名的参数,则取最后出现的那个值。\n```\nfunction f(a, a) {\n console.log(a);\n}\n\nf(1, 2) // 2\n```\n上面的函数`f`有两个参数,且参数名都是`a`。取值的时候,以后面的`a`为准。即使后面的`a`没有值或被省略,也是以其为准。\n```\nfunction f(a, a){\n console.log(a);\n}\n\nf(1) // undefined\n```\n调用函数`f`的时候,没有提供第二个参数,`a`的取值就变成了`undefined`。这时,如果要获得第一个`a`的值,可以使用`arguments`对象。\n```\nfunction f(a, a){\n console.log(arguments[0]);\n}\n\nf(1) // 1\n```\n## arguments对象\n### 定义\n由于JavaScript允许函数有不定数目的参数,所以我们需要一种机制,可以在函数体内部读取所有参数。这就是`arguments`对象的由来。\n\n`arguments`对象包含了函数运行时的所有参数,`arguments[0]`就是第一个参数,`arguments[1]`就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。\n```\nvar f = function(one) {\n console.log(arguments[0]);\n console.log(arguments[1]);\n console.log(arguments[2]);\n}\n\nf(1, 2, 3)\n// 1\n// 2\n// 3\n```\n`arguments`对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)。\n```\nvar f = function(a, b) {\n arguments[0] = 3;\n arguments[1] = 2;\n return a + b;\n}\n\nf(1, 1)\n// 5\n```\n可以通过`arguments`对象的`length`属性,判断函数调用时到底带几个参数。\n```\nfunction f() {\n return arguments.length;\n}\n\nf(1, 2, 3) // 3\nf(1) // 1\nf() // 0\n```\n### 与数组的关系\n需要注意的是,虽然`arguments`很像数组,但它是一个对象。数组专有的方法(比如`slice`和`forEach`),不能在`arguments`对象上直接使用。\n\n但是,可以通过`apply`方法,把`arguments`作为参数传进去,这样就可以让`arguments`使用数组方法了。\n```\n// 用于apply方法\nmyfunction.apply(obj, arguments).\n\n// 使用与另一个数组合并\nArray.prototype.concat.apply([1,2,3], arguments)\n```\n要让`arguments`对象使用数组方法,真正的解决方法是将`arguments`转为真正的数组。下面是两种常用的转换方法:`slice`方法和逐一填入新数组。\n```\nvar args = Array.prototype.slice.call(arguments);\n\n// or\n\nvar args = [];\nfor (var i = 0; i < arguments.length; i++) {\n args.push(arguments[i]);\n}\n```\n### callee属性\n`arguments`对象带有一个`callee`属性,返回它所对应的原函数。\n```\nvar f = function(one) {\n console.log(arguments.callee === f);\n}\n\nf() // true\n```\n可以通过`arguments.callee`,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。\n# 函数的其他知识点\n## 闭包\n闭包(`closure`)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。\n\n要理解闭包,首先必须理解变量作用域。前面提到,JavaScript有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。\n```\nvar n = 999;\n\nfunction f1() {\n console.log(n);\n}\nf1() // 999\n```\n上面代码中,函数`f1`可以读取全局变量`n`。\n\n但是,在函数外部无法读取函数内部声明的变量。\n```\nfunction f1() {\n var n = 999;\n}\n\nconsole.log(n)\n// Uncaught ReferenceError: n is not defined(\n```\n上面代码中,函数`f1`内部声明的变量`n`,函数外是无法读取的。\n\n如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。\n```\nfunction f1() {\n var n = 999;\n function f2() {\n console.log(n); // 999\n }\n}\n```\n上面代码中,函数`f2`就在函数`f1`内部,这时`f1`内部的所有局部变量,对`f2`都是可见的。但是反过来就不行,`f2`内部的局部变量,对f1就是不可见的。这就是JavaScript语言特有的”链式作用域”结构(`chain scope`),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。\n\n既然`f2`可以读取`f1`的局部变量,那么只要把`f2`作为返回值,我们不就可以在`f1`外部读取它的内部变量了吗!\n```\nfunction f1() {\n var n = 999;\n function f2() {\n console.log(n);\n }\n return f2;\n}\n\nvar result = f1();\nresult(); // 999\n```\n上面代码中,函数`f1`的返回值就是函数`f2`,由于`f2`可以读取`f1`的内部变量,所以就可以在外部获得`f1`的内部变量了。\n\n闭包就是函数`f2`,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如`f2`记住了它诞生的环境`f1`,所以从`f2`可以得到`f1`的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。\n\n闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。\n```\nfunction createIncrementor(start) {\n return function () {\n return start++;\n };\n}\n\nvar inc = createIncrementor(5);\n\ninc() // 5\ninc() // 6\ninc() // 7\n```\n上面代码中,`start`是函数`createIncrementor`的内部变量。通过闭包,`start`的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包`inc`使得函数`createIncrementor`的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。\n\n为什么会这样呢?原因就在于`inc`始终在内存中,而inc的存在依赖于`createIncrementor`,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。\n\n闭包的另一个用处,是封装对象的私有属性和私有方法。\n```\nfunction Person(name) {\n var _age;\n function setAge(n) {\n _age = n;\n }\n function getAge() {\n return _age;\n }\n\n return {\n name: name,\n getAge: getAge,\n setAge: setAge\n };\n}\n\nvar p1 = person('张三');\np1.setAge(25);\np1.getAge() // 25\n```\n上面代码中,函数`Person`的内部变量`_age`,通过闭包`getAge`和`setAge`,变成了返回对象`p1`的私有变量。\n\n注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。\n## 立即调用的函数表达式(IIFE)\n在Javascript中,一对圆括号`()`是一种运算符,跟在函数名之后,表示调用该函数。比如,`print()`就表示调用`print`函数。\n\n有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。\n```\nfunction(){ /* code */ }();\n// SyntaxError: Unexpected token (\n```\n产生这个错误的原因是,`function`这个关键字即可以当作语句,也可以当作表达式。\n```\n// 语句\nfunction f() {}\n\n// 表达式\nvar f = function f() {}\n```\n为了避免解析上的歧义,JavaScript引擎规定,如果`function`关键字出现在行首,一律解释成语句。因此,JavaScript引擎看到行首是`function`关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。\n\n解决方法就是不要让`function`出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。\n```\n(function(){ /* code */ }());\n// 或者\n(function(){ /* code */ })();\n```\n上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称IIFE。\n\n注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个IIFE,可能就会报错。\n```\n// 报错\n(function(){ /* code */ }())\n(function(){ /* code */ }())\n```\n上面代码的两行之间没有分号,JavaScript会将它们连在一起解释,将第二行解释为第一行的参数。\n\n推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。\n```\nvar i = function(){ return 10; }();\ntrue && function(){ /* code */ }();\n0, function(){ /* code */ }();\n```\n甚至像下面这样写,也是可以的。\n```\n!function(){ /* code */ }();\n~function(){ /* code */ }();\n-function(){ /* code */ }();\n+function(){ /* code */ }();\n```\n`new`关键字也能达到这个效果。\n```\nnew function(){ /* code */ }\n\nnew function(){ /* code */ }()\n// 只有传递参数时,才需要最后那个圆括号\n```\n通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。\n```\n// 写法一\nvar tmp = newData;\nprocessData(tmp);\nstoreData(tmp);\n\n// 写法二\n(function (){\n var tmp = newData;\n processData(tmp);\n storeData(tmp);\n}());\n```\n上面代码中,写法二比写法一更好,因为完全避免了污染全局变量。\n# eval 命令\n`eval`命令的作用是,将字符串当作语句执行。\n```\neval('var a = 1;');\na // 1\n```\n上面代码将字符串当作语句运行,生成了变量`a`。\n\n放在`eval`中的字符串,应该有独自存在的意义,不能用来与`eval`以外的命令配合使用。举例来说,下面的代码将会报错。\n```\neval('return;');\n```\n由于`eval`没有自己的作用域,都在当前作用域内执行,因此可能会修改其他外部变量的值,造成安全问题。\n```\nvar a = 1;\neval('a = 2');\n\na // 2\n```\n上面代码中,`eval`命令修改了外部变量`a`的值。由于这个原因,所以`eval`有安全风险,如果无法做到作用域隔离,最好不要使用。此外,`eval`的命令字符串不会得到JavaScript引擎的优化,运行速度较慢,也是另一个不应该使用它的理由。通常情况下,`eval`最常见的场合是解析`JSON`数据字符串,正确的做法是这时应该使用浏览器提供的`JSON.parse`方法。\n\nECMAScript 5将`eval`的使用分成两种情况,像上面这样的调用,就叫做“直接使用”,这种情况下`eval`的作用域就是当前作用域(即全局作用域或函数作用域)。另一种情况是,`eval`不是直接调用,而是“间接调用”,此时`eval`的作用域总是全局作用域。\n```\nvar a = 1;\n\nfunction f(){\n var a = 2;\n var e = eval;\n e('console.log(a)');\n}\n\nf() // 1\n```\n上面代码中,`eval`是间接调用,所以即使它是在函数中,它的作用域还是全局作用域,因此输出的`a`为全局变量。\n\n`eval`的间接调用的形式五花八门,只要不是直接调用,几乎都属于间接调用。\n```\neval.call(null, '...')\nwindow.eval('...')\n(1, eval)('...')\n(eval, eval)('...')\n(1 ? eval : 0)('...')\n(__ = eval)('...')\nvar e = eval; e('...')\n(function(e) { e('...') })(eval)\n(function(e) { return e })(eval)('...')\n(function() { arguments[0]('...') })(eval)\nthis.eval('...')\nthis['eval']('...')\n[eval][0]('...')\neval.call(this, '...')\neval('eval')('...')\n```\n上面这些形式都是`eval`的间接调用,因此它们的作用域都是全局作用域。\n\n# new Function()\n\n与`eval`作用类似的还有`Function`构造函数。利用它生成一个函数,然后调用该函数,也能将字符串当作命令执行。\n```\nvar jsonp = 'foo({\"id\":42})';\n\nvar f = new Function( \"foo\", jsonp );\n// 相当于定义了如下函数\n// function f(foo) {\n// foo({\"id\":42});\n// }\n\nf(function(json){\n console.log( json.id ); // 42\n})\n```\n`new Function生成的function始终存在于全局作用域中。`\n```\nvar a = 123;\n(function() {\n\tvar a = 1;\n\tfunction f() {\n\t\tvar a = 2;\n\t\tvar bc = new Function('console.log(a)');\n\t\tbc();\n\t}\n\tf();//输出 123,证明 new Function生成的function始终存在于全局作用域中\n}());\n```\n\n上面代码中,`jsonp`是一个字符串,`Function`构造函数将这个字符串,变成了函数体。调用该函数的时候,`jsonp`就会执行。这种写法的实质是将代码放到函数作用域执行,避免对全局作用域造成影响。\n转自: [阮一峰教程](http://javascript.ruanyifeng.com/grammar/function.html#toc15)","slug":"function","published":1,"updated":"2016-07-12T14:04:11.457Z","layout":"post","photos":[],"link":"","_id":"citf9om99001nskv7tortwzm5"},{"title":"error","date":"2016-07-02T09:12:35.000Z","comments":1,"_content":"# Error对象\nJavaScript 原生提供`Error`对象,所有抛出的错误都是这个对象的实例。\n<!--more-->\n```\nvar err = new Error('出错了');\n```\n当代码解析或运行时发生错误,JavaScript引擎就会自动产生并抛出一个`Error`对象的实例,然后整个程序就中断在发生错误的地方。\n\n根据语言标准,`Error`对象的实例必须有`message`属性,表示出错时的提示信息,其他属性则没有提及。大多数JavaScript引擎,对`Error`实例还提供`name`和`stack`属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。\n1. message:错误提示信息\n2. name:错误名称(非标准属性)\n3. stack:错误的堆栈(非标准属性)\n利用`name`和`message`这两个属性,可以对发生什么错误有一个大概的了解。\n```\nif (error.name){\n console.log(error.name + \": \" + error.message);\n}\n```\n上面代码表示,显示错误的名称以及出错提示信息。\n\n`stack`属性用来查看错误发生时的堆栈。\n```\nfunction throwit() {\n throw new Error('');\n}\n\nfunction catchit() {\n try {\n throwit();\n } catch(e) {\n console.log(e.stack); // print stack trace\n }\n}\n\ncatchit()\n// Error\n// at throwit (~/examples/throwcatch.js:9:11)\n// at catchit (~/examples/throwcatch.js:3:9)\n// at repl:1:5\n```\n上面代码显示,抛出错误首先是在`throwit`函数,然后是在`catchit`函数,最后是在函数的运行环境中。\n# JavaScript的原生错误类型\n`Error`对象是最一般的错误类型,在它的基础上,JavaScript还定义了其他6种错误,也就是说,存在`Error`的6个派生对象。\n## SyntaxError\n`SyntaxError`是解析代码时发生的语法错误。\n```\n// 变量名错误\nvar 1a;\n\n// 缺少括号\nconsole.log 'hello');\n```\n## ReferenceError\n`ReferenceError`是引用一个不存在的变量时发生的错误。\n```\nunknownVariable\n// ReferenceError: unknownVariable is not defined\n```\n另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果或者`this`赋值。\n```\nconsole.log() = 1\n// ReferenceError: Invalid left-hand side in assignment\n\nthis = 1\n// ReferenceError: Invalid left-hand side in assignment\n```\n上面代码对函数`console.log`的运行结果和`this`赋值,结果都引发了`ReferenceError`错误。\n## RangeError\n`RangeError`是当一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是`Number`对象的方法参数超出范围,以及函数堆栈超过最大值。\n```\nnew Array(-1)\n// RangeError: Invalid array length\n\n(1234).toExponential(21)\n// RangeError: toExponential() argument must be between 0 and 20 \n```\n## TypeError\n`TypeError`是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等原始类型的值使用`new`命令,就会抛出这种错误,因为`new`命令的参数应该是一个构造函数。\n```\nnew 123\n//TypeError: number is not a func\n\nvar obj = {};\nobj.unknownMethod()\n// TypeError: undefined is not a function \n```\n上面代码的第二种情况,调用对象不存在的方法,会抛出`TypeError`错误。\n## URIError\n`URIError`是URI相关函数的参数不正确时抛出的错误,主要涉及`encodeURI()`、`decodeURI()`、`encodeURIComponent()`、`decodeURIComponent()`、`escape()`和`unescape()`这六个函数。\n```\ndecodeURI('%2')\n// URIError: URI malformed\n```\n## EvalError\n`eval`函数没有被正确执行时,会抛出`EvalError`误。该错误类型已经不再在`ES5`中出现了,只是为了保证与以前代码兼容,才继续保留。\n\n以上这6种派生错误,连同原始的`Error`对象,都是构造函数。开发者可以使用它们,人为生成错误对象的实例。\n```\nnew Error('出错了!');\nnew RangeError('出错了,变量超出有效范围!');\nnew TypeError('出错了,变量类型无效!');\n```\n上面代码新建错误对象的实例,实质就是手动抛出错误。可以看到,错误对象的构造函数接受一个参数,代表错误提示信息(message)。\n# 自定义错误\n除了JavaScript内建的7种错误对象,还可以定义自己的错误对象。\n```\nfunction UserError(message) {\n this.message = message || \"默认信息\";\n this.name = \"UserError\";\n}\n\nUserError.prototype = new Error();\nUserError.prototype.constructor = UserError;\n```\n上面代码自定义一个错误对象`UserError`,让它继承`Error`对象。然后,就可以生成这种自定义的错误了。\n```\nnew UserError(\"这是自定义的错误!\");\n```\n# throw语句\n`throw`语句的作用是中断程序执行,抛出一个意外或错误。它接受一个表达式作为参数,可以抛出各种值。\n```\n// 抛出一个字符串\nthrow \"Error!\";\n\n// 抛出一个数值\nthrow 42;\n\n// 抛出一个布尔值\nthrow true;\n\n// 抛出一个对象\nthrow {toString: function() { return \"Error!\"; } };\n```\n上面代码表示,`throw`可以接受各种值作为参数。JavaScript引擎一旦遇到`throw`语句,就会停止执行后面的语句,并将`throw`语句的参数值,返回给用户。\n\n如果只是简单的错误,返回一条出错信息就可以了,但是如果遇到复杂的情况,就需要在出错以后进一步处理。这时最好的做法是使用`throw`语句手动抛出一个`Error`对象。\n```\nthrow new Error('出错了!');\n```\n上面语句新建一个`Error`对象,然后将这个对象抛出,整个程序就会中断在这个地方。\n\n`throw`语句还可以抛出用户自定义的错误。\n```\nfunction UserError(message) {\n this.message = message || \"默认信息\";\n this.name = \"UserError\";\n}\n\nUserError.prototype.toString = function (){\n return this.name + ': \"' + this.message + '\"';\n}\n\nthrow new UserError(\"出错了!\");\n```\n可以通过自定义一个`assert`函数,规范化`throw`抛出的信息。\n```\nfunction assert(expression, message) {\n if (!expression)\n throw {name: 'Assertion Exception', message: message};\n}\n```\n上面代码定义了一个`assert`函数,它接受一个表达式和一个字符串作为参数。一旦表达式不为真,就抛出指定的字符串。它的用法如下。\n```\nassert(typeof myVar != 'undefined', 'myVar is undefined!');\n```\n`console`对象的`assert`方法,与上面函数的工作机制一模一样,所以可以直接使用。\n```\nconsole.assert(typeof myVar != 'undefined', 'myVar is undefined!');\n```\n# try…catch结构\n为了对错误进行处理,需要使用`try...catch`结构。\n```\ntry {\n throw new Error('出错了!');\n} catch (e) {\n console.log(e.name + \": \" + e.message);\n console.log(e.stack);\n}\n// Error: 出错了!\n// at <anonymous>:3:9\n// ...\n```\n上面代码中,`try`代码块一抛出错误(上例用的是`throw`语句),JavaScript引擎就立即把代码的执行,转到`catch`代码块。可以看作,错误可以被`catch`代码块捕获。`catch`接受一个参数,表示`try`代码块抛出的值。\n```\nfunction throwIt(exception) {\n try {\n throw exception;\n } catch (e) {\n console.log('Caught: '+ e);\n }\n}\n\nthrowIt(3);\n// Caught: 3\nthrowIt('hello');\n// Caught: hello\nthrowIt(new Error('An error happened'));\n// Caught: Error: An error happened\n```\n上面代码中,`throw`语句先后抛出数值、字符串和错误对象。\n\n`catch`代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。\n```\ntry {\n throw \"出错了\";\n} catch (e) {\n console.log(111);\n}\nconsole.log(222);\n// 111\n// 222\n```\n上面代码中,`try`代码块抛出的错误,被`catch`代码块捕获后,程序会继续向下执行。\n\n`catch`代码块之中,还可以再抛出错误,甚至使用嵌套的`try...catch`结构。\n```\nvar n = 100;\n\ntry {\n throw n;\n} catch (e) {\n if (e <= 50) {\n // ...\n } else {\n throw e;\n }\n}\n```\n上面代码中,`catch`代码之中又抛出了一个错误。\n\n为了捕捉不同类型的错误,`catch`代码块之中可以加入判断语句。\n```\ntry {\n foo.bar();\n} catch (e) {\n if (e instanceof EvalError) {\n console.log(e.name + \": \" + e.message);\n } else if (e instanceof RangeError) {\n console.log(e.name + \": \" + e.message);\n }\n // ...\n}\n```\n上面代码中,`catch`捕获错误之后,会判断错误类型(`EvalError`还是`RangeError`),进行不同的处理。\n\n`try...catch`结构是JavaScript语言受到Java语言影响的一个明显的例子。这种结构多多少少是对结构化编程原则一种破坏,处理不当就会变成类似goto语句的效果,应该谨慎使用。\n# finally代码块\n`try...catch`结构允许在最后添加一个`finally`代码块,表示不管是否出现错误,都必需在最后运行的语句。\n```\nfunction cleansUp() {\n try {\n throw new Error('出错了……');\n console.log('此行不会执行');\n } finally {\n console.log('完成清理工作');\n }\n}\n\ncleansUp()\n// 完成清理工作\n// Error: 出错了……\n```\n上面代码中,由于没有`catch`语句块,所以错误没有捕获。执行`finally`代码块以后,程序就中断在错误抛出的地方。\n```\nfunction idle(x) {\n try {\n console.log(x);\n return 'result';\n } finally {\n console.log(\"FINALLY\");\n }\n}\n\nidle('hello')\n// hello\n// FINALLY\n// \"result\"\n```\n上面代码说明,即使有`return`语句在前,`finally`代码块依然会得到执行,且在其执行完毕后,才会显示`return`语句的值。\n\n下面的例子说明,`return`语句的执行是排在`finally`代码之前,只是等`finally`代码执行完毕后才返回。\n```\nvar count = 0;\nfunction countUp() {\n try {\n return count;\n } finally {\n count++;\n }\n}\n\ncountUp()\n// 0\ncount\n// 1\n```\n上面代码说明,`return`语句的`count`的值,是在`finally`代码块运行之前,就获取完成了。\n\n下面是`finally`代码块用法的典型场景。\n```\nopenFile();\n\ntry {\n writeFile(Data);\n} catch(e) {\n handleError(e);\n} finally {\n closeFile();\n}\n```\n上面代码首先打开一个文件,然后在`try`代码块中写入文件,如果没有发生错误,则运行`finally`代码块关闭文件;一旦发生错误,则先使用`catch`代码块处理错误,再使用`finally`代码块关闭文件。\n\n下面的例子充分反应了`try...catch...finally`这三者之间的执行顺序。\n```\nfunction f() {\n try {\n console.log(0);\n throw \"bug\";\n } catch(e) {\n console.log(1);\n return true; // 这句原本会延迟到finally代码块结束再执行\n console.log(2); // 不会运行\n } finally {\n console.log(3);\n return false; // 这句会覆盖掉catch中的那句return\n console.log(4); // 不会运行\n }\n\n console.log(5); // 不会运行\n}\n\nvar result = f();\n// 0\n// 1\n// 3\n\nresult\n// false\n```\n上面代码中,`catch`代码块结束执行之前,会先执行`finally`代码块。从`catch`转入`finally`的标志,不仅有`return`语句,还有`throw`语句。\n```\nfunction f() {\n try {\n throw '出错了!';\n } catch(e) {\n console.log('捕捉到内部错误');\n throw e; // 这句原本会等到finally结束再执行,但是由于finally中有return,直接return了,就没有执行到\n } finally {\n return false; // 直接返回\n }\n}\n\ntry {\n f();\n} catch(e) {\n // 此处不会执行\n console.log('caught outer \"bogus\"');\n}\n\n// 捕捉到内部错误\n```\n上面代码中,进入`catch`代码块之后,一遇到`throw`语句,就会去执行`finally`代码块,其中有`return false`语句,因此就直接返回了,不再会回去执行`catch`代码块剩下的部分了。\n\n转自:[阮一峰教程](http://javascript.ruanyifeng.com/grammar/error.html)","source":"_posts/error-2016-07-02.md","raw":"title: error\ndate: 2016-07-02 17:12:35\ntags:\n- jQuery\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# Error对象\nJavaScript 原生提供`Error`对象,所有抛出的错误都是这个对象的实例。\n<!--more-->\n```\nvar err = new Error('出错了');\n```\n当代码解析或运行时发生错误,JavaScript引擎就会自动产生并抛出一个`Error`对象的实例,然后整个程序就中断在发生错误的地方。\n\n根据语言标准,`Error`对象的实例必须有`message`属性,表示出错时的提示信息,其他属性则没有提及。大多数JavaScript引擎,对`Error`实例还提供`name`和`stack`属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。\n1. message:错误提示信息\n2. name:错误名称(非标准属性)\n3. stack:错误的堆栈(非标准属性)\n利用`name`和`message`这两个属性,可以对发生什么错误有一个大概的了解。\n```\nif (error.name){\n console.log(error.name + \": \" + error.message);\n}\n```\n上面代码表示,显示错误的名称以及出错提示信息。\n\n`stack`属性用来查看错误发生时的堆栈。\n```\nfunction throwit() {\n throw new Error('');\n}\n\nfunction catchit() {\n try {\n throwit();\n } catch(e) {\n console.log(e.stack); // print stack trace\n }\n}\n\ncatchit()\n// Error\n// at throwit (~/examples/throwcatch.js:9:11)\n// at catchit (~/examples/throwcatch.js:3:9)\n// at repl:1:5\n```\n上面代码显示,抛出错误首先是在`throwit`函数,然后是在`catchit`函数,最后是在函数的运行环境中。\n# JavaScript的原生错误类型\n`Error`对象是最一般的错误类型,在它的基础上,JavaScript还定义了其他6种错误,也就是说,存在`Error`的6个派生对象。\n## SyntaxError\n`SyntaxError`是解析代码时发生的语法错误。\n```\n// 变量名错误\nvar 1a;\n\n// 缺少括号\nconsole.log 'hello');\n```\n## ReferenceError\n`ReferenceError`是引用一个不存在的变量时发生的错误。\n```\nunknownVariable\n// ReferenceError: unknownVariable is not defined\n```\n另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果或者`this`赋值。\n```\nconsole.log() = 1\n// ReferenceError: Invalid left-hand side in assignment\n\nthis = 1\n// ReferenceError: Invalid left-hand side in assignment\n```\n上面代码对函数`console.log`的运行结果和`this`赋值,结果都引发了`ReferenceError`错误。\n## RangeError\n`RangeError`是当一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是`Number`对象的方法参数超出范围,以及函数堆栈超过最大值。\n```\nnew Array(-1)\n// RangeError: Invalid array length\n\n(1234).toExponential(21)\n// RangeError: toExponential() argument must be between 0 and 20 \n```\n## TypeError\n`TypeError`是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等原始类型的值使用`new`命令,就会抛出这种错误,因为`new`命令的参数应该是一个构造函数。\n```\nnew 123\n//TypeError: number is not a func\n\nvar obj = {};\nobj.unknownMethod()\n// TypeError: undefined is not a function \n```\n上面代码的第二种情况,调用对象不存在的方法,会抛出`TypeError`错误。\n## URIError\n`URIError`是URI相关函数的参数不正确时抛出的错误,主要涉及`encodeURI()`、`decodeURI()`、`encodeURIComponent()`、`decodeURIComponent()`、`escape()`和`unescape()`这六个函数。\n```\ndecodeURI('%2')\n// URIError: URI malformed\n```\n## EvalError\n`eval`函数没有被正确执行时,会抛出`EvalError`误。该错误类型已经不再在`ES5`中出现了,只是为了保证与以前代码兼容,才继续保留。\n\n以上这6种派生错误,连同原始的`Error`对象,都是构造函数。开发者可以使用它们,人为生成错误对象的实例。\n```\nnew Error('出错了!');\nnew RangeError('出错了,变量超出有效范围!');\nnew TypeError('出错了,变量类型无效!');\n```\n上面代码新建错误对象的实例,实质就是手动抛出错误。可以看到,错误对象的构造函数接受一个参数,代表错误提示信息(message)。\n# 自定义错误\n除了JavaScript内建的7种错误对象,还可以定义自己的错误对象。\n```\nfunction UserError(message) {\n this.message = message || \"默认信息\";\n this.name = \"UserError\";\n}\n\nUserError.prototype = new Error();\nUserError.prototype.constructor = UserError;\n```\n上面代码自定义一个错误对象`UserError`,让它继承`Error`对象。然后,就可以生成这种自定义的错误了。\n```\nnew UserError(\"这是自定义的错误!\");\n```\n# throw语句\n`throw`语句的作用是中断程序执行,抛出一个意外或错误。它接受一个表达式作为参数,可以抛出各种值。\n```\n// 抛出一个字符串\nthrow \"Error!\";\n\n// 抛出一个数值\nthrow 42;\n\n// 抛出一个布尔值\nthrow true;\n\n// 抛出一个对象\nthrow {toString: function() { return \"Error!\"; } };\n```\n上面代码表示,`throw`可以接受各种值作为参数。JavaScript引擎一旦遇到`throw`语句,就会停止执行后面的语句,并将`throw`语句的参数值,返回给用户。\n\n如果只是简单的错误,返回一条出错信息就可以了,但是如果遇到复杂的情况,就需要在出错以后进一步处理。这时最好的做法是使用`throw`语句手动抛出一个`Error`对象。\n```\nthrow new Error('出错了!');\n```\n上面语句新建一个`Error`对象,然后将这个对象抛出,整个程序就会中断在这个地方。\n\n`throw`语句还可以抛出用户自定义的错误。\n```\nfunction UserError(message) {\n this.message = message || \"默认信息\";\n this.name = \"UserError\";\n}\n\nUserError.prototype.toString = function (){\n return this.name + ': \"' + this.message + '\"';\n}\n\nthrow new UserError(\"出错了!\");\n```\n可以通过自定义一个`assert`函数,规范化`throw`抛出的信息。\n```\nfunction assert(expression, message) {\n if (!expression)\n throw {name: 'Assertion Exception', message: message};\n}\n```\n上面代码定义了一个`assert`函数,它接受一个表达式和一个字符串作为参数。一旦表达式不为真,就抛出指定的字符串。它的用法如下。\n```\nassert(typeof myVar != 'undefined', 'myVar is undefined!');\n```\n`console`对象的`assert`方法,与上面函数的工作机制一模一样,所以可以直接使用。\n```\nconsole.assert(typeof myVar != 'undefined', 'myVar is undefined!');\n```\n# try…catch结构\n为了对错误进行处理,需要使用`try...catch`结构。\n```\ntry {\n throw new Error('出错了!');\n} catch (e) {\n console.log(e.name + \": \" + e.message);\n console.log(e.stack);\n}\n// Error: 出错了!\n// at <anonymous>:3:9\n// ...\n```\n上面代码中,`try`代码块一抛出错误(上例用的是`throw`语句),JavaScript引擎就立即把代码的执行,转到`catch`代码块。可以看作,错误可以被`catch`代码块捕获。`catch`接受一个参数,表示`try`代码块抛出的值。\n```\nfunction throwIt(exception) {\n try {\n throw exception;\n } catch (e) {\n console.log('Caught: '+ e);\n }\n}\n\nthrowIt(3);\n// Caught: 3\nthrowIt('hello');\n// Caught: hello\nthrowIt(new Error('An error happened'));\n// Caught: Error: An error happened\n```\n上面代码中,`throw`语句先后抛出数值、字符串和错误对象。\n\n`catch`代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。\n```\ntry {\n throw \"出错了\";\n} catch (e) {\n console.log(111);\n}\nconsole.log(222);\n// 111\n// 222\n```\n上面代码中,`try`代码块抛出的错误,被`catch`代码块捕获后,程序会继续向下执行。\n\n`catch`代码块之中,还可以再抛出错误,甚至使用嵌套的`try...catch`结构。\n```\nvar n = 100;\n\ntry {\n throw n;\n} catch (e) {\n if (e <= 50) {\n // ...\n } else {\n throw e;\n }\n}\n```\n上面代码中,`catch`代码之中又抛出了一个错误。\n\n为了捕捉不同类型的错误,`catch`代码块之中可以加入判断语句。\n```\ntry {\n foo.bar();\n} catch (e) {\n if (e instanceof EvalError) {\n console.log(e.name + \": \" + e.message);\n } else if (e instanceof RangeError) {\n console.log(e.name + \": \" + e.message);\n }\n // ...\n}\n```\n上面代码中,`catch`捕获错误之后,会判断错误类型(`EvalError`还是`RangeError`),进行不同的处理。\n\n`try...catch`结构是JavaScript语言受到Java语言影响的一个明显的例子。这种结构多多少少是对结构化编程原则一种破坏,处理不当就会变成类似goto语句的效果,应该谨慎使用。\n# finally代码块\n`try...catch`结构允许在最后添加一个`finally`代码块,表示不管是否出现错误,都必需在最后运行的语句。\n```\nfunction cleansUp() {\n try {\n throw new Error('出错了……');\n console.log('此行不会执行');\n } finally {\n console.log('完成清理工作');\n }\n}\n\ncleansUp()\n// 完成清理工作\n// Error: 出错了……\n```\n上面代码中,由于没有`catch`语句块,所以错误没有捕获。执行`finally`代码块以后,程序就中断在错误抛出的地方。\n```\nfunction idle(x) {\n try {\n console.log(x);\n return 'result';\n } finally {\n console.log(\"FINALLY\");\n }\n}\n\nidle('hello')\n// hello\n// FINALLY\n// \"result\"\n```\n上面代码说明,即使有`return`语句在前,`finally`代码块依然会得到执行,且在其执行完毕后,才会显示`return`语句的值。\n\n下面的例子说明,`return`语句的执行是排在`finally`代码之前,只是等`finally`代码执行完毕后才返回。\n```\nvar count = 0;\nfunction countUp() {\n try {\n return count;\n } finally {\n count++;\n }\n}\n\ncountUp()\n// 0\ncount\n// 1\n```\n上面代码说明,`return`语句的`count`的值,是在`finally`代码块运行之前,就获取完成了。\n\n下面是`finally`代码块用法的典型场景。\n```\nopenFile();\n\ntry {\n writeFile(Data);\n} catch(e) {\n handleError(e);\n} finally {\n closeFile();\n}\n```\n上面代码首先打开一个文件,然后在`try`代码块中写入文件,如果没有发生错误,则运行`finally`代码块关闭文件;一旦发生错误,则先使用`catch`代码块处理错误,再使用`finally`代码块关闭文件。\n\n下面的例子充分反应了`try...catch...finally`这三者之间的执行顺序。\n```\nfunction f() {\n try {\n console.log(0);\n throw \"bug\";\n } catch(e) {\n console.log(1);\n return true; // 这句原本会延迟到finally代码块结束再执行\n console.log(2); // 不会运行\n } finally {\n console.log(3);\n return false; // 这句会覆盖掉catch中的那句return\n console.log(4); // 不会运行\n }\n\n console.log(5); // 不会运行\n}\n\nvar result = f();\n// 0\n// 1\n// 3\n\nresult\n// false\n```\n上面代码中,`catch`代码块结束执行之前,会先执行`finally`代码块。从`catch`转入`finally`的标志,不仅有`return`语句,还有`throw`语句。\n```\nfunction f() {\n try {\n throw '出错了!';\n } catch(e) {\n console.log('捕捉到内部错误');\n throw e; // 这句原本会等到finally结束再执行,但是由于finally中有return,直接return了,就没有执行到\n } finally {\n return false; // 直接返回\n }\n}\n\ntry {\n f();\n} catch(e) {\n // 此处不会执行\n console.log('caught outer \"bogus\"');\n}\n\n// 捕捉到内部错误\n```\n上面代码中,进入`catch`代码块之后,一遇到`throw`语句,就会去执行`finally`代码块,其中有`return false`语句,因此就直接返回了,不再会回去执行`catch`代码块剩下的部分了。\n\n转自:[阮一峰教程](http://javascript.ruanyifeng.com/grammar/error.html)","slug":"error","published":1,"updated":"2016-07-04T01:43:43.409Z","layout":"post","photos":[],"link":"","_id":"citf9om9g001qskv7gmf56r5p"},{"title":"JavaScript模块管理器","date":"2016-07-10T07:47:41.000Z","comments":1,"_content":"# 模块化\n时光回溯到2009年,CommonJS规范和NodeJS都还在襁褓之中,离Bower诞生还有三年时间,Ruby还统治着github,CoffeeScript在年末提交了第一个commit……\n备受加载顺序,依赖关系折磨的前端开发,开始站起来试图解决日益复杂的前端开发的种种问题,RequireJS降临了。如果说NodeJS吹响了JS全栈革命的号角,那么同时发生的前端模块化革命便是RequireJS的历史使命。\n五年过去了,RequireJS战胜了同级生LabJS,带起了中国小伙伴SeaJS。他完美地引领了前端模块化的革命,但今天看来,它有些过时了:它重浏览器端,轻打包编译,没有及时跟进包管理体系,almond没有成为标配而只是周边,配置晦涩……诞生太早的RequireJS,虽然一度成为了前端模块化的某种程度上的事实标准,但难掩其缺点。\n为了解决这个问题,前端的模块管理器(package management)应运而生,五年间,NodeJS成为了服务端以及脚本工具的一代翘楚,NPM的成功让大家意识到一个集中式的依赖/包管理体系的重要性,Bower应运而生,还有试图将CMD和NPM包带到前端领域,统一前后端包格式的Browserify等等,大量的前端工具爆发式地出现,WebPack是其中的(又)一款模块打包工具。\n\n<!--more-->\nrequire.js一类的模块管理器的问题在于各种参数设置过于繁琐,不容易学习,很难完全掌握。而且,实际应用中,往往还需要在服务器端,将所有模块合并后,再统一加载,这多出了很多工作量。\n* seajs / require : 是一种在线\"编译\" 模块的方案,相当于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module 这些东西。也就实现了模块化。\n* browserify / webpack / Duo : 是一个预编译模块的方案,相比于上面 ,这个方案更加智能。没用过browserify,这里以webpack为例。首先,它是预编译的,不需要在浏览器中加载解释器。另外,你在本地直接写JS,不管是 AMD / CMD / ES6 风格的模块化,它都能认识,并且编译成浏览器认识的JS。\n## Bower\n[Bower](https://bower.io/)的主要作用是,为模块的安装、升级和删除,提供一种统一的、可维护的管理模式。\nBower是一个客户端技术的软件包管理器,它可用于搜索、安装和卸载如JavaScript、HTML、CSS之类的网络资源。其他一些建立在Bower基础之上的开发工具,如YeoMan和Grunt,这个会在以后的文章中介绍。\n首先,安装Bower。\n```\nnpm install -g bower\n```\n然后,使用`bower install`命令安装各种模块。下面是一些例子。\n```\n# 模块的名称\n$ bower install jquery\n# github用户名/项目名\n$ bower install jquery/jquery\n# git代码仓库地址\n$ bower install git://github.com/user/package.git\n# 模块网址\n$ bower install http://example.com/script.js\n```\n所谓\"安装\",就是将该模块(以及其依赖的模块)下载到当前目录的bower_components子目录中。下载后,就可以直接插入网页。\n```\n<script src=\"/bower_componets/jquery/dist/jquery.min.js\">\n```\n`bower update`命令用于更新模块。如果不给出模块的名称,则更新所有模块。\n```\nbower update jquery\n```\n`bower uninstall`命令用于卸载模块。\n```\nbower uninstall jquery\n```\n注意,默认情况下,会连所依赖的模块一起卸载。比如,如果卸载jquery-ui,会连jquery一起卸载,除非还有别的模块依赖jquery。\n\n(bower简明入门教程)[https://segmentfault.com/a/1190000000349555]\n\n## Duo\n(Duo)[http://duojs.org/]是在Component的基础上开发的,语法和配置文件基本通用,并且借鉴了Browserify和Go语言的一些特点,相当地强大和好用。\n首先,安装Duo。\n```\nnpm install -g duo\n```\n然后,编写一个本地文件index.js。\n```\nvar uid = require('matthewmueller/uid');\nvar fmt = require('yields/fmt');\n \nvar msg = fmt('Your unique ID is %s!', uid());\nwindow.alert(msg);\n```\n上面代码加载了uid和fmt两个模块,采用Component的\"github用户名/项目名\"格式。\n接着,编译最终的脚本文件。\n```\nduo index.js > build.js\n```\n编译后的文件可以直接插入网页。\n```\n<script src=\"build.js\"></script>\n```\nDuo不仅可以编译JavaScript,还可以编译CSS。\n```\n@import 'necolas/normalize.css';\n@import './layout/layout.css';\n \nbody {\n color: teal;\n background: url('./background-image.jpg');\n}\n```\n编译时,Duo自动将normalize.css和layout.css,与当前样式表合并成同一个文件。\n```\nduo index.css > build.css\n```\n编译后,插入网页即可。\n```\n<link rel=\"stylesheet\" href=\"build.css\">\n```\n[Duo js 一个非常酷的前端打包工具](http://www.ifeenan.com/nodejs/2014-08-24-Duo%20JS%20%E4%B8%80%E4%B8%AA%E9%9D%9E%E5%B8%B8%E9%85%B7%E7%9A%84%E5%89%8D%E7%AB%AF%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7/)\n\n## Browserify\nBrowserify本身不是模块管理器,只是让服务器端的`CommonJS`格式的模块可以运行在浏览器端。这意味着通过它,我们可以使用`Node.js的npm模块管理器`。所以,实际上,它等于间接为浏览器提供了npm的功能。\n首先,安装Browserify。\n```\nnpm install -g browserify\n```\n然后,编写一个服务器端脚本。\n```\nvar uniq = require('uniq');\nvar nums = [ 5, 2, 1, 3, 2, 5, 4, 2, 0, 1 ];\nconsole.log(uniq(nums));\n```\n上面代码中uniq模块是CommonJS格式,无法在浏览器中运行。这时,Browserify就登场了,将上面代码编译为浏览器脚本。\n```\nbrowserify robot.js > bundle.js\n```\n生成的bundle.js可以直接插入网页。\n<script src=\"bundle.js\"></script>\nBrowserify编译的时候,会将脚本所依赖的模块一起编译进去。这意味着,它可以将多个模块合并成一个文件。\n\n## webpack\nweb开发中常用到的静态资源主要有JavaScript、CSS、图片、Jade等文件,webpack中将静态资源文件称之为模块。\nwebpack是一个module bundler(模块打包工具),其可以兼容多种js书写规范,且可以处理模块间的依赖关系,具有更强大的js模块化的功能。Webpack对它们进行统一的管理以及打包发布,其官方主页用下面这张图来说明Webpack的作用:\n{% asset_img webpack.png %}\n### webpack 好处:\n0. 模块来源广泛,支持包括npm/bower等等的各种主流模块安装/依赖解决方案\n1. 对 CommonJS 、 AMD 、ES6的语法做了兼容\n2. 对js、css、图片等资源文件都支持打包\n3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持\n4. 有独立的配置文件webpack.config.js\n5. 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间\n6. 支持 SourceUrls 和 SourceMaps,易于调试\n7. 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活\n8. webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快\n\n### 有了gulp和webpack,还需要bower吗?\nwebpack是一个比browserify功能更强大的模块加载器。既然是模块加载器,当然就包括对各种各样模块的加载,包括SASS/LESS/CoffeeScript/png/jpg等,以及webpack对于node_module模块加载已经非常完善了。\n那么,为什么还需要bower呢?由于前端开发很多第三方模块并非都以标准npm包形式存在,有一些非主流,或者各种原因没放到npm上的包,可以在bower找到。\n基于这个原因,使用webpack时候,凭着能用npm就用(依赖加载更加方便,功能更加强大),不能用的时候使用bower声明第三方模块依赖,然后使用js/css加载方式进行加载。\n值得一提的是,webpack官方也提供非常便利的方式加载bower模块(模块的主要文件,被声明在bower.json main属性里面),通过配置后就可以很方便地沿用require来加载bower模块。\n\n### 为什么很多人喜欢gulp+webpack,而不直接使用webpack?\n一个例子,webpack没有雪碧图功能,可能还有其他,但是这是我遇到的,需要配合gulp完成的\n\n[详解前端模块化工具-Webpack](https://segmentfault.com/a/1190000003970448)\n[gulp与webpack的迷思](https://segmentfault.com/a/1190000004249679)\n\n> 阮一峰:http://www.ruanyifeng.com/blog/2014/09/package-management.html","source":"_posts/JavaScript模块管理器-2016-07-10.md","raw":"title: JavaScript模块管理器\ndate: 2016-07-10 15:47:41\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 模块化\n---\n# 模块化\n时光回溯到2009年,CommonJS规范和NodeJS都还在襁褓之中,离Bower诞生还有三年时间,Ruby还统治着github,CoffeeScript在年末提交了第一个commit……\n备受加载顺序,依赖关系折磨的前端开发,开始站起来试图解决日益复杂的前端开发的种种问题,RequireJS降临了。如果说NodeJS吹响了JS全栈革命的号角,那么同时发生的前端模块化革命便是RequireJS的历史使命。\n五年过去了,RequireJS战胜了同级生LabJS,带起了中国小伙伴SeaJS。他完美地引领了前端模块化的革命,但今天看来,它有些过时了:它重浏览器端,轻打包编译,没有及时跟进包管理体系,almond没有成为标配而只是周边,配置晦涩……诞生太早的RequireJS,虽然一度成为了前端模块化的某种程度上的事实标准,但难掩其缺点。\n为了解决这个问题,前端的模块管理器(package management)应运而生,五年间,NodeJS成为了服务端以及脚本工具的一代翘楚,NPM的成功让大家意识到一个集中式的依赖/包管理体系的重要性,Bower应运而生,还有试图将CMD和NPM包带到前端领域,统一前后端包格式的Browserify等等,大量的前端工具爆发式地出现,WebPack是其中的(又)一款模块打包工具。\n\n<!--more-->\nrequire.js一类的模块管理器的问题在于各种参数设置过于繁琐,不容易学习,很难完全掌握。而且,实际应用中,往往还需要在服务器端,将所有模块合并后,再统一加载,这多出了很多工作量。\n* seajs / require : 是一种在线\"编译\" 模块的方案,相当于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module 这些东西。也就实现了模块化。\n* browserify / webpack / Duo : 是一个预编译模块的方案,相比于上面 ,这个方案更加智能。没用过browserify,这里以webpack为例。首先,它是预编译的,不需要在浏览器中加载解释器。另外,你在本地直接写JS,不管是 AMD / CMD / ES6 风格的模块化,它都能认识,并且编译成浏览器认识的JS。\n## Bower\n[Bower](https://bower.io/)的主要作用是,为模块的安装、升级和删除,提供一种统一的、可维护的管理模式。\nBower是一个客户端技术的软件包管理器,它可用于搜索、安装和卸载如JavaScript、HTML、CSS之类的网络资源。其他一些建立在Bower基础之上的开发工具,如YeoMan和Grunt,这个会在以后的文章中介绍。\n首先,安装Bower。\n```\nnpm install -g bower\n```\n然后,使用`bower install`命令安装各种模块。下面是一些例子。\n```\n# 模块的名称\n$ bower install jquery\n# github用户名/项目名\n$ bower install jquery/jquery\n# git代码仓库地址\n$ bower install git://github.com/user/package.git\n# 模块网址\n$ bower install http://example.com/script.js\n```\n所谓\"安装\",就是将该模块(以及其依赖的模块)下载到当前目录的bower_components子目录中。下载后,就可以直接插入网页。\n```\n<script src=\"/bower_componets/jquery/dist/jquery.min.js\">\n```\n`bower update`命令用于更新模块。如果不给出模块的名称,则更新所有模块。\n```\nbower update jquery\n```\n`bower uninstall`命令用于卸载模块。\n```\nbower uninstall jquery\n```\n注意,默认情况下,会连所依赖的模块一起卸载。比如,如果卸载jquery-ui,会连jquery一起卸载,除非还有别的模块依赖jquery。\n\n(bower简明入门教程)[https://segmentfault.com/a/1190000000349555]\n\n## Duo\n(Duo)[http://duojs.org/]是在Component的基础上开发的,语法和配置文件基本通用,并且借鉴了Browserify和Go语言的一些特点,相当地强大和好用。\n首先,安装Duo。\n```\nnpm install -g duo\n```\n然后,编写一个本地文件index.js。\n```\nvar uid = require('matthewmueller/uid');\nvar fmt = require('yields/fmt');\n \nvar msg = fmt('Your unique ID is %s!', uid());\nwindow.alert(msg);\n```\n上面代码加载了uid和fmt两个模块,采用Component的\"github用户名/项目名\"格式。\n接着,编译最终的脚本文件。\n```\nduo index.js > build.js\n```\n编译后的文件可以直接插入网页。\n```\n<script src=\"build.js\"></script>\n```\nDuo不仅可以编译JavaScript,还可以编译CSS。\n```\n@import 'necolas/normalize.css';\n@import './layout/layout.css';\n \nbody {\n color: teal;\n background: url('./background-image.jpg');\n}\n```\n编译时,Duo自动将normalize.css和layout.css,与当前样式表合并成同一个文件。\n```\nduo index.css > build.css\n```\n编译后,插入网页即可。\n```\n<link rel=\"stylesheet\" href=\"build.css\">\n```\n[Duo js 一个非常酷的前端打包工具](http://www.ifeenan.com/nodejs/2014-08-24-Duo%20JS%20%E4%B8%80%E4%B8%AA%E9%9D%9E%E5%B8%B8%E9%85%B7%E7%9A%84%E5%89%8D%E7%AB%AF%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7/)\n\n## Browserify\nBrowserify本身不是模块管理器,只是让服务器端的`CommonJS`格式的模块可以运行在浏览器端。这意味着通过它,我们可以使用`Node.js的npm模块管理器`。所以,实际上,它等于间接为浏览器提供了npm的功能。\n首先,安装Browserify。\n```\nnpm install -g browserify\n```\n然后,编写一个服务器端脚本。\n```\nvar uniq = require('uniq');\nvar nums = [ 5, 2, 1, 3, 2, 5, 4, 2, 0, 1 ];\nconsole.log(uniq(nums));\n```\n上面代码中uniq模块是CommonJS格式,无法在浏览器中运行。这时,Browserify就登场了,将上面代码编译为浏览器脚本。\n```\nbrowserify robot.js > bundle.js\n```\n生成的bundle.js可以直接插入网页。\n<script src=\"bundle.js\"></script>\nBrowserify编译的时候,会将脚本所依赖的模块一起编译进去。这意味着,它可以将多个模块合并成一个文件。\n\n## webpack\nweb开发中常用到的静态资源主要有JavaScript、CSS、图片、Jade等文件,webpack中将静态资源文件称之为模块。\nwebpack是一个module bundler(模块打包工具),其可以兼容多种js书写规范,且可以处理模块间的依赖关系,具有更强大的js模块化的功能。Webpack对它们进行统一的管理以及打包发布,其官方主页用下面这张图来说明Webpack的作用:\n{% asset_img webpack.png %}\n### webpack 好处:\n0. 模块来源广泛,支持包括npm/bower等等的各种主流模块安装/依赖解决方案\n1. 对 CommonJS 、 AMD 、ES6的语法做了兼容\n2. 对js、css、图片等资源文件都支持打包\n3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、ES6的支持\n4. 有独立的配置文件webpack.config.js\n5. 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间\n6. 支持 SourceUrls 和 SourceMaps,易于调试\n7. 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活\n8. webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快\n\n### 有了gulp和webpack,还需要bower吗?\nwebpack是一个比browserify功能更强大的模块加载器。既然是模块加载器,当然就包括对各种各样模块的加载,包括SASS/LESS/CoffeeScript/png/jpg等,以及webpack对于node_module模块加载已经非常完善了。\n那么,为什么还需要bower呢?由于前端开发很多第三方模块并非都以标准npm包形式存在,有一些非主流,或者各种原因没放到npm上的包,可以在bower找到。\n基于这个原因,使用webpack时候,凭着能用npm就用(依赖加载更加方便,功能更加强大),不能用的时候使用bower声明第三方模块依赖,然后使用js/css加载方式进行加载。\n值得一提的是,webpack官方也提供非常便利的方式加载bower模块(模块的主要文件,被声明在bower.json main属性里面),通过配置后就可以很方便地沿用require来加载bower模块。\n\n### 为什么很多人喜欢gulp+webpack,而不直接使用webpack?\n一个例子,webpack没有雪碧图功能,可能还有其他,但是这是我遇到的,需要配合gulp完成的\n\n[详解前端模块化工具-Webpack](https://segmentfault.com/a/1190000003970448)\n[gulp与webpack的迷思](https://segmentfault.com/a/1190000004249679)\n\n> 阮一峰:http://www.ruanyifeng.com/blog/2014/09/package-management.html","slug":"JavaScript模块管理器","published":1,"updated":"2016-07-10T09:50:13.913Z","layout":"post","photos":[],"link":"","_id":"citf9oma9001uskv7tatmwdjn"},{"title":"JavaScript-code-style","date":"2016-07-02T09:58:57.000Z","comments":1,"_content":"## 编程风格\n所谓”编程风格”(programming style),指的是编写代码的样式规则。不同的程序员,往往有不同的编程风格。\n\n有人说,编译器的规范叫做”语法规则”(grammar),这是程序员必须遵守的;而编译器忽略的部分,就叫”编程风格”(programming style),这是程序员可以自由选择的。这种说法不完全正确,程序员固然可以自由选择编程风格,但是好的编程风格有助于写出质量更高、错误更少、更易于维护的程序。\n\n所以,”编程风格”的选择不应该基于个人爱好、熟悉程度、打字量等因素,而要考虑如何尽量使代码清晰易读、减少出错。你选择的,不是你喜欢的风格,而是一种能够清晰表达你的意图的风格。这一点,对于JavaScript这种语法自由度很高的语言尤其重要。\n\n必须牢记的一点是,如果你选定了一种“编程风格”,就应该坚持遵守,切忌多种风格混用。如果你加入他人的项目,就应该遵守现有的风格。\n<!--more-->\n## 区块\n如果循环和判断的代码体只有一行,JavaScript允许该区块(block)省略大括号。\n```\nif (a)\n b();\n c();\n```\n上面代码的原意可能是下面这样。\n```\nif (a) {\n b();\n c();\n}\n```\n但是,实际效果却是下面这样。\n```\nif (a) {\n b();\n}\n c();\n```\n因此,总是使用大括号表示区块。\n\n另外,区块起首的大括号的位置,有许多不同的写法。\n\n最流行的有两种。一种是起首的大括号另起一行:\n```\nblock\n{\n // ...\n}\n```\n另一种是起首的大括号跟在关键字的后面。\n```\nblock {\n // ...\n}\n```\n一般来说,这两种写法都可以接受。但是,JavaScript要使用后一种,因为JavaScript会自动添加句末的分号,导致一些难以察觉的错误。\n```\nreturn\n{\n key: value\n};\n\n// 相当于\nreturn;\n{\n key: value\n};\n```\n上面的代码的原意,是要返回一个对象,但实际上返回的是`undefined`,因为JavaScript自动在`return`语句后面添加了分号。为了避免这一类错误,需要写成下面这样。\n```\nreturn {\n key : value\n};\n```\n因此,表示区块起首的大括号,不要另起一行。\n## 圆括号\n圆括号(parentheses)在JavaScript中有两种作用,一种表示函数的调用,另一种表示表达式的组合(grouping)。\n```\n// 圆括号表示函数的调用\nconsole.log('abc');\n\n// 圆括号表示表达式的组合\n(1 + 2) * 3\n```\n我们可以用空格,区分这两种不同的括号。\n1. 表示函数调用时,函数名与左括号之间没有空格。\n2. 表示函数定义时,函数名与左括号之间没有空格。\n3. 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。\n按照上面的规则,下面的写法都是不规范的。\n```\nfoo (bar)\nreturn(a+b);\nif(a === 0) {...}\nfunction foo (b) {...}\nfunction(x) {...}\n```\n上面代码的最后一行是一个匿名函数,function是语法关键字,不是函数名,所以与左括号之间应该要有一个空格。\n## 行尾的分号\n分号表示一条语句的结束。JavaScript规定,行尾的分号可以省略。事实上,确实有一些开发者行尾从来不写分号。但是,由于下面要讨论的原因,建议还是不要这个分号。\n### 不使用分号的情况\n#### for和while循环\n```\nfor ( ; ; ) {\n} // 没有分号\n\nwhile (true) {\n} // 没有分号\n```\n需要注意的是`do...while`循环是有分号的。\n```\ndo {\n a--;\n} while(a > 0); // 分号不能省略\n```\n#### 分支语句:if,switch,try\n```\nif (true) {\n} // 没有分号\n\nswitch () {\n} // 没有分号\n\ntry {\n} catch {\n} // 没有分号\n```\n#### 函数的声明语句\n```\nfunction f() {\n} // 没有分号\n```\n但是函数表达式仍然要使用分号。\n```\nvar f = function f() {\n};\n```\n以上三种情况,如果使用了分号,并不会出错。因为,解释引擎会把这个分号解释为空语句。\n### 分号的自动添加\n除了上一节的三种情况,所有语句都应该使用分号。但是,如果没有使用分号,大多数情况下,JavaScript会自动添加。\n```\nvar a = 1\n// 等同于\nvar a = 1;\n```\n这种语法特性被称为“分号的自动添加”(Automatic Semicolon Insertion,简称ASI)。\n\n因此,有人提倡省略句尾的分号。麻烦的是,如果下一行的开始可以与本行的结尾连在一起解释,JavaScript就不会自动添加分号。\n```\n// 等同于 var a = 3\nvar\na\n=\n3\n\n// 等同于 'abc'.length\n'abc'\n.length\n\n// 等同于 return a + b;\nreturn a +\nb;\n\n// 等同于 obj.foo(arg1, arg2);\nobj.foo(arg1,\narg2);\n\n// 等同于 3 * 2 + 10 * (27 / 6)\n3 * 2\n+\n10 * (27 / 6)\n```\n上面代码都会多行放在一起解释,不会每一行自动添加分号。这些例子还是比较容易看出来的,但是下面这个例子就不那么容易看出来了。\n```\nx = y\n(function () {\n // ...\n})();\n\n// 等同于\nx = y(function () {...})();\n```\n下面是更多不会自动添加分号的例子。\n```\n// 解释为 c(d+e)\nvar a = b + c\n(d+e).toString();\n\n// 解释为 a = b/hi/g.exec(c).map(d)\n// 正则表达式的斜杠,会当作除法运算符\na = b\n/hi/g.exec(c).map(d);\n\n// 解释为'b'['red', 'green'],\n// 即把字符串当作一个数组,按索引取值\nvar a = 'b'\n['red', 'green'].forEach(function (c) {\n console.log(c);\n})\n\n// 解释为 function(x) { return x }(a++)\n// 即调用匿名函数,结果f等于0\nvar a = 0;\nvar f = function(x) { return x }\n(a++)\n```\n只有下一行的开始与本行的结尾,无法放在一起解释,JavaScript引擎才会自动添加分号。\n```\nif (a < 0) a = 0\nconsole.log(a)\n\n// 等同于下面的代码,\n// 因为0console没有意义\n\nif (a < 0) a = 0;\nconsole.log(a)\n```\n另外,如果一行的起首是“自增”(`++`)或“自减”(`--`)运算符,则它们的前面会自动添加分号。\n```\na = b = c = 1\n\na\n++\nb\n--\nc\n\nconsole.log(a, b, c)\n// 1 2 0\n```\n上面代码之所以会得到“1 2 0”的结果,原因是自增和自减运算符前,自动加上了分号。上面的代码实际上等同于下面的形式。\n```\na = b = c = 1;\na;\n++b;\n--c;\n```\n如果`continue`、`break`、`return`和`throw`这四个语句后面,直接跟换行符,则会自动添加分号。这意味着,如果`return`语句返回的是一个对象的字面量,起首的大括号一定要写在同一行,否则得不到预期结果。\n```\nreturn\n{ first: 'Jane' };\n\n// 解释成\nreturn;\n{ first: 'Jane' };\n```\n由于解释引擎自动添加分号的行为难以预测,因此编写代码的时候不应该省略行尾的分号。\n\n不应该省略结尾的分号,还有一个原因。有些JavaScript代码压缩器不会自动添加分号,因此遇到没有分号的结尾,就会让代码保持原状,而不是压缩成一行,使得压缩无法得到最优的结果。\n\n另外,不写结尾的分号,可能会导致脚本合并出错。所以,有的代码库在第一行语句开始前,会加上一个分号。\n```\n;var a = 1;\n// ...\n```\n上面这种写法就可以避免与其他脚本合并时,排在前面的脚本最后一行语句没有分号,导致运行出错的问题。\n## 变量声明\nJavaScript会自动将变量声明”提升”(hoist)到代码块(block)的头部。\n```\nif (!o) {\n var o = {};\n}\n\n// 等同于\n\nvar o;\nif (!o) {\n o = {};\n}\n```\n为了避免可能出现的问题,最好把变量声明都放在代码块的头部。\n```\nfor (var i = 0; i < 10; i++) {\n // ...\n}\n\n// 写成\n\nvar i;\nfor (i = 0; i < 10; i++) {\n // ...\n}\n```\n另外,所有函数都应该在使用之前定义,函数内部的变量声明,都应该放在函数的头部。\n## new命令\nJavaScript使用`new`命令,从构造函数生成一个新对象。\n```\nvar o = new myObject();\n```\n上面这种做法的问题是,一旦你忘了加上`new`,`myObject()`内部的`this`关键字就会指向全局对象,导致所有绑定在`this`上面的变量,都变成全局变量。\n\n因此,建议使用`Object.create()`命令,替代`new`命令。如果不得不使用`new`,为了防止出错,最好在视觉上把构造函数与其他函数区分开来。比如,构造函数的函数名,采用首字母大写(`InitialCap`),其他函数名一律首字母小写。\n## 相等和严格相等\nJavaScript有两个表示”相等”的运算符:”相等”(`==`)和”严格相等”(`===`)。\n\n因为”相等”运算符会自动转换变量类型,造成很多意想不到的情况:\n```\n0 == ''// true\n1 == true // true\n2 == true // false\n0 == '0' // true\nfalse == 'false' // false\nfalse == '0' // true\n’ \\t\\r\\n ' == 0 // true\n```\n因此,不要使用“相等”(`==`)运算符,只使用“严格相等”(`===`)运算符。\n\n## switch…case结构\n`switch...case`结构要求,在每一个`case`的最后一行必须是`break`语句,否则会接着运行下一个`case`。这样不仅容易忘记,还会造成代码的冗长。\n\n而且,`switch...case`不使用大括号,不利于代码形式的统一。此外,这种结构类似于`goto`语句,容易造成程序流程的混乱,使得代码结构混乱不堪,不符合面向对象编程的原则。\n```\nfunction doAction(action) {\n switch (action) {\n case 'hack':\n return 'hack';\n break;\n case 'slash':\n return 'slash';\n break;\n case 'run':\n return 'run';\n break;\n default:\n throw new Error('Invalid action.');\n }\n}\n```\n上面的代码建议改写成对象结构。\n```\nfunction doAction(action) {\n var actions = {\n 'hack': function () {\n return 'hack';\n },\n 'slash': function () {\n return 'slash';\n },\n 'run': function () {\n return 'run';\n }\n };\n\n if (typeof actions[action] !== 'function') {\n throw new Error('Invalid action.');\n }\n\n return actions[action]();\n}\n```\n建议避免使用`switch...case`结构,用对象结构代替。\n转自: [阮一峰教程](http://javascript.ruanyifeng.com/grammar/style.html)","source":"_posts/JavaScript-code-style-2016-07-02.md","raw":"title: JavaScript-code-style\ndate: 2016-07-02 17:58:57\ntags:\n- jQuery\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n## 编程风格\n所谓”编程风格”(programming style),指的是编写代码的样式规则。不同的程序员,往往有不同的编程风格。\n\n有人说,编译器的规范叫做”语法规则”(grammar),这是程序员必须遵守的;而编译器忽略的部分,就叫”编程风格”(programming style),这是程序员可以自由选择的。这种说法不完全正确,程序员固然可以自由选择编程风格,但是好的编程风格有助于写出质量更高、错误更少、更易于维护的程序。\n\n所以,”编程风格”的选择不应该基于个人爱好、熟悉程度、打字量等因素,而要考虑如何尽量使代码清晰易读、减少出错。你选择的,不是你喜欢的风格,而是一种能够清晰表达你的意图的风格。这一点,对于JavaScript这种语法自由度很高的语言尤其重要。\n\n必须牢记的一点是,如果你选定了一种“编程风格”,就应该坚持遵守,切忌多种风格混用。如果你加入他人的项目,就应该遵守现有的风格。\n<!--more-->\n## 区块\n如果循环和判断的代码体只有一行,JavaScript允许该区块(block)省略大括号。\n```\nif (a)\n b();\n c();\n```\n上面代码的原意可能是下面这样。\n```\nif (a) {\n b();\n c();\n}\n```\n但是,实际效果却是下面这样。\n```\nif (a) {\n b();\n}\n c();\n```\n因此,总是使用大括号表示区块。\n\n另外,区块起首的大括号的位置,有许多不同的写法。\n\n最流行的有两种。一种是起首的大括号另起一行:\n```\nblock\n{\n // ...\n}\n```\n另一种是起首的大括号跟在关键字的后面。\n```\nblock {\n // ...\n}\n```\n一般来说,这两种写法都可以接受。但是,JavaScript要使用后一种,因为JavaScript会自动添加句末的分号,导致一些难以察觉的错误。\n```\nreturn\n{\n key: value\n};\n\n// 相当于\nreturn;\n{\n key: value\n};\n```\n上面的代码的原意,是要返回一个对象,但实际上返回的是`undefined`,因为JavaScript自动在`return`语句后面添加了分号。为了避免这一类错误,需要写成下面这样。\n```\nreturn {\n key : value\n};\n```\n因此,表示区块起首的大括号,不要另起一行。\n## 圆括号\n圆括号(parentheses)在JavaScript中有两种作用,一种表示函数的调用,另一种表示表达式的组合(grouping)。\n```\n// 圆括号表示函数的调用\nconsole.log('abc');\n\n// 圆括号表示表达式的组合\n(1 + 2) * 3\n```\n我们可以用空格,区分这两种不同的括号。\n1. 表示函数调用时,函数名与左括号之间没有空格。\n2. 表示函数定义时,函数名与左括号之间没有空格。\n3. 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。\n按照上面的规则,下面的写法都是不规范的。\n```\nfoo (bar)\nreturn(a+b);\nif(a === 0) {...}\nfunction foo (b) {...}\nfunction(x) {...}\n```\n上面代码的最后一行是一个匿名函数,function是语法关键字,不是函数名,所以与左括号之间应该要有一个空格。\n## 行尾的分号\n分号表示一条语句的结束。JavaScript规定,行尾的分号可以省略。事实上,确实有一些开发者行尾从来不写分号。但是,由于下面要讨论的原因,建议还是不要这个分号。\n### 不使用分号的情况\n#### for和while循环\n```\nfor ( ; ; ) {\n} // 没有分号\n\nwhile (true) {\n} // 没有分号\n```\n需要注意的是`do...while`循环是有分号的。\n```\ndo {\n a--;\n} while(a > 0); // 分号不能省略\n```\n#### 分支语句:if,switch,try\n```\nif (true) {\n} // 没有分号\n\nswitch () {\n} // 没有分号\n\ntry {\n} catch {\n} // 没有分号\n```\n#### 函数的声明语句\n```\nfunction f() {\n} // 没有分号\n```\n但是函数表达式仍然要使用分号。\n```\nvar f = function f() {\n};\n```\n以上三种情况,如果使用了分号,并不会出错。因为,解释引擎会把这个分号解释为空语句。\n### 分号的自动添加\n除了上一节的三种情况,所有语句都应该使用分号。但是,如果没有使用分号,大多数情况下,JavaScript会自动添加。\n```\nvar a = 1\n// 等同于\nvar a = 1;\n```\n这种语法特性被称为“分号的自动添加”(Automatic Semicolon Insertion,简称ASI)。\n\n因此,有人提倡省略句尾的分号。麻烦的是,如果下一行的开始可以与本行的结尾连在一起解释,JavaScript就不会自动添加分号。\n```\n// 等同于 var a = 3\nvar\na\n=\n3\n\n// 等同于 'abc'.length\n'abc'\n.length\n\n// 等同于 return a + b;\nreturn a +\nb;\n\n// 等同于 obj.foo(arg1, arg2);\nobj.foo(arg1,\narg2);\n\n// 等同于 3 * 2 + 10 * (27 / 6)\n3 * 2\n+\n10 * (27 / 6)\n```\n上面代码都会多行放在一起解释,不会每一行自动添加分号。这些例子还是比较容易看出来的,但是下面这个例子就不那么容易看出来了。\n```\nx = y\n(function () {\n // ...\n})();\n\n// 等同于\nx = y(function () {...})();\n```\n下面是更多不会自动添加分号的例子。\n```\n// 解释为 c(d+e)\nvar a = b + c\n(d+e).toString();\n\n// 解释为 a = b/hi/g.exec(c).map(d)\n// 正则表达式的斜杠,会当作除法运算符\na = b\n/hi/g.exec(c).map(d);\n\n// 解释为'b'['red', 'green'],\n// 即把字符串当作一个数组,按索引取值\nvar a = 'b'\n['red', 'green'].forEach(function (c) {\n console.log(c);\n})\n\n// 解释为 function(x) { return x }(a++)\n// 即调用匿名函数,结果f等于0\nvar a = 0;\nvar f = function(x) { return x }\n(a++)\n```\n只有下一行的开始与本行的结尾,无法放在一起解释,JavaScript引擎才会自动添加分号。\n```\nif (a < 0) a = 0\nconsole.log(a)\n\n// 等同于下面的代码,\n// 因为0console没有意义\n\nif (a < 0) a = 0;\nconsole.log(a)\n```\n另外,如果一行的起首是“自增”(`++`)或“自减”(`--`)运算符,则它们的前面会自动添加分号。\n```\na = b = c = 1\n\na\n++\nb\n--\nc\n\nconsole.log(a, b, c)\n// 1 2 0\n```\n上面代码之所以会得到“1 2 0”的结果,原因是自增和自减运算符前,自动加上了分号。上面的代码实际上等同于下面的形式。\n```\na = b = c = 1;\na;\n++b;\n--c;\n```\n如果`continue`、`break`、`return`和`throw`这四个语句后面,直接跟换行符,则会自动添加分号。这意味着,如果`return`语句返回的是一个对象的字面量,起首的大括号一定要写在同一行,否则得不到预期结果。\n```\nreturn\n{ first: 'Jane' };\n\n// 解释成\nreturn;\n{ first: 'Jane' };\n```\n由于解释引擎自动添加分号的行为难以预测,因此编写代码的时候不应该省略行尾的分号。\n\n不应该省略结尾的分号,还有一个原因。有些JavaScript代码压缩器不会自动添加分号,因此遇到没有分号的结尾,就会让代码保持原状,而不是压缩成一行,使得压缩无法得到最优的结果。\n\n另外,不写结尾的分号,可能会导致脚本合并出错。所以,有的代码库在第一行语句开始前,会加上一个分号。\n```\n;var a = 1;\n// ...\n```\n上面这种写法就可以避免与其他脚本合并时,排在前面的脚本最后一行语句没有分号,导致运行出错的问题。\n## 变量声明\nJavaScript会自动将变量声明”提升”(hoist)到代码块(block)的头部。\n```\nif (!o) {\n var o = {};\n}\n\n// 等同于\n\nvar o;\nif (!o) {\n o = {};\n}\n```\n为了避免可能出现的问题,最好把变量声明都放在代码块的头部。\n```\nfor (var i = 0; i < 10; i++) {\n // ...\n}\n\n// 写成\n\nvar i;\nfor (i = 0; i < 10; i++) {\n // ...\n}\n```\n另外,所有函数都应该在使用之前定义,函数内部的变量声明,都应该放在函数的头部。\n## new命令\nJavaScript使用`new`命令,从构造函数生成一个新对象。\n```\nvar o = new myObject();\n```\n上面这种做法的问题是,一旦你忘了加上`new`,`myObject()`内部的`this`关键字就会指向全局对象,导致所有绑定在`this`上面的变量,都变成全局变量。\n\n因此,建议使用`Object.create()`命令,替代`new`命令。如果不得不使用`new`,为了防止出错,最好在视觉上把构造函数与其他函数区分开来。比如,构造函数的函数名,采用首字母大写(`InitialCap`),其他函数名一律首字母小写。\n## 相等和严格相等\nJavaScript有两个表示”相等”的运算符:”相等”(`==`)和”严格相等”(`===`)。\n\n因为”相等”运算符会自动转换变量类型,造成很多意想不到的情况:\n```\n0 == ''// true\n1 == true // true\n2 == true // false\n0 == '0' // true\nfalse == 'false' // false\nfalse == '0' // true\n’ \\t\\r\\n ' == 0 // true\n```\n因此,不要使用“相等”(`==`)运算符,只使用“严格相等”(`===`)运算符。\n\n## switch…case结构\n`switch...case`结构要求,在每一个`case`的最后一行必须是`break`语句,否则会接着运行下一个`case`。这样不仅容易忘记,还会造成代码的冗长。\n\n而且,`switch...case`不使用大括号,不利于代码形式的统一。此外,这种结构类似于`goto`语句,容易造成程序流程的混乱,使得代码结构混乱不堪,不符合面向对象编程的原则。\n```\nfunction doAction(action) {\n switch (action) {\n case 'hack':\n return 'hack';\n break;\n case 'slash':\n return 'slash';\n break;\n case 'run':\n return 'run';\n break;\n default:\n throw new Error('Invalid action.');\n }\n}\n```\n上面的代码建议改写成对象结构。\n```\nfunction doAction(action) {\n var actions = {\n 'hack': function () {\n return 'hack';\n },\n 'slash': function () {\n return 'slash';\n },\n 'run': function () {\n return 'run';\n }\n };\n\n if (typeof actions[action] !== 'function') {\n throw new Error('Invalid action.');\n }\n\n return actions[action]();\n}\n```\n建议避免使用`switch...case`结构,用对象结构代替。\n转自: [阮一峰教程](http://javascript.ruanyifeng.com/grammar/style.html)","slug":"JavaScript-code-style","published":1,"updated":"2016-07-04T01:50:04.505Z","layout":"post","photos":[],"link":"","_id":"citf9omag001xskv7wek3cf29"},{"title":"ECMAScrip中的对象存取器:getter和setter","date":"2016-01-21T09:34:17.000Z","comments":1,"_content":"显然这是一个无关IE(高级IE除外)的话题,尽管如此,有兴趣的同学还是一起来认识一下ECMAScript5标准中getter和setter的实现。在一个对象中,操作其中的属性或方法,通常运用最多的就是读(引用)和写了,譬如说o.get,这就是一个读的操作,而o.set = 1则是一个写的操作。事实上在除ie外最新主流浏览器的实现中,任何一个对象的键值都可以被getter和setter方法所取代,这被称之为“存取器属性”。\n<!--more-->\n毫无疑问,getter负责查询值,它不带任何参数,setter则负责设置键值,值是以参数的形式传递,在他的函数体中,一切的return都是无效的。和普通属性不同的是,存储器属性在只声明了get或set时,对于读和写是两者不可兼得的,当它只拥有了getter方法,那么它仅仅只读,同样的,当它只有setter方法,那么您读到的永远都是undefined。如何声明对象存储器属性呢? 最快捷的途径就是利用对象字面量的语法来写了,请看下述一段代码:\n\n var oo = {\n name : '贤心',\n get sex(){\n return 'man';\n }\n };\n //显然这是不允许的,因为贤心并不希望外界去改变他是男性的事实,所以对于sex只设置了只读功能\n oo.sex = 'woman';//在严格模式下报错。\n console.log(oo.sex); //结果依然是man\n有意思的是,这颠覆了我们以往的理解,就是在方法定义时并未用function关键字。事实上这里的get或set,你可以理解为两种不同状态下的function:包容的一面(写),安全的一面(读),当一种整体被肢解为不同的形态,意味着我们可能不再需要在表现形式上遵循传统,所以我们并没有使用冒号将键和值分开。那么,继续上面的例子。你将如何在存储器属性的基础上变得读写兼备呢,也许下面的一段会给你带来答案:\n\n var oo = {\n name : '贤心',\n get sex(){\n if(this.sexx){\n return this.sexx;\n }else{\n return 'man';\n }\n }, set sex(val){\n this.sexx = val;\n }\n };\n //噢,他如此包容,乃至于人们改变他的性别,他也接受\n oo.sex = 'woman';\n console.log(oo.sex); //结果woman\n\n或许你会觉得这是多此一举的,因为我们完全可以忽视get和set,直接让sex方法具备两种权限。 但之所以我们将get和set单独拿出来,是为了更加清晰地理解ECMAScript5对javascript对象键值操作中,一个更为严谨的诠释。 当然,在IE污染的中国,新型的主流技术总是显得格格不入,在实际的项目开发中,也许你永远不会用到get和set,但谁又能保证以后不会呢……\n\n摘自:[贤心博客](http://sentsin.com/web/20.html)\n","source":"_posts/ECMAScrip中的对象存取器-getter和setter-2016-01-21.md","raw":"title: 'ECMAScrip中的对象存取器:getter和setter'\ndate: 2016-01-21 17:34:17\ntags:\n- JavaScript\ncomments: true\n---\n显然这是一个无关IE(高级IE除外)的话题,尽管如此,有兴趣的同学还是一起来认识一下ECMAScript5标准中getter和setter的实现。在一个对象中,操作其中的属性或方法,通常运用最多的就是读(引用)和写了,譬如说o.get,这就是一个读的操作,而o.set = 1则是一个写的操作。事实上在除ie外最新主流浏览器的实现中,任何一个对象的键值都可以被getter和setter方法所取代,这被称之为“存取器属性”。\n<!--more-->\n毫无疑问,getter负责查询值,它不带任何参数,setter则负责设置键值,值是以参数的形式传递,在他的函数体中,一切的return都是无效的。和普通属性不同的是,存储器属性在只声明了get或set时,对于读和写是两者不可兼得的,当它只拥有了getter方法,那么它仅仅只读,同样的,当它只有setter方法,那么您读到的永远都是undefined。如何声明对象存储器属性呢? 最快捷的途径就是利用对象字面量的语法来写了,请看下述一段代码:\n\n var oo = {\n name : '贤心',\n get sex(){\n return 'man';\n }\n };\n //显然这是不允许的,因为贤心并不希望外界去改变他是男性的事实,所以对于sex只设置了只读功能\n oo.sex = 'woman';//在严格模式下报错。\n console.log(oo.sex); //结果依然是man\n有意思的是,这颠覆了我们以往的理解,就是在方法定义时并未用function关键字。事实上这里的get或set,你可以理解为两种不同状态下的function:包容的一面(写),安全的一面(读),当一种整体被肢解为不同的形态,意味着我们可能不再需要在表现形式上遵循传统,所以我们并没有使用冒号将键和值分开。那么,继续上面的例子。你将如何在存储器属性的基础上变得读写兼备呢,也许下面的一段会给你带来答案:\n\n var oo = {\n name : '贤心',\n get sex(){\n if(this.sexx){\n return this.sexx;\n }else{\n return 'man';\n }\n }, set sex(val){\n this.sexx = val;\n }\n };\n //噢,他如此包容,乃至于人们改变他的性别,他也接受\n oo.sex = 'woman';\n console.log(oo.sex); //结果woman\n\n或许你会觉得这是多此一举的,因为我们完全可以忽视get和set,直接让sex方法具备两种权限。 但之所以我们将get和set单独拿出来,是为了更加清晰地理解ECMAScript5对javascript对象键值操作中,一个更为严谨的诠释。 当然,在IE污染的中国,新型的主流技术总是显得格格不入,在实际的项目开发中,也许你永远不会用到get和set,但谁又能保证以后不会呢……\n\n摘自:[贤心博客](http://sentsin.com/web/20.html)\n","slug":"ECMAScrip中的对象存取器-getter和setter","published":1,"updated":"2016-01-21T09:37:50.135Z","layout":"post","photos":[],"link":"","_id":"citf9omao0021skv7k78oiq9d"},{"title":"onload vs DOMContentLoaded","date":"2016-01-08T02:27:11.000Z","comments":1,"_content":"* `$(document).ready(function () { });`\n* `$(function () { });`\n\n以上两行代码的目的和效果都一样———待DOM加载完成之后,执行传入的function函数。\n\n这是我们在页面初始化时经常使用的监听方案,那么他的实际的执行关系时什么样的呢?\n在原生js中是什么样的一种表现?\n<!--more-->\n## 定义\n+ onload:\n当onload事件触发的时候,页面上的所有dom,样式表,脚本,图片,flash,iframe都已经加载完成了。\n+ DOMContentLoaded:\n当DOMContentLoaded事件触发时,仅当dom加载完成,不包括样式表,图片,flash,iframe\n\n光看定义,一目了然,哪个比较适合作为我们判断的标准:图片啊什么的,我们完全可以不用等。\n\n在某些Gecko和Webkit引擎版本的浏览器里面,包括IE8在内,会同时发起多个http的请求并行加载样式表和脚步,但是脚本会等样式表加载完成之后才会被执行,甚至样式表加载之前页面都不会渲染。opera不会,样式表未加载好就可以执行js。\n\n{% asset_img onLoadVSDomContentLoaded.png %}\n\n### 兼容方案\n#### ie8及以下兼容处理方案\nie的一般处理方案 --- `onreadystatechange` 事件。\nhtml加载过程中会有一个document.readyState状态\n五种状态:\n+ 0(未初始化):还没有send\n+ 1 loading(载入):正在发送请求\n+ 2 loaded(载入完成):执行完成,已经接收到全部响应内容\n+ 3 interactive(交互): 正在解析响应内容\n+ 4 complete(完成): 响应内容解析完成,客户端可以用了。\n*complete事件和window.onload事件是同时的。*\n\n这就是要监听页面的readystatechange事件,当事件为interactive或者complete时就可以开始做js的事情了。但是如果我们注册 ready 函数的时间点太晚了,这时页面已经加载完成,而我们才注册自己的 ready 函数,那就用不着上面的层层检查了,直接看看当前页面的 readyState 就可以了,如果已经是 complete ,那就可以直接执行我们准备注册的 ready 函数了。不过 ChrisS 报告了一个很特别的错误情况,我们需要延迟一下执行。\n\n> setTimeout 经常被用来做网页上的定时器,允许为它指定一个毫秒数作为间隔执行的时间。当被启动的程序需要在非常短的时间内运行,我们就会给它指定一个很小的时间数,或者需要马上执行的话,我们甚至把这个毫秒数设置为0,但事实上,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动。这个最小的时间间隔是多少呢?这和浏览器及操作系统有关。在John Resig的新书《Javascript忍者的秘密》一书中提到。\n>> Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒),另外,MDC中关于setTimeout的介绍中也提到,Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒。\n\n既然规范都是这样写的,那看来使用setTimeout是没办法再把这个最小时间间隔缩短了。这样,通过设置为 1, 我们可以让程序在浏览器支持的最小时间间隔之后执行了。\n\n```javascript\n if (document.readyState === \"complete\") {\n // 延迟 1 毫秒之后,执行 ready 函数\n setTimeout(jQuery.ready, 1);\n }\n```\n\n#### doScroll 检测法\n但是当页面中带有iframe时,这个readyState状态会挂起一直等待,等待页面的iframe也加载完毕之后再处理,这个过程是我们不想要得,那就有另外一种处理方案。\n> MSDN 关于 JScript 的一个方法有段不起眼的话,当页面 DOM 未加载完成时,调用 doScroll 方法时,会产生异常。那么我们反过来用,如果不异常,那么就是页面DOM加载完毕了!Diego Perini 在 2007 年的时候,报告了一种检测 IE 是否加载完成的方式,使用 doScroll 方法调用。详细的说明见这里。原理是对于 IE 在非 iframe 内时,只有不断地通过能否执行 doScroll 判断 DOM 是否加载完毕。在本例中每间隔 50 毫秒尝试去执行 doScroll,注意,由于页面没有加载完成的时候,调用 doScroll 会导致异常,所以使用了 try -catch 来捕获异常。\n\n```javascript\n (function doScrollCheck() {\n if (!jQuery.isReady) { \n try {\n // Use the trick by Diego Perini\n // http://javascript.nwbox.com/IEContentLoaded/\n top.doScroll(\"left\");\n } catch (e) {\n return setTimeout(doScrollCheck, 50);\n } \n // and execute any waiting functions\n jQuery.ready();\n }\n })();\n```\n\n#### jQuery的实现 \n\n```javascript\n //全局方法\n DOMContentLoaded = function() {\n if ( document.addEventListener ) {\n document.removeEventListener( \"DOMContentLoaded\", DOMContentLoaded, false );\n jQuery.ready();\n } else if ( document.readyState === \"complete\" ) {\n // we're here because readyState === \"complete\" in oldIE\n // which is good enough for us to call the dom ready!\n document.detachEvent( \"onreadystatechange\", DOMContentLoaded );\n jQuery.ready();\n }\n }\n\n //入口 jquery实例调用\n ready: function( fn ) {\n // Add the callback\n jQuery.ready.promise().done( fn );\n\n return this;\n }\n\n jQuery.ready.promise = function( obj ) {\n if ( !readyList ) {\n\n readyList = jQuery.Deferred();\n\n // Catch cases where $(document).ready() is called after the browser event has already occurred.\n // we once tried to use readyState \"interactive\" here, but it caused issues like the one\n // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n // 当页面加载完了,直接调用ready方法\n if ( document.readyState === \"complete\" ) {\n // Handle it asynchronously to allow scripts the opportunity to delay ready\n setTimeout( jQuery.ready, 1 );\n\n // Standards-based browsers support DOMContentLoaded\n } else if ( document.addEventListener ) {\n // Use the handy event callback\n document.addEventListener( \"DOMContentLoaded\", DOMContentLoaded, false );\n\n // A fallback to window.onload, that will always work\n window.addEventListener( \"load\", jQuery.ready, false );\n\n // If IE event model is used\n } else {\n // Ensure firing before onload, maybe late but safe also for iframes\n document.attachEvent( \"onreadystatechange\", DOMContentLoaded );\n\n // A fallback to window.onload, that will always work\n window.attachEvent( \"onload\", jQuery.ready );\n\n // If IE and not a frame\n // continually check to see if the document is ready\n var top = false;\n\n try {\n top = window.frameElement == null && document.documentElement;\n } catch(e) {}\n\n if ( top && top.doScroll ) {\n (function doScrollCheck() {\n if ( !jQuery.isReady ) {\n\n try {\n // Use the trick by Diego Perini\n // http://javascript.nwbox.com/IEContentLoaded/\n top.doScroll(\"left\");\n } catch(e) {\n return setTimeout( doScrollCheck, 50 );\n }\n\n // and execute any waiting functions\n jQuery.ready();\n }\n })();\n }\n }\n }\n return readyList.promise( obj );\n };\n\n jQuery.extend({\n // 表示ready方法是否正在执行,若正在执行,则将isReady设置为true\n isReady: false,\n\n // ready方法执行前需要等待的次数\n readyWait: 1,\n\n // hold或者释放ready方法,若参数为true则readyWait++,否则执行ready,传入参数为true\n holdReady: function( hold ) {\n if ( hold ) {\n jQuery.readyWait++;\n } else {\n jQuery.ready( true );\n }\n },\n\n // 当DOM加载完毕时开始执行ready\n ready: function( wait ) {\n\n // 若传入的参数为true,则--readyWait;否则判断isReady,即ready是否正在执行 \n if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n return;\n }\n\n // Remember that the DOM is ready\n jQuery.isReady = true;\n\n // 若readyWait-1后还是大于0,则返回,不执行ready。\n if ( wait !== true && --jQuery.readyWait > 0 ) {\n return;\n }\n\n // If there are functions bound, to execute\n readyList.resolveWith( document, [ jQuery ] );\n\n // 触发ready方法,然后解除绑定的ready方法。\n if ( jQuery.fn.triggerHandler ) {\n jQuery( document ).triggerHandler( \"ready\" );\n jQuery( document ).off( \"ready\" );\n }\n }\n });\n```\n根据以上代码可见,最终DOMContented事件执行的,其实是jQUery.ready()这个工具函数。\n(注意,jquery.ready()和jquery(document).raedy()不一样!!,前者是工具函数,后者是实例函数。)\n这里是通过定义一个DOMContentLoaded函数作为桥梁来执行jquery.ready()函数的,这样做的目的就是为了及时的remove掉document的DOMContentLoaded事件的引用。\n{% asset_img zongjie.png %}\n推荐好文: [何控制jquery的ready事件](http://www.xiabingbao.com/jquery/2015/06/27/jquery-holdready/)\n","source":"_posts/DOMContentLoaded-2016-01-08.md","raw":"title: onload vs DOMContentLoaded\ndate: 2016-01-08 10:27:11\ntags:\n- jQuery\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n* `$(document).ready(function () { });`\n* `$(function () { });`\n\n以上两行代码的目的和效果都一样———待DOM加载完成之后,执行传入的function函数。\n\n这是我们在页面初始化时经常使用的监听方案,那么他的实际的执行关系时什么样的呢?\n在原生js中是什么样的一种表现?\n<!--more-->\n## 定义\n+ onload:\n当onload事件触发的时候,页面上的所有dom,样式表,脚本,图片,flash,iframe都已经加载完成了。\n+ DOMContentLoaded:\n当DOMContentLoaded事件触发时,仅当dom加载完成,不包括样式表,图片,flash,iframe\n\n光看定义,一目了然,哪个比较适合作为我们判断的标准:图片啊什么的,我们完全可以不用等。\n\n在某些Gecko和Webkit引擎版本的浏览器里面,包括IE8在内,会同时发起多个http的请求并行加载样式表和脚步,但是脚本会等样式表加载完成之后才会被执行,甚至样式表加载之前页面都不会渲染。opera不会,样式表未加载好就可以执行js。\n\n{% asset_img onLoadVSDomContentLoaded.png %}\n\n### 兼容方案\n#### ie8及以下兼容处理方案\nie的一般处理方案 --- `onreadystatechange` 事件。\nhtml加载过程中会有一个document.readyState状态\n五种状态:\n+ 0(未初始化):还没有send\n+ 1 loading(载入):正在发送请求\n+ 2 loaded(载入完成):执行完成,已经接收到全部响应内容\n+ 3 interactive(交互): 正在解析响应内容\n+ 4 complete(完成): 响应内容解析完成,客户端可以用了。\n*complete事件和window.onload事件是同时的。*\n\n这就是要监听页面的readystatechange事件,当事件为interactive或者complete时就可以开始做js的事情了。但是如果我们注册 ready 函数的时间点太晚了,这时页面已经加载完成,而我们才注册自己的 ready 函数,那就用不着上面的层层检查了,直接看看当前页面的 readyState 就可以了,如果已经是 complete ,那就可以直接执行我们准备注册的 ready 函数了。不过 ChrisS 报告了一个很特别的错误情况,我们需要延迟一下执行。\n\n> setTimeout 经常被用来做网页上的定时器,允许为它指定一个毫秒数作为间隔执行的时间。当被启动的程序需要在非常短的时间内运行,我们就会给它指定一个很小的时间数,或者需要马上执行的话,我们甚至把这个毫秒数设置为0,但事实上,setTimeout有一个最小执行时间,当指定的时间小于该时间时,浏览器会用最小允许的时间作为setTimeout的时间间隔,也就是说即使我们把setTimeout的毫秒数设置为0,被调用的程序也没有马上启动。这个最小的时间间隔是多少呢?这和浏览器及操作系统有关。在John Resig的新书《Javascript忍者的秘密》一书中提到。\n>> Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在苹果机上的最小时间间隔是10毫秒,在Windows系统上的最小时间间隔大约是15毫秒),另外,MDC中关于setTimeout的介绍中也提到,Firefox中定义的最小时间间隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定义的最小时间间隔是4毫秒。\n\n既然规范都是这样写的,那看来使用setTimeout是没办法再把这个最小时间间隔缩短了。这样,通过设置为 1, 我们可以让程序在浏览器支持的最小时间间隔之后执行了。\n\n```javascript\n if (document.readyState === \"complete\") {\n // 延迟 1 毫秒之后,执行 ready 函数\n setTimeout(jQuery.ready, 1);\n }\n```\n\n#### doScroll 检测法\n但是当页面中带有iframe时,这个readyState状态会挂起一直等待,等待页面的iframe也加载完毕之后再处理,这个过程是我们不想要得,那就有另外一种处理方案。\n> MSDN 关于 JScript 的一个方法有段不起眼的话,当页面 DOM 未加载完成时,调用 doScroll 方法时,会产生异常。那么我们反过来用,如果不异常,那么就是页面DOM加载完毕了!Diego Perini 在 2007 年的时候,报告了一种检测 IE 是否加载完成的方式,使用 doScroll 方法调用。详细的说明见这里。原理是对于 IE 在非 iframe 内时,只有不断地通过能否执行 doScroll 判断 DOM 是否加载完毕。在本例中每间隔 50 毫秒尝试去执行 doScroll,注意,由于页面没有加载完成的时候,调用 doScroll 会导致异常,所以使用了 try -catch 来捕获异常。\n\n```javascript\n (function doScrollCheck() {\n if (!jQuery.isReady) { \n try {\n // Use the trick by Diego Perini\n // http://javascript.nwbox.com/IEContentLoaded/\n top.doScroll(\"left\");\n } catch (e) {\n return setTimeout(doScrollCheck, 50);\n } \n // and execute any waiting functions\n jQuery.ready();\n }\n })();\n```\n\n#### jQuery的实现 \n\n```javascript\n //全局方法\n DOMContentLoaded = function() {\n if ( document.addEventListener ) {\n document.removeEventListener( \"DOMContentLoaded\", DOMContentLoaded, false );\n jQuery.ready();\n } else if ( document.readyState === \"complete\" ) {\n // we're here because readyState === \"complete\" in oldIE\n // which is good enough for us to call the dom ready!\n document.detachEvent( \"onreadystatechange\", DOMContentLoaded );\n jQuery.ready();\n }\n }\n\n //入口 jquery实例调用\n ready: function( fn ) {\n // Add the callback\n jQuery.ready.promise().done( fn );\n\n return this;\n }\n\n jQuery.ready.promise = function( obj ) {\n if ( !readyList ) {\n\n readyList = jQuery.Deferred();\n\n // Catch cases where $(document).ready() is called after the browser event has already occurred.\n // we once tried to use readyState \"interactive\" here, but it caused issues like the one\n // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n // 当页面加载完了,直接调用ready方法\n if ( document.readyState === \"complete\" ) {\n // Handle it asynchronously to allow scripts the opportunity to delay ready\n setTimeout( jQuery.ready, 1 );\n\n // Standards-based browsers support DOMContentLoaded\n } else if ( document.addEventListener ) {\n // Use the handy event callback\n document.addEventListener( \"DOMContentLoaded\", DOMContentLoaded, false );\n\n // A fallback to window.onload, that will always work\n window.addEventListener( \"load\", jQuery.ready, false );\n\n // If IE event model is used\n } else {\n // Ensure firing before onload, maybe late but safe also for iframes\n document.attachEvent( \"onreadystatechange\", DOMContentLoaded );\n\n // A fallback to window.onload, that will always work\n window.attachEvent( \"onload\", jQuery.ready );\n\n // If IE and not a frame\n // continually check to see if the document is ready\n var top = false;\n\n try {\n top = window.frameElement == null && document.documentElement;\n } catch(e) {}\n\n if ( top && top.doScroll ) {\n (function doScrollCheck() {\n if ( !jQuery.isReady ) {\n\n try {\n // Use the trick by Diego Perini\n // http://javascript.nwbox.com/IEContentLoaded/\n top.doScroll(\"left\");\n } catch(e) {\n return setTimeout( doScrollCheck, 50 );\n }\n\n // and execute any waiting functions\n jQuery.ready();\n }\n })();\n }\n }\n }\n return readyList.promise( obj );\n };\n\n jQuery.extend({\n // 表示ready方法是否正在执行,若正在执行,则将isReady设置为true\n isReady: false,\n\n // ready方法执行前需要等待的次数\n readyWait: 1,\n\n // hold或者释放ready方法,若参数为true则readyWait++,否则执行ready,传入参数为true\n holdReady: function( hold ) {\n if ( hold ) {\n jQuery.readyWait++;\n } else {\n jQuery.ready( true );\n }\n },\n\n // 当DOM加载完毕时开始执行ready\n ready: function( wait ) {\n\n // 若传入的参数为true,则--readyWait;否则判断isReady,即ready是否正在执行 \n if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n return;\n }\n\n // Remember that the DOM is ready\n jQuery.isReady = true;\n\n // 若readyWait-1后还是大于0,则返回,不执行ready。\n if ( wait !== true && --jQuery.readyWait > 0 ) {\n return;\n }\n\n // If there are functions bound, to execute\n readyList.resolveWith( document, [ jQuery ] );\n\n // 触发ready方法,然后解除绑定的ready方法。\n if ( jQuery.fn.triggerHandler ) {\n jQuery( document ).triggerHandler( \"ready\" );\n jQuery( document ).off( \"ready\" );\n }\n }\n });\n```\n根据以上代码可见,最终DOMContented事件执行的,其实是jQUery.ready()这个工具函数。\n(注意,jquery.ready()和jquery(document).raedy()不一样!!,前者是工具函数,后者是实例函数。)\n这里是通过定义一个DOMContentLoaded函数作为桥梁来执行jquery.ready()函数的,这样做的目的就是为了及时的remove掉document的DOMContentLoaded事件的引用。\n{% asset_img zongjie.png %}\n推荐好文: [何控制jquery的ready事件](http://www.xiabingbao.com/jquery/2015/06/27/jquery-holdready/)\n","slug":"DOMContentLoaded","published":1,"updated":"2016-06-01T06:25:10.999Z","layout":"post","photos":[],"link":"","_id":"citf9omaz0023skv7pdt82cbb"},{"title":"Array","date":"2016-07-02T04:56:18.000Z","comments":1,"_content":"# 数组\n<!--more-->\n## 数组的定义\n数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。\n```\nvar arr = ['a', 'b', 'c'];\n```\n上面代码中的`a`、`b`、`c`就构成一个数组,两端的方括号是数组的标志。`[]\na`是0号位置,b是1号位置,c是2号位置。\n\n除了在定义时赋值,数组也可以先定义后赋值。\n```\nvar arr = [];\n\narr[0] = 'a';\narr[1] = 'b';\narr[2] = 'c';\n```\n任何类型的数据,都可以放入数组。\n```\nvar arr = [\n {a: 1},\n [1, 2, 3],\n function() {return true;}\n];\n\narr[0] // Object {a: 1}\narr[1] // [1, 2, 3]\narr[2] // function (){return true;}\n```\n上面数组arr的3个成员依次是对象、数组、函数。\n如果数组的元素还是数组,就形成了多维数组。\n```\nvar a = [[1, 2], [3, 4]];\na[0][1] // 2\na[1][1] // 4\n```\n## 数组的本质\n本质上,数组属于一种特殊的对象。`typeof`运算符会返回数组的类型是`object`。\n```\ntypeof [1, 2, 3] // \"object\"\n```\n上面代码表明,`typeof`运算符认为数组的类型就是对象。\n\n数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。\n```\nvar arr = ['a', 'b', 'c'];\n\nObject.keys(arr)\n// [\"0\", \"1\", \"2\"]\n```\n上面代码中,`Object.keys`方法返回数组的所有键名。可以看到数组的键名就是整数0、1、2。\n\n由于数组成员的键名是固定的,因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。\n\nJavaScript语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。\n```\nvar arr = ['a', 'b', 'c'];\n\narr['0'] // 'a'\narr[0] // 'a'\n```\n上面代码分别用数值和字符串作为键名,结果都能读取数组。原因是数值键名被自动转为了字符串。\n\n需要注意的是,这一条在赋值时也成立。如果一个值可以被转换为整数,则以该值为键名,等于以对应的整数为键名。\n```\nvar a = [];\n\na['1000'] = 'abc';\na[1000] // 'abc'\n\na[1.00] = 6;\na[1] // 6\n```\n上一节说过,对象有两种读取成员的方法:“点”结构(`object.key`)和方括号结构(`object[key]`)。但是,对于数值的键名,不能使用点结构。\n```\nvar arr = [1, 2, 3];\narr.0 // SyntaxError\n```\n上面代码中,`arr.0`的写法不合法,因为单独的数值不能作为标识符(`identifier`)。所以,数组成员只能用方括号`arr[0]`表示(方括号是运算符,可以接受数值)。\n## length属性\n数组的`length`属性,返回数组的成员数量。\n```\n['a', 'b', 'c'].length // 3\n```\n\nJavaScript使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有4294967295个(2<sup>32</sup> - 1)个,也就是说`length`属性的最大值就是4294967295。\n数组的`length`属性与对象的`length`属性有区别,只要是数组,就一定有`length`属性,而对象不一定有。而且,数组的`length`属性是一个动态的值,等于键名中的最大整数加上`1`。\n```\nvar arr = ['a', 'b'];\narr.length // 2\n\narr[2] = 'c';\narr.length // 3\n\narr[9] = 'd';\narr.length // 10\n\narr[1000] = 'e';\narr.length // 1001\n```\n上面代码表示,数组的数字键不需要连续,`length`属性的值总是比最大的那个整数键大`1`。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。\n`length`属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到`length`设置的值。\n```\nvar arr = [ 'a', 'b', 'c' ];\narr.length // 3\n\narr.length = 2;\narr // [\"a\", \"b\"]\n```\n上面代码表示,当数组的`length`属性设为`2`(即最大的整数键只能是`1`)那么整数键`2`(值为c)就已经不在数组中了,被自动删除了。\n\n将数组清空的一个有效方法,就是将`length`属性设为`0`。\n```\nvar arr = [ 'a', 'b', 'c' ];\n\narr.length = 0;\narr // []\n```\n如果人为设置`length`大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位。\n```\nvar a = ['a'];\n\na.length = 3;\na[1] // undefined\n```\n上面代码表示,当`length`属性设为大于数组个数时,读取新增的位置都会返回`undefined`。\n\n如果人为设置`length`为不合法的值,JavaScript会报错。\n```\n// 设置负值\n[].length = -1\n// RangeError: Invalid array length\n\n// 数组元素个数大于等于2的32次方\n[].length = Math.pow(2,32)\n// RangeError: Invalid array length\n\n// 设置字符串\n[].length = 'abc'\n// RangeError: Invalid array length\n```\n值得注意的是,由于数组本质上是对象的一种,所以我们可以为数组添加属性,但是这不影响`length`属性的值。\n```\nvar a = [];\n\na['p'] = 'abc';\na.length // 0\n\na[2.1] = 'abc';\na.length // 0\n```\n上面代码将数组的键分别设为字符串和小数,结果都不影响`length`属性。因为,`length`属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以`length`属性保持为`0`。\n## 类似数组的对象\n在JavaScript中,有些对象被称为“`类似数组的对象`”(array-like object)。意思是,它们看上去很像数组,可以使用`length`属性,但是它们并不是数组,所以无法使用一些数组的方法。\n\n下面就是一个类似数组的对象。\n```\nvar obj = {\n 0: 'a',\n 1: 'b',\n 2: 'c',\n length: 3\n};\n\nobj[0] // 'a'\nobj[2] // 'c'\nobj.length // 3\n```\n上面代码的变量`obj`是一个对象,但是看上去跟数组很像。所以只要有数字键和`length`属性,就是一个类似数组的对象。当然,变量`obj`无法使用数组特有的一些方法,比如`pop`和`push`方法。而且,`length`属性不是动态值,不会随着成员的变化而变化。\n```\nvar obj = {\n length: 0\n};\nobj[3] = 'd';\nobj.length // 0\n```\n上面代码为对象`obj`添加了一个数字键,但是`length`属性没变。这就说明了`obj`不是数组。\n\n典型的类似数组的对象是函数的`arguments`对象,以及大多数`DOM`元素集,还有字符串。\n```\n// arguments对象\nfunction args() { return arguments }\nvar arrayLike = args('a', 'b');\n\narrayLike[0] // 'a'\narrayLike.length // 2\narrayLike instanceof Array // false\n\n// DOM元素集\nvar elts = document.getElementsByTagName('h3');\nelts.length // 3\nelts instanceof Array // false\n\n// 字符串\n'abc'[1] // 'b'\n'abc'.length // 3\n'abc' instanceof Array // false\n```\n数组的`slice`方法将类似数组的对象,变成真正的数组。\n```\nvar arr = Array.prototype.slice.call(arrayLike);\n```\n遍历类似数组的对象,可以采用`for`循环,也可以采用数组的`forEach`方法。\n```\n// for循环\nfunction logArgs() {\n for (var i = 0; i < arguments.length; i++) {\n console.log(i + '. ' + arguments[i]);\n }\n}\n\n// forEach方法\nfunction logArgs() {\n Array.prototype.forEach.call(arguments, function (elem, i) {\n console.log(i+'. '+elem);\n });\n}\n```\n由于字符串也是类似数组的对象,所以也可以用`Array.prototype.forEach.call`遍历。\n```\nArray.prototype.forEach.call('abc', function(chr) {\n console.log(chr);\n});\n// a\n// b\n// c\n```\n## in运算符\n检查某个键名是否存在的运算符`in`,适用于对象,也适用于数组。\n```\n2 in [ 'a', 'b', 'c' ] // true\n'2' in [ 'a', 'b', 'c' ] // true\n```\n上面代码表明,数组存在键名为`2`的键。由于键名都是字符串,所以数值`2`会自动转成字符串。\n## for…in循环和数组的遍历\n`for...in`循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。\n```\nvar a = [1, 2, 3];\n\nfor (var i in a) {\n console.log(a[i]);\n}\n// 1\n// 2\n// 3\n```\n但是,`for...in`不仅会遍历数组所有的数字键,还会遍历非数字键。\n```\nvar a = [1, 2, 3];\na.foo = true;\n\nfor (var key in a) {\n console.log(key);\n}\n// 0\n// 1\n// 2\n// foo\n```\n上面代码在遍历数组时,也遍历到了非整数键`foo`。所以,不推荐使用`for...in`遍历数组。\n\n数组的遍历可以考虑使用`for`循环或`while`循环。\n```\nvar a = [1, 2, 3];\n\n// for循环\nfor(var i = 0; i < a.length; i++) {\n console.log(a[i]);\n}\n\n// while循环\nvar i = 0;\nwhile (i < a.length) {\n console.log(a[i]);\n i++;\n}\n\nvar l = a.length;\nwhile (l--) {\n console.log(a[l]);\n}\n```\n上面代码是三种遍历数组的写法。最后一种写法是逆向遍历,即从最后一个元素向第一个元素遍历。\n\n数组的`forEach`方法,也可以用来遍历数组,详见《标准库》一章的`Array`对象部分。\n```\nvar colors = ['red', 'green', 'blue'];\ncolors.forEach(function (color) {\n console.log(color);\n});\n```\n## 数组的空位\n当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。\n```\nvar a = [1, , 1];\na.length // 3\n```\n上面代码表明,数组的空位不影响`length`属性。\n\n需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。\n```\nvar a = [1, 2, 3,];\n\na.length // 3\na // [1, 2, 3]\n```\n上面代码中,数组最后一个成员后面有一个逗号,这不影响`length`属性的值,与没有这个逗号时效果一样。\n\n数组的空位是可以读取的,返回`undefined`。\n```\nvar a = [, , ,];\na[1] // undefined\n```\n使用`delete`命令删除一个值,会形成空位。\n```\nvar a = [1, 2, 3];\n\ndelete a[1];\na[1] // undefined\n```\n`delete`命令不影响`length`属性。\n```\nvar a = [1, 2, 3];\ndelete a[1];\ndelete a[2];\na.length // 3\n```\n上面代码用`delete`命令删除了两个键,对`length`属性没有影响。也就是说,`length`属性不过滤空位。所以,使用`length`属性进行数组遍历,一定要非常小心。\n\n数组的某个位置是空位,与某个位置是`undefined`,是不一样的。如果是空位,使用数组的`forEach`方法、`for...in`结构、以及`Object.keys`方法进行遍历,空位都会被跳过。\n\n# 数组对象Array\n`Array`是JavaScript的内置对象,同时也是一个构造函数,可以用它生成新的数组。作为构造函数时,`Array`可以接受参数,但是不同的参数,会使得`Array`产生不同的行为。\n```\n// 无参数时,返回一个空数组\nnew Array() // []\n\n// 单个正整数参数,表示返回的新数组的长度\nnew Array(1) // [undefined × 1]\nnew Array(2) // [undefined x 2]\n\n// 单个非正整数参数(比如字符串、布尔值、对象等),\n// 则该参数是返回的新数组的成员\nnew Array('abc') // ['abc']\nnew Array([1]) // [Array[1]]\n\n// 多参数时,所有参数都是返回的新数组的成员\nnew Array(1, 2) // [1, 2]\n```\n从上面代码可以看到,Array作为构造函数,行为很不一致。因此,不建议使用它生成新数组,直接使用数组的字面量是更好的方法。\n```\n// bad\nvar arr = new Array(1, 2);\n\n// good\nvar arr = [1, 2];\n```\n另外,`Array`作为构造函数时,如果参数是一个正整数,返回的空数组虽然可以取到`length`属性,但是取不到键名。\n```\nArray(3).length // 3\n\nArray(3)[0] // undefined\nArray(3)[1] // undefined\nArray(3)[2] // undefined\n\n0 in Array(3) // false\n1 in Array(3) // false\n2 in Array(3) // false\n```\n上面代码中,`Array(3)`是一个长度为`3`的空数组。虽然可以取到每个位置的键值,但是所有的键名都取不到,实际上是产生了3个空位。JavaScript语言的设计规格,就是这么规定的,虽然不是一个大问题,但是还是必须小心。这也是不推荐使用Array构造函数的一个理由。\n\n## Array对象的静态方法\n### isArray方法\n`Array.isArray`方法用来判断一个值是否为数组。它可以弥补typeof运算符的不足。\n```\nvar a = [1, 2, 3];\n\ntypeof a // \"object\"\nArray.isArray(a) // true\n```\n上面代码表示,`typeof`运算符只能显示数组的类型是`Object`,而`Array.isArray`方法可以对数组返回`true`。\n> Polyfill\n> \n ```\n if (!Array.isArray) {\n Array.isArray = function(arg) {\n return Object.prototype.toString.call(arg) === '[object Array]';\n };\n }\n ```\n## Array实例的方法\n以下这些`Array`实例对象的方法,都是数组实例才能使用。如果不想创建实例,只是想单纯调用这些方法,可以写成`[].method.call(调用对象,参数)` 的形式,或者`Array.prototype.method.call(调用对象,参数)`的形式。\n### valueOf方法,toString方法\n`valueOf`方法返回数组本身。\n```\nvar a = [1, 2, 3];\na.valueOf() // [1, 2, 3]\n```\n`toString`方法返回数组的字符串形式。\n```\nvar a = [1, 2, 3];\na.toString() // \"1,2,3\"\n\nvar a = [1, 2, 3, [4, 5, 6]];\na.toString() // \"1,2,3,4,5,6\"\n```\n### push(),pop()\n`push`方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。`注意,该方法会改变原数组`。\n```\nvar a = [];\n\na.push(1) // 1\na.push('a') // 2\na.push(true, {}) // 4\na // [1, 'a', true, {}]\n```\n上面代码使用`push`方法,先后往数组中添加了四个成员。如果需要合并两个数组,可以这样写。\n```\nvar a = [1, 2, 3];\nvar b = [4, 5, 6];\n\nArray.prototype.push.apply(a, b)\n// 或者\na.push.apply(a, b)\n\n// 上面两种写法等同于\na.push(4, 5, 6)\n\na // [1, 2, 3, 4, 5, 6]\n```\n`push`方法还可以用于向对象添加元素,添加后的对象变成类似数组的对象,即新加入元素的键对应数组的索引,并且对象有一个`length`属性。\n```\nvar a = {a: 1};\n\n[].push.call(a, 2);\na // {a:1, 0:2, length: 1}\n\n[].push.call(a, [3]);\na // {a:1, 0:2, 1:[3], length: 2}\n```\n`pop`方法用于删除数组的最后一个元素,并返回该元素。`注意,该方法会改变原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.pop() // 'c'\na // ['a', 'b']\n```\n对空数组使用`pop`方法,不会报错,而是返回`undefined`。\n```\n[].pop() // undefined\n```\n`push`和`pop`结合使用,就构成了“后进先出”的栈结构(stack)。\n\n### join(),concat()\n`join`方法以参数作为分隔符,将所有数组成员组成一个字符串返回。如果不提供参数,默认用逗号分隔。\n```\nvar a = [1, 2, 3, 4];\n\na.join(' ') // '1 2 3 4'\na.join(' | ') // \"1 | 2 | 3 | 4\"\na.join() // \"1,2,3,4\"\n```\n通过`call`方法,`join`方法(即`Array.prototype.join`)也可以用于字符串。\n```\nArray.prototype.join.call('hello', '-')\n// \"h-e-l-l-o\"\n```\n`concat`方法用于多个数组的合并。它将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不变。\n```\n['hello'].concat(['world'])\n// [\"hello\", \"world\"]\n\n['hello'].concat(['world'], ['!'])\n// [\"hello\", \"world\", \"!\"]\n```\n除了接受数组作为参数,`concat`也可以接受其他类型的值作为参数。它们会作为新的元素,添加数组尾部。\n```\n[1, 2, 3].concat(4, 5, 6)\n// [1, 2, 3, 4, 5, 6]\n\n[1, 2, 3].concat(4, [5, 6])\n```\n如果不提供参数,`concat`方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是如果数组成员包括复合类型的值(比如对象),则新数组拷贝的是该值的引用。\n```\nvar obj = { a:1 };\nvar oldArray = [obj];\n\nvar newArray = oldArray.concat();\n\nobj.a = 2;\nnewArray[0].a // 2\n```\n上面代码中,原数组包含一个对象,`concat`方法生成的新数组包含这个对象的引用。所以,改变原对象以后,新数组跟着改变。事实上,只要原数组的成员中包含对象,`concat`方法不管有没有参数,总是返回该对象的引用。\n`concat`方法也可以用于将对象合并为数组,但是必须借助`call`方法。\n```\n[].concat.call({ a: 1 }, { b: 2 })\n// [{ a: 1 }, { b: 2 }]\n\n[].concat.call({ a: 1 }, [2])\n// [{a:1}, 2]\n\n// 等同于\n\n[2].concat({a:1})\nArray.prototype.concat.call({ a: 1 }, { b: 2 })\n```\n### shift(),unshift()\n`shift`方法用于删除数组的第一个元素,并返回该元素。注意,`该方法会改变原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.shift() // 'a'\na // ['b', 'c']\n```\n`shift`方法可以遍历并清空一个数组。\n```\nvar list = [1, 2, 3, 4, 5, 6];\nvar item;\n\nwhile (item = list.shift()) {\n console.log(item);\n}\n\nlist // []\n```\n`push`和`shift`结合使用,就构成了“先进先出”的队列结构(queue)。\n\n`unshift`方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,`该方法会改变原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.unshift('x'); // 4\na // ['x', 'a', 'b', 'c']\n```\n### reverse()\n`reverse`方法用于颠倒数组中元素的顺序,使用这个方法以后,返回`改变后的原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.reverse() // [\"c\", \"b\", \"a\"] \na // [\"c\", \"b\", \"a\"] \n```\n### slice()\n`slice`方法用于提取原数组的一部分,返回一个新数组,`原数组不变`。\n\n它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内,`包含头不包含尾`)。如果省略第二个参数,则一直返回到原数组的最后一个成员。\n```\n// 格式\narr.slice(start_index, upto_index);\n\n// 用法\nvar a = ['a', 'b', 'c'];\n\na.slice(0) // [\"a\", \"b\", \"c\"]\na.slice(1) // [\"b\", \"c\"]\na.slice(1, 2) // [\"b\"]\na.slice(2, 6) // [\"c\"]\n```\n如果`slice`方法的参数是负数,则表示倒数计算的字符串位置。\n```\nvar a = ['a', 'b', 'c'];\na.slice(-2) // [\"b\", \"c\"] 相当于 a.slice(a.length + (-2))\na.slice(-2, -1) // [\"b\"] 相当于 a.slice(a.length + (-2), a.length + (-1))\n```\n如果参数值大于数组成员的个数,或者第二个参数小于第一个参数,则返回空数组。\n```\nvar a = ['a', 'b', 'c'];\na.slice(4) // []\na.slice(2, 1) // []\n```\n`slice`方法的一个重要应用,是将类似数组的对象转为真正的数组。\n```\nArray.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })\n// ['a', 'b']\n\nArray.prototype.slice.call(document.querySelectorAll(\"div\"));\nArray.prototype.slice.call(arguments);\n```\n上面代码的参数都不是数组,但是通过`call`方法,在它们上面调用`slice`方法,就可以把它们转为真正的数组。\n\n### splice()\n`splice`方法用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。\n`splice`的第一个参数是删除的起始位置,第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。\n```\n// 格式\narr.splice(index, count_to_remove, addElement1, addElement2, ...);\n\n// 用法\nvar a = ['a', 'b', 'c', 'd', 'e', 'f'];\na.splice(4, 2) // [\"e\", \"f\"]\na // [\"a\", \"b\", \"c\", \"d\"]\n```\n上面代码从原数组位置4开始,删除了两个数组成员。\n```\nvar a = ['a', 'b', 'c', 'd', 'e', 'f'];\na.splice(4, 2, 1, 2) // [\"e\", \"f\"]\na // [\"a\", \"b\", \"c\", \"d\", 1, 2]\n```\n上面代码除了删除成员,还插入了两个新成员。\n\n如果只是单纯地插入元素,`splice`方法的第二个参数可以设为0。\n```\nvar a = [1, 1, 1];\n\na.splice(1, 0, 2) // []\na // [1, 2, 1, 1]\n```\n如果只提供第一个参数,则实际上等同于将原数组在指定位置拆分成两个数组。\n```\nvar a = [1, 2, 3, 4];\na.splice(2) // [3, 4]\na // [1, 2]\n```\n### sort()\n`sort`方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。\n```\n['d', 'c', 'b', 'a'].sort()\n// ['a', 'b', 'c', 'd']\n\n[4, 3, 2, 1].sort()\n// [1, 2, 3, 4]\n\n[11, 101].sort()\n// [101, 11]\n\n[10111,1101,111].sort()\n// [10111, 1101, 111]\n```\n上面代码的最后两个例子,需要特殊注意。sort方法不是按照大小排序,而是按照对应字符串的字典顺序排序,所以101排在11的前面。\n\n如果想让`sort`方法按照自定义方式排序,可以传入一个函数作为参数,表示按照自定义方法进行排序。\n该函数本身又接受两个参数,表示进行比较的两个元素。如果返回值大于0,表示第一个元素排在第二个元素后面;其他情况下,都是第一个元素排在第二个元素前面。\n```\n[10111,1101,111].sort(function (a,b){\n return a - b;\n})\n// [111, 1101, 10111]\n\n[\n { name: \"张三\", age: 30 },\n { name: \"李四\", age: 24 },\n { name: \"王五\", age: 28 }\n].sort(function(o1, o2) {\n return o1.age - o2.age;\n})\n// [\n// { name: \"李四\", age: 24 },\n// { name: \"王五\", age: 28 },\n// { name: \"张三\", age: 30 }\n// ]\n```\n### ECMAScript 5 新加入的数组方法\nECMAScript 5新增了9个数组实例的方法,分别是`map`、`forEach`、`filter`、`every`、`some`、`reduce`、`reduceRight`、`indexOf`和`lastIndexOf`。其中,前7个与函数式(functional)操作有关。\n这些方法可以在数组上使用,也可以在字符串和类似数组的对象上使用,这是它们不同于传统数组方法的一个地方。\n在用法上,这些方法的参数是一个函数,这个作为参数的函数本身又接受三个参数:数组的当前元素elem、该元素的位置index和整个数组arr(详见下面的实例)。另外,上下文对象(context)可以作为第二个参数,传入`forEach()`, `every()`, `some()`, `filter()`, `map()`方法,用来绑定函数运行时的上下文。\n对于不支持这些方法的老式浏览器(主要是IE 8及以下版本),可以使用函数库[es5-shim](https://github.com/es-shims/es5-shim),或者[Underscore](http://underscorejs.org/)和[Lo-Dash](https://lodash.com/docs)。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n转自:[阮一峰教程](http://javascript.ruanyifeng.com/grammar/array.html)","source":"_posts/Array-2016-07-02.md","raw":"title: Array\ndate: 2016-07-02 12:56:18\ntags:\n- jQuery\n- JavaScript\ncomments: true\ncategories:\n- JavaScript\n---\n# 数组\n<!--more-->\n## 数组的定义\n数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。\n```\nvar arr = ['a', 'b', 'c'];\n```\n上面代码中的`a`、`b`、`c`就构成一个数组,两端的方括号是数组的标志。`[]\na`是0号位置,b是1号位置,c是2号位置。\n\n除了在定义时赋值,数组也可以先定义后赋值。\n```\nvar arr = [];\n\narr[0] = 'a';\narr[1] = 'b';\narr[2] = 'c';\n```\n任何类型的数据,都可以放入数组。\n```\nvar arr = [\n {a: 1},\n [1, 2, 3],\n function() {return true;}\n];\n\narr[0] // Object {a: 1}\narr[1] // [1, 2, 3]\narr[2] // function (){return true;}\n```\n上面数组arr的3个成员依次是对象、数组、函数。\n如果数组的元素还是数组,就形成了多维数组。\n```\nvar a = [[1, 2], [3, 4]];\na[0][1] // 2\na[1][1] // 4\n```\n## 数组的本质\n本质上,数组属于一种特殊的对象。`typeof`运算符会返回数组的类型是`object`。\n```\ntypeof [1, 2, 3] // \"object\"\n```\n上面代码表明,`typeof`运算符认为数组的类型就是对象。\n\n数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。\n```\nvar arr = ['a', 'b', 'c'];\n\nObject.keys(arr)\n// [\"0\", \"1\", \"2\"]\n```\n上面代码中,`Object.keys`方法返回数组的所有键名。可以看到数组的键名就是整数0、1、2。\n\n由于数组成员的键名是固定的,因此数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。\n\nJavaScript语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。\n```\nvar arr = ['a', 'b', 'c'];\n\narr['0'] // 'a'\narr[0] // 'a'\n```\n上面代码分别用数值和字符串作为键名,结果都能读取数组。原因是数值键名被自动转为了字符串。\n\n需要注意的是,这一条在赋值时也成立。如果一个值可以被转换为整数,则以该值为键名,等于以对应的整数为键名。\n```\nvar a = [];\n\na['1000'] = 'abc';\na[1000] // 'abc'\n\na[1.00] = 6;\na[1] // 6\n```\n上一节说过,对象有两种读取成员的方法:“点”结构(`object.key`)和方括号结构(`object[key]`)。但是,对于数值的键名,不能使用点结构。\n```\nvar arr = [1, 2, 3];\narr.0 // SyntaxError\n```\n上面代码中,`arr.0`的写法不合法,因为单独的数值不能作为标识符(`identifier`)。所以,数组成员只能用方括号`arr[0]`表示(方括号是运算符,可以接受数值)。\n## length属性\n数组的`length`属性,返回数组的成员数量。\n```\n['a', 'b', 'c'].length // 3\n```\n\nJavaScript使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有4294967295个(2<sup>32</sup> - 1)个,也就是说`length`属性的最大值就是4294967295。\n数组的`length`属性与对象的`length`属性有区别,只要是数组,就一定有`length`属性,而对象不一定有。而且,数组的`length`属性是一个动态的值,等于键名中的最大整数加上`1`。\n```\nvar arr = ['a', 'b'];\narr.length // 2\n\narr[2] = 'c';\narr.length // 3\n\narr[9] = 'd';\narr.length // 10\n\narr[1000] = 'e';\narr.length // 1001\n```\n上面代码表示,数组的数字键不需要连续,`length`属性的值总是比最大的那个整数键大`1`。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。\n`length`属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到`length`设置的值。\n```\nvar arr = [ 'a', 'b', 'c' ];\narr.length // 3\n\narr.length = 2;\narr // [\"a\", \"b\"]\n```\n上面代码表示,当数组的`length`属性设为`2`(即最大的整数键只能是`1`)那么整数键`2`(值为c)就已经不在数组中了,被自动删除了。\n\n将数组清空的一个有效方法,就是将`length`属性设为`0`。\n```\nvar arr = [ 'a', 'b', 'c' ];\n\narr.length = 0;\narr // []\n```\n如果人为设置`length`大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位。\n```\nvar a = ['a'];\n\na.length = 3;\na[1] // undefined\n```\n上面代码表示,当`length`属性设为大于数组个数时,读取新增的位置都会返回`undefined`。\n\n如果人为设置`length`为不合法的值,JavaScript会报错。\n```\n// 设置负值\n[].length = -1\n// RangeError: Invalid array length\n\n// 数组元素个数大于等于2的32次方\n[].length = Math.pow(2,32)\n// RangeError: Invalid array length\n\n// 设置字符串\n[].length = 'abc'\n// RangeError: Invalid array length\n```\n值得注意的是,由于数组本质上是对象的一种,所以我们可以为数组添加属性,但是这不影响`length`属性的值。\n```\nvar a = [];\n\na['p'] = 'abc';\na.length // 0\n\na[2.1] = 'abc';\na.length // 0\n```\n上面代码将数组的键分别设为字符串和小数,结果都不影响`length`属性。因为,`length`属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以`length`属性保持为`0`。\n## 类似数组的对象\n在JavaScript中,有些对象被称为“`类似数组的对象`”(array-like object)。意思是,它们看上去很像数组,可以使用`length`属性,但是它们并不是数组,所以无法使用一些数组的方法。\n\n下面就是一个类似数组的对象。\n```\nvar obj = {\n 0: 'a',\n 1: 'b',\n 2: 'c',\n length: 3\n};\n\nobj[0] // 'a'\nobj[2] // 'c'\nobj.length // 3\n```\n上面代码的变量`obj`是一个对象,但是看上去跟数组很像。所以只要有数字键和`length`属性,就是一个类似数组的对象。当然,变量`obj`无法使用数组特有的一些方法,比如`pop`和`push`方法。而且,`length`属性不是动态值,不会随着成员的变化而变化。\n```\nvar obj = {\n length: 0\n};\nobj[3] = 'd';\nobj.length // 0\n```\n上面代码为对象`obj`添加了一个数字键,但是`length`属性没变。这就说明了`obj`不是数组。\n\n典型的类似数组的对象是函数的`arguments`对象,以及大多数`DOM`元素集,还有字符串。\n```\n// arguments对象\nfunction args() { return arguments }\nvar arrayLike = args('a', 'b');\n\narrayLike[0] // 'a'\narrayLike.length // 2\narrayLike instanceof Array // false\n\n// DOM元素集\nvar elts = document.getElementsByTagName('h3');\nelts.length // 3\nelts instanceof Array // false\n\n// 字符串\n'abc'[1] // 'b'\n'abc'.length // 3\n'abc' instanceof Array // false\n```\n数组的`slice`方法将类似数组的对象,变成真正的数组。\n```\nvar arr = Array.prototype.slice.call(arrayLike);\n```\n遍历类似数组的对象,可以采用`for`循环,也可以采用数组的`forEach`方法。\n```\n// for循环\nfunction logArgs() {\n for (var i = 0; i < arguments.length; i++) {\n console.log(i + '. ' + arguments[i]);\n }\n}\n\n// forEach方法\nfunction logArgs() {\n Array.prototype.forEach.call(arguments, function (elem, i) {\n console.log(i+'. '+elem);\n });\n}\n```\n由于字符串也是类似数组的对象,所以也可以用`Array.prototype.forEach.call`遍历。\n```\nArray.prototype.forEach.call('abc', function(chr) {\n console.log(chr);\n});\n// a\n// b\n// c\n```\n## in运算符\n检查某个键名是否存在的运算符`in`,适用于对象,也适用于数组。\n```\n2 in [ 'a', 'b', 'c' ] // true\n'2' in [ 'a', 'b', 'c' ] // true\n```\n上面代码表明,数组存在键名为`2`的键。由于键名都是字符串,所以数值`2`会自动转成字符串。\n## for…in循环和数组的遍历\n`for...in`循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。\n```\nvar a = [1, 2, 3];\n\nfor (var i in a) {\n console.log(a[i]);\n}\n// 1\n// 2\n// 3\n```\n但是,`for...in`不仅会遍历数组所有的数字键,还会遍历非数字键。\n```\nvar a = [1, 2, 3];\na.foo = true;\n\nfor (var key in a) {\n console.log(key);\n}\n// 0\n// 1\n// 2\n// foo\n```\n上面代码在遍历数组时,也遍历到了非整数键`foo`。所以,不推荐使用`for...in`遍历数组。\n\n数组的遍历可以考虑使用`for`循环或`while`循环。\n```\nvar a = [1, 2, 3];\n\n// for循环\nfor(var i = 0; i < a.length; i++) {\n console.log(a[i]);\n}\n\n// while循环\nvar i = 0;\nwhile (i < a.length) {\n console.log(a[i]);\n i++;\n}\n\nvar l = a.length;\nwhile (l--) {\n console.log(a[l]);\n}\n```\n上面代码是三种遍历数组的写法。最后一种写法是逆向遍历,即从最后一个元素向第一个元素遍历。\n\n数组的`forEach`方法,也可以用来遍历数组,详见《标准库》一章的`Array`对象部分。\n```\nvar colors = ['red', 'green', 'blue'];\ncolors.forEach(function (color) {\n console.log(color);\n});\n```\n## 数组的空位\n当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。\n```\nvar a = [1, , 1];\na.length // 3\n```\n上面代码表明,数组的空位不影响`length`属性。\n\n需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。\n```\nvar a = [1, 2, 3,];\n\na.length // 3\na // [1, 2, 3]\n```\n上面代码中,数组最后一个成员后面有一个逗号,这不影响`length`属性的值,与没有这个逗号时效果一样。\n\n数组的空位是可以读取的,返回`undefined`。\n```\nvar a = [, , ,];\na[1] // undefined\n```\n使用`delete`命令删除一个值,会形成空位。\n```\nvar a = [1, 2, 3];\n\ndelete a[1];\na[1] // undefined\n```\n`delete`命令不影响`length`属性。\n```\nvar a = [1, 2, 3];\ndelete a[1];\ndelete a[2];\na.length // 3\n```\n上面代码用`delete`命令删除了两个键,对`length`属性没有影响。也就是说,`length`属性不过滤空位。所以,使用`length`属性进行数组遍历,一定要非常小心。\n\n数组的某个位置是空位,与某个位置是`undefined`,是不一样的。如果是空位,使用数组的`forEach`方法、`for...in`结构、以及`Object.keys`方法进行遍历,空位都会被跳过。\n\n# 数组对象Array\n`Array`是JavaScript的内置对象,同时也是一个构造函数,可以用它生成新的数组。作为构造函数时,`Array`可以接受参数,但是不同的参数,会使得`Array`产生不同的行为。\n```\n// 无参数时,返回一个空数组\nnew Array() // []\n\n// 单个正整数参数,表示返回的新数组的长度\nnew Array(1) // [undefined × 1]\nnew Array(2) // [undefined x 2]\n\n// 单个非正整数参数(比如字符串、布尔值、对象等),\n// 则该参数是返回的新数组的成员\nnew Array('abc') // ['abc']\nnew Array([1]) // [Array[1]]\n\n// 多参数时,所有参数都是返回的新数组的成员\nnew Array(1, 2) // [1, 2]\n```\n从上面代码可以看到,Array作为构造函数,行为很不一致。因此,不建议使用它生成新数组,直接使用数组的字面量是更好的方法。\n```\n// bad\nvar arr = new Array(1, 2);\n\n// good\nvar arr = [1, 2];\n```\n另外,`Array`作为构造函数时,如果参数是一个正整数,返回的空数组虽然可以取到`length`属性,但是取不到键名。\n```\nArray(3).length // 3\n\nArray(3)[0] // undefined\nArray(3)[1] // undefined\nArray(3)[2] // undefined\n\n0 in Array(3) // false\n1 in Array(3) // false\n2 in Array(3) // false\n```\n上面代码中,`Array(3)`是一个长度为`3`的空数组。虽然可以取到每个位置的键值,但是所有的键名都取不到,实际上是产生了3个空位。JavaScript语言的设计规格,就是这么规定的,虽然不是一个大问题,但是还是必须小心。这也是不推荐使用Array构造函数的一个理由。\n\n## Array对象的静态方法\n### isArray方法\n`Array.isArray`方法用来判断一个值是否为数组。它可以弥补typeof运算符的不足。\n```\nvar a = [1, 2, 3];\n\ntypeof a // \"object\"\nArray.isArray(a) // true\n```\n上面代码表示,`typeof`运算符只能显示数组的类型是`Object`,而`Array.isArray`方法可以对数组返回`true`。\n> Polyfill\n> \n ```\n if (!Array.isArray) {\n Array.isArray = function(arg) {\n return Object.prototype.toString.call(arg) === '[object Array]';\n };\n }\n ```\n## Array实例的方法\n以下这些`Array`实例对象的方法,都是数组实例才能使用。如果不想创建实例,只是想单纯调用这些方法,可以写成`[].method.call(调用对象,参数)` 的形式,或者`Array.prototype.method.call(调用对象,参数)`的形式。\n### valueOf方法,toString方法\n`valueOf`方法返回数组本身。\n```\nvar a = [1, 2, 3];\na.valueOf() // [1, 2, 3]\n```\n`toString`方法返回数组的字符串形式。\n```\nvar a = [1, 2, 3];\na.toString() // \"1,2,3\"\n\nvar a = [1, 2, 3, [4, 5, 6]];\na.toString() // \"1,2,3,4,5,6\"\n```\n### push(),pop()\n`push`方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。`注意,该方法会改变原数组`。\n```\nvar a = [];\n\na.push(1) // 1\na.push('a') // 2\na.push(true, {}) // 4\na // [1, 'a', true, {}]\n```\n上面代码使用`push`方法,先后往数组中添加了四个成员。如果需要合并两个数组,可以这样写。\n```\nvar a = [1, 2, 3];\nvar b = [4, 5, 6];\n\nArray.prototype.push.apply(a, b)\n// 或者\na.push.apply(a, b)\n\n// 上面两种写法等同于\na.push(4, 5, 6)\n\na // [1, 2, 3, 4, 5, 6]\n```\n`push`方法还可以用于向对象添加元素,添加后的对象变成类似数组的对象,即新加入元素的键对应数组的索引,并且对象有一个`length`属性。\n```\nvar a = {a: 1};\n\n[].push.call(a, 2);\na // {a:1, 0:2, length: 1}\n\n[].push.call(a, [3]);\na // {a:1, 0:2, 1:[3], length: 2}\n```\n`pop`方法用于删除数组的最后一个元素,并返回该元素。`注意,该方法会改变原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.pop() // 'c'\na // ['a', 'b']\n```\n对空数组使用`pop`方法,不会报错,而是返回`undefined`。\n```\n[].pop() // undefined\n```\n`push`和`pop`结合使用,就构成了“后进先出”的栈结构(stack)。\n\n### join(),concat()\n`join`方法以参数作为分隔符,将所有数组成员组成一个字符串返回。如果不提供参数,默认用逗号分隔。\n```\nvar a = [1, 2, 3, 4];\n\na.join(' ') // '1 2 3 4'\na.join(' | ') // \"1 | 2 | 3 | 4\"\na.join() // \"1,2,3,4\"\n```\n通过`call`方法,`join`方法(即`Array.prototype.join`)也可以用于字符串。\n```\nArray.prototype.join.call('hello', '-')\n// \"h-e-l-l-o\"\n```\n`concat`方法用于多个数组的合并。它将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不变。\n```\n['hello'].concat(['world'])\n// [\"hello\", \"world\"]\n\n['hello'].concat(['world'], ['!'])\n// [\"hello\", \"world\", \"!\"]\n```\n除了接受数组作为参数,`concat`也可以接受其他类型的值作为参数。它们会作为新的元素,添加数组尾部。\n```\n[1, 2, 3].concat(4, 5, 6)\n// [1, 2, 3, 4, 5, 6]\n\n[1, 2, 3].concat(4, [5, 6])\n```\n如果不提供参数,`concat`方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是如果数组成员包括复合类型的值(比如对象),则新数组拷贝的是该值的引用。\n```\nvar obj = { a:1 };\nvar oldArray = [obj];\n\nvar newArray = oldArray.concat();\n\nobj.a = 2;\nnewArray[0].a // 2\n```\n上面代码中,原数组包含一个对象,`concat`方法生成的新数组包含这个对象的引用。所以,改变原对象以后,新数组跟着改变。事实上,只要原数组的成员中包含对象,`concat`方法不管有没有参数,总是返回该对象的引用。\n`concat`方法也可以用于将对象合并为数组,但是必须借助`call`方法。\n```\n[].concat.call({ a: 1 }, { b: 2 })\n// [{ a: 1 }, { b: 2 }]\n\n[].concat.call({ a: 1 }, [2])\n// [{a:1}, 2]\n\n// 等同于\n\n[2].concat({a:1})\nArray.prototype.concat.call({ a: 1 }, { b: 2 })\n```\n### shift(),unshift()\n`shift`方法用于删除数组的第一个元素,并返回该元素。注意,`该方法会改变原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.shift() // 'a'\na // ['b', 'c']\n```\n`shift`方法可以遍历并清空一个数组。\n```\nvar list = [1, 2, 3, 4, 5, 6];\nvar item;\n\nwhile (item = list.shift()) {\n console.log(item);\n}\n\nlist // []\n```\n`push`和`shift`结合使用,就构成了“先进先出”的队列结构(queue)。\n\n`unshift`方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,`该方法会改变原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.unshift('x'); // 4\na // ['x', 'a', 'b', 'c']\n```\n### reverse()\n`reverse`方法用于颠倒数组中元素的顺序,使用这个方法以后,返回`改变后的原数组`。\n```\nvar a = ['a', 'b', 'c'];\n\na.reverse() // [\"c\", \"b\", \"a\"] \na // [\"c\", \"b\", \"a\"] \n```\n### slice()\n`slice`方法用于提取原数组的一部分,返回一个新数组,`原数组不变`。\n\n它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内,`包含头不包含尾`)。如果省略第二个参数,则一直返回到原数组的最后一个成员。\n```\n// 格式\narr.slice(start_index, upto_index);\n\n// 用法\nvar a = ['a', 'b', 'c'];\n\na.slice(0) // [\"a\", \"b\", \"c\"]\na.slice(1) // [\"b\", \"c\"]\na.slice(1, 2) // [\"b\"]\na.slice(2, 6) // [\"c\"]\n```\n如果`slice`方法的参数是负数,则表示倒数计算的字符串位置。\n```\nvar a = ['a', 'b', 'c'];\na.slice(-2) // [\"b\", \"c\"] 相当于 a.slice(a.length + (-2))\na.slice(-2, -1) // [\"b\"] 相当于 a.slice(a.length + (-2), a.length + (-1))\n```\n如果参数值大于数组成员的个数,或者第二个参数小于第一个参数,则返回空数组。\n```\nvar a = ['a', 'b', 'c'];\na.slice(4) // []\na.slice(2, 1) // []\n```\n`slice`方法的一个重要应用,是将类似数组的对象转为真正的数组。\n```\nArray.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })\n// ['a', 'b']\n\nArray.prototype.slice.call(document.querySelectorAll(\"div\"));\nArray.prototype.slice.call(arguments);\n```\n上面代码的参数都不是数组,但是通过`call`方法,在它们上面调用`slice`方法,就可以把它们转为真正的数组。\n\n### splice()\n`splice`方法用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。\n`splice`的第一个参数是删除的起始位置,第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。\n```\n// 格式\narr.splice(index, count_to_remove, addElement1, addElement2, ...);\n\n// 用法\nvar a = ['a', 'b', 'c', 'd', 'e', 'f'];\na.splice(4, 2) // [\"e\", \"f\"]\na // [\"a\", \"b\", \"c\", \"d\"]\n```\n上面代码从原数组位置4开始,删除了两个数组成员。\n```\nvar a = ['a', 'b', 'c', 'd', 'e', 'f'];\na.splice(4, 2, 1, 2) // [\"e\", \"f\"]\na // [\"a\", \"b\", \"c\", \"d\", 1, 2]\n```\n上面代码除了删除成员,还插入了两个新成员。\n\n如果只是单纯地插入元素,`splice`方法的第二个参数可以设为0。\n```\nvar a = [1, 1, 1];\n\na.splice(1, 0, 2) // []\na // [1, 2, 1, 1]\n```\n如果只提供第一个参数,则实际上等同于将原数组在指定位置拆分成两个数组。\n```\nvar a = [1, 2, 3, 4];\na.splice(2) // [3, 4]\na // [1, 2]\n```\n### sort()\n`sort`方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。\n```\n['d', 'c', 'b', 'a'].sort()\n// ['a', 'b', 'c', 'd']\n\n[4, 3, 2, 1].sort()\n// [1, 2, 3, 4]\n\n[11, 101].sort()\n// [101, 11]\n\n[10111,1101,111].sort()\n// [10111, 1101, 111]\n```\n上面代码的最后两个例子,需要特殊注意。sort方法不是按照大小排序,而是按照对应字符串的字典顺序排序,所以101排在11的前面。\n\n如果想让`sort`方法按照自定义方式排序,可以传入一个函数作为参数,表示按照自定义方法进行排序。\n该函数本身又接受两个参数,表示进行比较的两个元素。如果返回值大于0,表示第一个元素排在第二个元素后面;其他情况下,都是第一个元素排在第二个元素前面。\n```\n[10111,1101,111].sort(function (a,b){\n return a - b;\n})\n// [111, 1101, 10111]\n\n[\n { name: \"张三\", age: 30 },\n { name: \"李四\", age: 24 },\n { name: \"王五\", age: 28 }\n].sort(function(o1, o2) {\n return o1.age - o2.age;\n})\n// [\n// { name: \"李四\", age: 24 },\n// { name: \"王五\", age: 28 },\n// { name: \"张三\", age: 30 }\n// ]\n```\n### ECMAScript 5 新加入的数组方法\nECMAScript 5新增了9个数组实例的方法,分别是`map`、`forEach`、`filter`、`every`、`some`、`reduce`、`reduceRight`、`indexOf`和`lastIndexOf`。其中,前7个与函数式(functional)操作有关。\n这些方法可以在数组上使用,也可以在字符串和类似数组的对象上使用,这是它们不同于传统数组方法的一个地方。\n在用法上,这些方法的参数是一个函数,这个作为参数的函数本身又接受三个参数:数组的当前元素elem、该元素的位置index和整个数组arr(详见下面的实例)。另外,上下文对象(context)可以作为第二个参数,传入`forEach()`, `every()`, `some()`, `filter()`, `map()`方法,用来绑定函数运行时的上下文。\n对于不支持这些方法的老式浏览器(主要是IE 8及以下版本),可以使用函数库[es5-shim](https://github.com/es-shims/es5-shim),或者[Underscore](http://underscorejs.org/)和[Lo-Dash](https://lodash.com/docs)。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n转自:[阮一峰教程](http://javascript.ruanyifeng.com/grammar/array.html)","slug":"Array","published":1,"updated":"2016-07-18T12:30:36.856Z","layout":"post","photos":[],"link":"","_id":"citf9ombp0027skv7pi7sg23v"},{"title":"第三章 基本概念","date":"2016-01-20T01:38:23.000Z","comments":1,"_content":"# 语法\n## 区分大小写\nECMAScript 中的一切(变量、函数名和操作符)都区分大小写。\n<!--more-->\n## 标示符\n所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。\n规则:\n* 第一个字符必须是一个字母、下划线`_`或一个美元符号`$`\n* 其他字符可以是字母、下划线、美元符号或数字。标识符中的字母包含扩展的ASCll或Unicode字母字符(如À和Æ);\n\n## 严格模式 `\"use strict;\"`\n\n[严格模式](http://www.yangshengdonghome.com/2016/01/21/%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/)\n\n\n## 语句\n\n* 一个语句由一个或多个表达式、关键字或运算符(符号)组成。通常,在一个行上书写一个语句,但可在两个行或多个行上书写一个语句。此外,在同一个行上书写两个或多个语句,用分号分隔。通常,每个新行都开始一个新语句。最好是显式终止您的语句。可使用分号 (;) 做到这一点,分号是 JavaScript 语句的终止字符。\n* 由括号 ({}) 包围的一组 JavaScript 语句称为一个块。组织在一个块中的语句通常可以视为一个语句,比如`var a = function (){};`和对象字面量(`a = {};`),这两个结尾的也需要加分号,其他的大括号情况(for循环、ifelse判断),可以不加分号。\n\n ```javascript\n abc={}bbc=function(){}//报错 Uncaught SyntaxError: Unexpected identifier\n abc=function(){}bbc=function(){}//报错 Uncaught SyntaxError: Unexpected identifier\n if(a==1){}bbc=function(){}// 正常\n for(var a = 1;a<10;a++){}bbc=function(){}//正常\n ```\n### 语句和表达式的区别\n\n ```javascript\n var a = 1 + 3;\n ```\n这条语句先用var命令,声明了变量a,然后将1 + 3的运算结果赋值给变量a。\n`1 + 3`叫做表达式(expression),指一个为了得到返回值的计算式。语句和表达式的区别在于,前者主要为了进行某种操作,一般情况下不需要返回值;后者则是为了得到返回值,一定会返回一个值。凡是JavaScript语言中预期为值的地方,都可以使用表达式。比如,赋值语句的等号右边,预期是一个值,因此可以放置各种表达式。一条语句可以包含多个表达式。\n# 关键字和保留字\n按照规则,关键字也是语言保留的,不能用作标识符。\n\n**关键字:真正意义上的保留字。**\n\nif分支语句:`if`, `else`\nswitch分支语句:`switch`, `case`, `default`, `break`\n循环语句:`do`, `while`, `for`, `continue`\n异常处理语句:`try`, `catch`, `finally`, `throw`\n获取类型:`typeof`, `instanceof`\n布尔值:`true`, `false`, `null`\n函数相关:`var`, `void`, `function`, `return`\n其他:`in`, `this`,` with`, `new`, `delete`\n\n**保留字:结合java、C++等面向对象语言的思路,将来有可能新加入的关键字。**\n\n基本数据类型:`byte`, `char`, `boolean`, `int`, `short`, `long`, `float`, `double`, <span style=\"color:green;\">enum</span>\n继承:<span style=\"color:red;\">implements</span>, <span style=\"color:green;\">extends</span>, <span style=\"color:green;\">super</span>\n类与接口:<span style=\"color:green;\">class</span>, <span style=\"color:red;\">interface</span>\n用来修饰函数的关键字:`abstract`, `native`, <span style=\"color:red;\">static</span>, `final`, <span style=\"color:green;\">const</span>, `volatile`, `synchronized`\n导入导出:<span style=\"color:green;\">export</span>, <span style=\"color:green;\">import</span>\n访问权限:<span style=\"color:red;\">private</span>, <span style=\"color:red;\">protected</span>, <span style=\"color:red;\">public</span>\n其他:goto, <span style=\"color:red;\">package</span>, `throws`, `transient`, `debugger`, <span style=\"color:red;\">let</span>, <span style=\"color:red;\">yield</span>, <span style=\"color:red;\">arguments</span>, <span style=\"color:red;\">eval</span>\n<span style=\"color:#ddd;\">注意:红色字体为第5版严格模式下做的限制,<span style=\"color:red;\">arguments</span>, <span style=\"color:red;\">eval</span>这两个在严格模式下不能做标识符或属性名。</span>\n<span style=\"color:#ddd;\">注意:第5版把在非严格模式下运行时的保留字减少到绿色字体这几个,但是为了最大的兼容性还是都不要使用了吧。</span>\n\n**顺便整理下javascript语言中提供的有用的常用的变量和函数**\n\n数据类型:`Number`, `Boolean`, `String`, `undefined`, `Object`, `Array`, `Function`, `Date`, `Math`, `RegExp`, `Error`\n错误类型:`EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`\n编码:`decodeURI`, `decodeURIComponent`, `encodeURI`\n转义:`escape`, `unescape`\n类型转换:`parentInt`, `parentFloat`\n特殊值及判断:`isFinite`, `isNaN`, `NaN`, `Infinity`\n其他:`arguments`, `eval`\n<span style=\"color: red;\">这些都不是ECMAScript的关键字,undefined不是关键字,但是null确是关键字。</span>\n最后注意:我们常用的函数`alert()`不属于上面的三类。\n\n# 变量\n\n## 声明变量\n\n1. 使用关键词 var,这个语法可以同时用来声明局部(function内部)和全局变量。\n2. 在非严格模式下,无论是在全局范围内还是函数内,使用直接给变量赋值`count = 3`这种语法,会产生一个全部变量count(隐式全局变量),这种方式不推荐。\n3. <span style=\"color:red;\">无法用var声明块级局部变量</span>。\n\n```javascript\n// A single declaration.\nvar count; \n// Multiple declarations with a single var keyword.\nvar count, amount, level; \n// Variable declaration and initialization in one statement.\nvar count = 0, amount = 100;\ncount = 3;//无论实在全局范围内还是函数内,使用这种语法直接给变量赋值,会产生一个全部变量\n```\n如果未在 var 语句中初始化您的变量,它将自动采用 undefined 值,试图访问一个未初始化的变量会导致一个 ReferenceError 异常被抛出\n\n如果使用`var`重新声明一个已经存在的变量,是无效的。\n\n```javascript\nvar x = 1;\nvar x;\nx // 1\n```\n但是,如果第二次声明的同时还赋值了,则会覆盖掉前面的值。\n> 这种写法在 严格模式下不会报错,但是如果是重复的属性名或者形参则会报错。\n\n```javascript\nvar x = 1;\nvar x = 2;\nx // 2\n```\n\n使用var声明的全局变量和不使用var的区别:\n\n```javascript\n// 定义三个全局变量\nvar global_var = 1;\nglobal_novar = 2; // 反面教材\n(function () {\n global_fromfunc = 3; // 反面教材\n}());\n\n// 试图删除\ndelete global_var; // false\ndelete global_novar; // true\ndelete global_fromfunc; // true\n\n// 测试该删除\ntypeof global_var; // \"number\"\ntypeof global_novar; // \"undefined\"\ntypeof global_fromfunc; // \"undefined\"\n```\n\n原因:使用var命令声明变量时(或者使用属性赋值的方式声明变量),变量的可配置性(configurable)为false。\n \n```javascript\nvar a = 3;\nb = 3;\nconsole.log(Object.getOwnPropertyDescriptor(window, \"a\"));//Object {value: 3, writable: true, enumerable: true, configurable: false}\nconsole.log(Object.getOwnPropertyDescriptor(window, \"b\"));//Object {value: 3, writable: true, enumerable: true, configurable: true}\n\n```\n---\n了解另外两种变量的声明方式:\n* let:声明块范围局部变量,可选初始化值。\n* const:声明一个只读命名常量。\n\n# 数据类型\n\nECMAScript是变量松散类型语言(动态数据类型语言),即每个变量只是一个占位符,其类型并不固定,可以随时变化,这意味着你定义变量时不必指定变量类型,而且变量类型会在脚本执行需要时自动转换。但是,<span style=\"color: red;\">数据本身和各种运算是有类型的</span>。\nECMAScript 中有5 种简单数据类型(也称为基本数据类型(primitive type)):Undefined、Null、Boolean、Number和String。还有1 种复杂数据类型——Object,Object 本质上是由一组无序的名值对组成的。(ES6又新增了第七种Symbol类型的值)。\nObject对象又可以分成三个子类型:\n\n- 狭义的对象(object)\n- 数组(array)\n- 函数(function)`function f() {} console.log(typeof f) //\"function\"`\n\n狭义的对象和数组是两种不同的数据组合方式,而函数其实是处理数据的方法。\n## undefined类型\n[好好学学undefined!](http://www.yangshengdonghome.com/2016/01/29/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6undefined%EF%BC%81/)\n\n## null类型\n\nnull类型也只有一个值:null , 表示一个变量中没有包含有效数据,null表示\"没有对象\"。`字面值null`在这里意为`空值`、`空对象`的意思,更确切的说,一个被赋值为null的变量没有保存有效的对象等,可以通过给一个变量赋值为null来清空变量中的内容(不删除变量)。\n\n主要用处:\n\n* 作为函数的参数传递,表示该函数的参数不是对象。\n* 作为对象原型链的终点(例如声明原型链的结束 Foo.prototype = null)。\n```javascript \nObject.getPrototypeOf(Object.prototype) // null\n```\n产生null的原因只有一个,即对一个变量显式的赋值为null 。\n```javascript \nvar p = null;\nconsole.log(p); //null\ntypeof p; // \"object\"\ntypeof null; // \"object\"\n```\n另外,需要注意的是,`typeof null` 应该返回\"null\",但实际上返回的是\"object\",这是一个历史遗留问题,并没有其他原因,不要想太多,曾经有提案 `typeof null === 'null'`但提案被拒绝。\n> 《javascript高级程序设计3》是这么解释的:从逻辑角度来看,null 值表示一个空对象指针,而这也正是使用typeof 操作符检测null 值时会返回\"object\"的原因。\n> \n```javascript \n//判断null值,这个时候就不能用typeOf了,直接用if(xxx === null){}\nconsole.log(typeof null) //\"Object\"\nconsole.log(null instanceof Object) // false\nvar a = null,b;\nconsole.log(a === null) //true\nconsole.log(b === null) //false 因为 undefined !== null 三个等号为false\nconsole.log(b == null) // true 两个等号为true\n```\n\n> undefined == null //true [为什么相等?](https://www.w3.org/html/ig/zh/wiki/ES5/expressions#.E7.AD.89.E5.80.BC.E8.BF.90.E7.AE.97.E7.AC.A6)\n\n## Boolean类型\n\nboolean类型只有两个字面值:`true`和`false` 。 但在Javascript中,所有类型的值都可以转化为与boolean等价的值。要将一个值转换为其对应的Boolean值,可以调用类型转换函数Boolean()。其中转换结果为false的值有(false, \"\", +0, -0, NaN, null, undefined),其他值(包括空对象、空数组)均将转换为true。\n\n产生原因:下列运算符会返回布尔值。\n\n- 两元逻辑运算符: `&&` (And),`||` (Or)\n- 前置逻辑运算符: `!` (Not)\n- 相等运算符:`===`,`!==`,`==`,`!=`\n- 比较运算符:`>`,`>=`,`<`,`<=`\n \n```javascript \ntypeof true; // \"boolean\"\n\ntypeof false; // \"boolean\"\n\nBoolean(new Object()); //true\n\nBoolean(undefined); //false\n\nBoolean(null); //false\n\nBoolean(''); //false\n\nBoolean(0); //false\n\nBoolean(100); // true\n\nBoolean([]) // true\n \nBoolean({}) // true\n```\n \n \n所有对象的布尔值都是true,甚至连false对应的布尔对象也是true。\n\n```javascript\nBoolean(new Boolean(false)) // true\n```\n \n如果JavaScript预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值,比如:\nif判断语句中自动调用Boolean()。\n \n```javascript \nif (x = y + z){} //将值 y + z 赋给变量 x,然后检查整个表达式的结果(x 的值)是否为 0。\n```\n\n## Number类型\n[好好学学Number!](http://www.yangshengdonghome.com/2016/01/29/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6number/)\n\n## String类型\n[好好学学String!](http://www.yangshengdonghome.com/2016/02/02/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6String/)\n\n## Object类型\n[好好学学Object!](http://www.yangshengdonghome.com/2016/02/03/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6Object/)\n\n# 类型转换\n[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)\n\n# 类型检验\n[类型检验](http://www.yangshengdonghome.com/2016/02/19/%E7%B1%BB%E5%9E%8B%E6%A3%80%E9%AA%8C/)\n\n# 值传递和引用传递\n[值传递和引用传递](http://www.yangshengdonghome.com/2016/02/22/js%E4%B8%AD%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0%E9%83%BD%E6%98%AF%E6%8C%89%E5%80%BC%E4%BC%A0%E9%80%92%E7%9A%84/)\n\n# 操作符&运算符\n[值传递和引用传递](http://www.yangshengdonghome.com/2016/02/25/%E8%BF%90%E7%AE%97%E7%AC%A6/)\n\n# 操作语句 \n`break switch continue do()while{}; while(){} with(){}; label for(){} for...in `\n\n# 数组\n[数组](http://www.yangshengdonghome.com/2016/07/02/Array/)\n\n# 函数\n[函数](http://www.yangshengdonghome.com/2016/07/02/function/)\n\n# 错误类型\n[错误类型](http://www.yangshengdonghome.com/2016/07/02/error/)","source":"_posts/3-第三章基本概念-2016-01-20.md","raw":"title: 第三章 基本概念\ndate: 2016-01-20 09:38:23\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\n# 语法\n## 区分大小写\nECMAScript 中的一切(变量、函数名和操作符)都区分大小写。\n<!--more-->\n## 标示符\n所谓标识符,就是指变量、函数、属性的名字,或者函数的参数。\n规则:\n* 第一个字符必须是一个字母、下划线`_`或一个美元符号`$`\n* 其他字符可以是字母、下划线、美元符号或数字。标识符中的字母包含扩展的ASCll或Unicode字母字符(如À和Æ);\n\n## 严格模式 `\"use strict;\"`\n\n[严格模式](http://www.yangshengdonghome.com/2016/01/21/%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F/)\n\n\n## 语句\n\n* 一个语句由一个或多个表达式、关键字或运算符(符号)组成。通常,在一个行上书写一个语句,但可在两个行或多个行上书写一个语句。此外,在同一个行上书写两个或多个语句,用分号分隔。通常,每个新行都开始一个新语句。最好是显式终止您的语句。可使用分号 (;) 做到这一点,分号是 JavaScript 语句的终止字符。\n* 由括号 ({}) 包围的一组 JavaScript 语句称为一个块。组织在一个块中的语句通常可以视为一个语句,比如`var a = function (){};`和对象字面量(`a = {};`),这两个结尾的也需要加分号,其他的大括号情况(for循环、ifelse判断),可以不加分号。\n\n ```javascript\n abc={}bbc=function(){}//报错 Uncaught SyntaxError: Unexpected identifier\n abc=function(){}bbc=function(){}//报错 Uncaught SyntaxError: Unexpected identifier\n if(a==1){}bbc=function(){}// 正常\n for(var a = 1;a<10;a++){}bbc=function(){}//正常\n ```\n### 语句和表达式的区别\n\n ```javascript\n var a = 1 + 3;\n ```\n这条语句先用var命令,声明了变量a,然后将1 + 3的运算结果赋值给变量a。\n`1 + 3`叫做表达式(expression),指一个为了得到返回值的计算式。语句和表达式的区别在于,前者主要为了进行某种操作,一般情况下不需要返回值;后者则是为了得到返回值,一定会返回一个值。凡是JavaScript语言中预期为值的地方,都可以使用表达式。比如,赋值语句的等号右边,预期是一个值,因此可以放置各种表达式。一条语句可以包含多个表达式。\n# 关键字和保留字\n按照规则,关键字也是语言保留的,不能用作标识符。\n\n**关键字:真正意义上的保留字。**\n\nif分支语句:`if`, `else`\nswitch分支语句:`switch`, `case`, `default`, `break`\n循环语句:`do`, `while`, `for`, `continue`\n异常处理语句:`try`, `catch`, `finally`, `throw`\n获取类型:`typeof`, `instanceof`\n布尔值:`true`, `false`, `null`\n函数相关:`var`, `void`, `function`, `return`\n其他:`in`, `this`,` with`, `new`, `delete`\n\n**保留字:结合java、C++等面向对象语言的思路,将来有可能新加入的关键字。**\n\n基本数据类型:`byte`, `char`, `boolean`, `int`, `short`, `long`, `float`, `double`, <span style=\"color:green;\">enum</span>\n继承:<span style=\"color:red;\">implements</span>, <span style=\"color:green;\">extends</span>, <span style=\"color:green;\">super</span>\n类与接口:<span style=\"color:green;\">class</span>, <span style=\"color:red;\">interface</span>\n用来修饰函数的关键字:`abstract`, `native`, <span style=\"color:red;\">static</span>, `final`, <span style=\"color:green;\">const</span>, `volatile`, `synchronized`\n导入导出:<span style=\"color:green;\">export</span>, <span style=\"color:green;\">import</span>\n访问权限:<span style=\"color:red;\">private</span>, <span style=\"color:red;\">protected</span>, <span style=\"color:red;\">public</span>\n其他:goto, <span style=\"color:red;\">package</span>, `throws`, `transient`, `debugger`, <span style=\"color:red;\">let</span>, <span style=\"color:red;\">yield</span>, <span style=\"color:red;\">arguments</span>, <span style=\"color:red;\">eval</span>\n<span style=\"color:#ddd;\">注意:红色字体为第5版严格模式下做的限制,<span style=\"color:red;\">arguments</span>, <span style=\"color:red;\">eval</span>这两个在严格模式下不能做标识符或属性名。</span>\n<span style=\"color:#ddd;\">注意:第5版把在非严格模式下运行时的保留字减少到绿色字体这几个,但是为了最大的兼容性还是都不要使用了吧。</span>\n\n**顺便整理下javascript语言中提供的有用的常用的变量和函数**\n\n数据类型:`Number`, `Boolean`, `String`, `undefined`, `Object`, `Array`, `Function`, `Date`, `Math`, `RegExp`, `Error`\n错误类型:`EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`\n编码:`decodeURI`, `decodeURIComponent`, `encodeURI`\n转义:`escape`, `unescape`\n类型转换:`parentInt`, `parentFloat`\n特殊值及判断:`isFinite`, `isNaN`, `NaN`, `Infinity`\n其他:`arguments`, `eval`\n<span style=\"color: red;\">这些都不是ECMAScript的关键字,undefined不是关键字,但是null确是关键字。</span>\n最后注意:我们常用的函数`alert()`不属于上面的三类。\n\n# 变量\n\n## 声明变量\n\n1. 使用关键词 var,这个语法可以同时用来声明局部(function内部)和全局变量。\n2. 在非严格模式下,无论是在全局范围内还是函数内,使用直接给变量赋值`count = 3`这种语法,会产生一个全部变量count(隐式全局变量),这种方式不推荐。\n3. <span style=\"color:red;\">无法用var声明块级局部变量</span>。\n\n```javascript\n// A single declaration.\nvar count; \n// Multiple declarations with a single var keyword.\nvar count, amount, level; \n// Variable declaration and initialization in one statement.\nvar count = 0, amount = 100;\ncount = 3;//无论实在全局范围内还是函数内,使用这种语法直接给变量赋值,会产生一个全部变量\n```\n如果未在 var 语句中初始化您的变量,它将自动采用 undefined 值,试图访问一个未初始化的变量会导致一个 ReferenceError 异常被抛出\n\n如果使用`var`重新声明一个已经存在的变量,是无效的。\n\n```javascript\nvar x = 1;\nvar x;\nx // 1\n```\n但是,如果第二次声明的同时还赋值了,则会覆盖掉前面的值。\n> 这种写法在 严格模式下不会报错,但是如果是重复的属性名或者形参则会报错。\n\n```javascript\nvar x = 1;\nvar x = 2;\nx // 2\n```\n\n使用var声明的全局变量和不使用var的区别:\n\n```javascript\n// 定义三个全局变量\nvar global_var = 1;\nglobal_novar = 2; // 反面教材\n(function () {\n global_fromfunc = 3; // 反面教材\n}());\n\n// 试图删除\ndelete global_var; // false\ndelete global_novar; // true\ndelete global_fromfunc; // true\n\n// 测试该删除\ntypeof global_var; // \"number\"\ntypeof global_novar; // \"undefined\"\ntypeof global_fromfunc; // \"undefined\"\n```\n\n原因:使用var命令声明变量时(或者使用属性赋值的方式声明变量),变量的可配置性(configurable)为false。\n \n```javascript\nvar a = 3;\nb = 3;\nconsole.log(Object.getOwnPropertyDescriptor(window, \"a\"));//Object {value: 3, writable: true, enumerable: true, configurable: false}\nconsole.log(Object.getOwnPropertyDescriptor(window, \"b\"));//Object {value: 3, writable: true, enumerable: true, configurable: true}\n\n```\n---\n了解另外两种变量的声明方式:\n* let:声明块范围局部变量,可选初始化值。\n* const:声明一个只读命名常量。\n\n# 数据类型\n\nECMAScript是变量松散类型语言(动态数据类型语言),即每个变量只是一个占位符,其类型并不固定,可以随时变化,这意味着你定义变量时不必指定变量类型,而且变量类型会在脚本执行需要时自动转换。但是,<span style=\"color: red;\">数据本身和各种运算是有类型的</span>。\nECMAScript 中有5 种简单数据类型(也称为基本数据类型(primitive type)):Undefined、Null、Boolean、Number和String。还有1 种复杂数据类型——Object,Object 本质上是由一组无序的名值对组成的。(ES6又新增了第七种Symbol类型的值)。\nObject对象又可以分成三个子类型:\n\n- 狭义的对象(object)\n- 数组(array)\n- 函数(function)`function f() {} console.log(typeof f) //\"function\"`\n\n狭义的对象和数组是两种不同的数据组合方式,而函数其实是处理数据的方法。\n## undefined类型\n[好好学学undefined!](http://www.yangshengdonghome.com/2016/01/29/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6undefined%EF%BC%81/)\n\n## null类型\n\nnull类型也只有一个值:null , 表示一个变量中没有包含有效数据,null表示\"没有对象\"。`字面值null`在这里意为`空值`、`空对象`的意思,更确切的说,一个被赋值为null的变量没有保存有效的对象等,可以通过给一个变量赋值为null来清空变量中的内容(不删除变量)。\n\n主要用处:\n\n* 作为函数的参数传递,表示该函数的参数不是对象。\n* 作为对象原型链的终点(例如声明原型链的结束 Foo.prototype = null)。\n```javascript \nObject.getPrototypeOf(Object.prototype) // null\n```\n产生null的原因只有一个,即对一个变量显式的赋值为null 。\n```javascript \nvar p = null;\nconsole.log(p); //null\ntypeof p; // \"object\"\ntypeof null; // \"object\"\n```\n另外,需要注意的是,`typeof null` 应该返回\"null\",但实际上返回的是\"object\",这是一个历史遗留问题,并没有其他原因,不要想太多,曾经有提案 `typeof null === 'null'`但提案被拒绝。\n> 《javascript高级程序设计3》是这么解释的:从逻辑角度来看,null 值表示一个空对象指针,而这也正是使用typeof 操作符检测null 值时会返回\"object\"的原因。\n> \n```javascript \n//判断null值,这个时候就不能用typeOf了,直接用if(xxx === null){}\nconsole.log(typeof null) //\"Object\"\nconsole.log(null instanceof Object) // false\nvar a = null,b;\nconsole.log(a === null) //true\nconsole.log(b === null) //false 因为 undefined !== null 三个等号为false\nconsole.log(b == null) // true 两个等号为true\n```\n\n> undefined == null //true [为什么相等?](https://www.w3.org/html/ig/zh/wiki/ES5/expressions#.E7.AD.89.E5.80.BC.E8.BF.90.E7.AE.97.E7.AC.A6)\n\n## Boolean类型\n\nboolean类型只有两个字面值:`true`和`false` 。 但在Javascript中,所有类型的值都可以转化为与boolean等价的值。要将一个值转换为其对应的Boolean值,可以调用类型转换函数Boolean()。其中转换结果为false的值有(false, \"\", +0, -0, NaN, null, undefined),其他值(包括空对象、空数组)均将转换为true。\n\n产生原因:下列运算符会返回布尔值。\n\n- 两元逻辑运算符: `&&` (And),`||` (Or)\n- 前置逻辑运算符: `!` (Not)\n- 相等运算符:`===`,`!==`,`==`,`!=`\n- 比较运算符:`>`,`>=`,`<`,`<=`\n \n```javascript \ntypeof true; // \"boolean\"\n\ntypeof false; // \"boolean\"\n\nBoolean(new Object()); //true\n\nBoolean(undefined); //false\n\nBoolean(null); //false\n\nBoolean(''); //false\n\nBoolean(0); //false\n\nBoolean(100); // true\n\nBoolean([]) // true\n \nBoolean({}) // true\n```\n \n \n所有对象的布尔值都是true,甚至连false对应的布尔对象也是true。\n\n```javascript\nBoolean(new Boolean(false)) // true\n```\n \n如果JavaScript预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值,比如:\nif判断语句中自动调用Boolean()。\n \n```javascript \nif (x = y + z){} //将值 y + z 赋给变量 x,然后检查整个表达式的结果(x 的值)是否为 0。\n```\n\n## Number类型\n[好好学学Number!](http://www.yangshengdonghome.com/2016/01/29/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6number/)\n\n## String类型\n[好好学学String!](http://www.yangshengdonghome.com/2016/02/02/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6String/)\n\n## Object类型\n[好好学学Object!](http://www.yangshengdonghome.com/2016/02/03/%E5%A5%BD%E5%A5%BD%E5%AD%A6%E5%AD%A6Object/)\n\n# 类型转换\n[类型转换](http://www.yangshengdonghome.com/2016/02/18/%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/)\n\n# 类型检验\n[类型检验](http://www.yangshengdonghome.com/2016/02/19/%E7%B1%BB%E5%9E%8B%E6%A3%80%E9%AA%8C/)\n\n# 值传递和引用传递\n[值传递和引用传递](http://www.yangshengdonghome.com/2016/02/22/js%E4%B8%AD%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0%E9%83%BD%E6%98%AF%E6%8C%89%E5%80%BC%E4%BC%A0%E9%80%92%E7%9A%84/)\n\n# 操作符&运算符\n[值传递和引用传递](http://www.yangshengdonghome.com/2016/02/25/%E8%BF%90%E7%AE%97%E7%AC%A6/)\n\n# 操作语句 \n`break switch continue do()while{}; while(){} with(){}; label for(){} for...in `\n\n# 数组\n[数组](http://www.yangshengdonghome.com/2016/07/02/Array/)\n\n# 函数\n[函数](http://www.yangshengdonghome.com/2016/07/02/function/)\n\n# 错误类型\n[错误类型](http://www.yangshengdonghome.com/2016/07/02/error/)","slug":"3-第三章基本概念","published":1,"updated":"2016-07-02T10:34:14.108Z","layout":"post","photos":[],"link":"","_id":"citf9ome8002bskv7czjtkqov"},{"title":"第二章 在HTML中使用JavaScrpt","date":"2016-01-05T06:25:13.000Z","comments":1,"_content":"## script元素\n\n`<script src=\"demo.js\"></script>`尽管`<script>` 标签内没有内容,结束的 `</script>` 标签也是必需的。外部文件一般扩展名为.js,但这不是强制的,不写.js扩展名一样可以运行。\n<!--more-->\n* type 这个属性不是必须的,默认值是 “text /javascript”,表示的是编写代码使用的脚本语言的内容类型(也称为MIME 类型)。服务器在传送js文件时使用的MIME类型,通常是application/x-javascript,但在type中设置这个值可能会导致脚本被忽略,考虑到约定俗成和最大浏览器兼容性,目前type属性的值依旧还是text/javascript。\n* async 只适用外部引入脚本。\n* defer 只适用外部引入脚本。\n* language 已废弃。\n* src\n\n>1. 如果通过<script></script>向页面写入一段可以执行的js代码?。\n>\n <script>\n document.write('<script>alert(0)</script>');//alert(0); 不执行\n document.write('<script>alert(4)</scr'+'ipt>'); //正常弹窗\n document.write('<script>alert(2)<\\/script>'); //正常弹窗\n document.write('<script>alert(3)</script>'); //报错\n </script>\n\n>2. 执行顺序:\n>无论如何包含代码,只要不存在defer 和async 属性,浏览器都会按照`<script>`元素在页面中出现的先后顺序对它们依次进行解析。换句话说,在第一个`<script>`元素包含的代码解析完成后,第二个`<script>`包含的代码才会被解析,然后才是第三个、第四个……\n\n## 标签的位置\n\n <!DOCTYPE html>\n <html>\n <head>\n <title>Example HTML Page</title>\n </head>\n <body>\n <!-- 这里放内容 -->\n <script type=\"text/javascript\" src=\"example1.js\"></script>\n <script type=\"text/javascript\" src=\"example2.js\"></script>\n </body>\n </html>\n## 延迟脚本 defer\n\ndefer这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕(`/HTML`)后再运行。因此,在`<script>`元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行。\n>**注意:**\n>多个延迟脚本并不一定会按照顺序执行,因此最好只包含一个延迟脚本。\n>**浏览器支持情况:**\nIE4、Firefox 3.5、Safari 5 和Chrome ,其他浏览器会忽略这个属性。为此,把延迟脚本放在页面底部仍然是最佳选择。\n>**defer执行的脚本都会在DOMContentLoaded之前就执行,defer会阻塞页面加载**\n\n## 异步脚本 async\n\nHTML5 为`<script>`元素定义了async 属性。指定async 属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。为此,建议异步脚本不要在加载期间修改DOM。\n>**注意:**\n>多个延迟脚本并不一定会按照顺序执行,因此最好只包含一个延迟脚本。\n>**浏览器支持情况:**\n- Firefox 3.6、Safari 5 和Chrome。\n- ie系列,async没有任何效果\n- 在chrome下,只有外联脚本,且是在body中引用的,才能生效.\n- **异步执行的表现是,在DOMContentLoaded事件之后,window.loaded事件之前,所以,这个属性处理阻塞的问题是可行的**\n\n## Defer和async的区别\n\n先来试个一句话解释仨,当浏览器碰到 script 脚本的时候:\n+ `<script src=\"script.js\"></script>` 没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。\n+ `<script async src=\"script.js\"></script>` 有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。\n+ `<script defer src=\"myscript.js\"></script>` 有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。\n\n然后从实用角度来说呢,首先把所有脚本都丢到 `</body>` 之前是最佳实践,因为对于旧浏览器来说这是唯一的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。\n接着,我们来看一张图咯:\n\n{% asset_img deferAsync.png %}\n\n**此图告诉我们以下几个要点:**\ndefer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)。它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的,关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用。async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics。\n\n### Defer async常规表现(一些高级浏览器)\n\n#### herder\n1. header中行内脚本执行顺序不受defer async影响,顺序执行,会阻塞DOMContentLoaded。\n2. header中引用外部脚本,添加defer async后,浏览器表现情况不统一,async的可能先执行,所以引用外部脚本并不适合加在header中,也不适合添加defer async标示。\n \n#### body\n1. body中图片加载会阻塞window.loaded,不会阻塞DOMContentLoaded。\n2. body中行内脚本执行顺序不受defer async影响,顺序执行,阻塞DOMContentLoaded。\n3. body中引用外部脚本,defer async表现正常,外部脚本应该加在body中,body结束标签上面。\n\n#### ajax\n1. 无论是header还是body中,行内脚本执行的ajax还是外部脚本执行的ajax,都对页面加载没有影响。\n\n#### 总结\n1. defer执行的脚本都会在DOMContentLoaded之前就执行,defer会阻塞页面加载。\n2. async脚本都会在loaded之前执行,它会阻塞window.loaded。\n3. DOMContentLoaded在window.loaded之前执行,阻塞DOMContentLoaded也就会阻塞window.loaded\n4. document ready在DOMContentLoaded之前执行,说明document ready是监听DOMContentLoaded完成的\n \n### IE\n\n1. IE支持defer属性,不支持async属性,从IE9及以上支持onload,支持DOMContentLoaded。\n2. IE6,7支持行内脚本defer属性, 从表现上来看IE6,7,8,9都支持行内脚本的defer,所以我们在ie6,7,8,9中观察到的现象是,行内的先执行async,再执行没加defer async标记的,defer的延迟执行了。\n3. 同时我们又发现IE6,7脚本中ajax影响了页面加载,影响document ready,IE8及以上版本不受影响。\n4. 到了IE8以上,表现和webkit内核浏览器基本相似了。\n\n**不是动态添加的脚本,都会阻塞页DOMContentLoaded,动态添加的脚本应该在document ready后加载,但是也会阻塞loaded**\n>不懂DOMContentLoaded的点[这里](http://www.yangshengdonghome.com/2016/01/08/DOMContentLoaded/)。\n\n## 嵌入代码与外部文件\n\n* 推荐通过`<script>`标签引入外部js文件。\n* 浏览器能够根据具体的设置缓存链接的所有外部JavaScript 文件。也就是说,如果有两个页面都使用同一个文件,那么这个文件只需下载一次。因此,最终结果就是能够加快页面加载的\n速度。\n\n## 文档模式 `<!DOCTYPE *>`\n\nE5.5 引入了文档模式的概念,而这个概念是通过使用文档类型(doctype)切换实现的。\n1. 混杂模式(quirks mode)\n2. 标准模式(standards mode)\n3. 准标准模式(almost standards mode)\n\n如果在文档开始处没有发现文档类型声明,则所有浏览器都会默认开启混杂模式。\n>现在所有的HTML文档都推荐使用HTML5规定的`<!DOCTYPE html>`\n\n## noscript\n\n包含在`<noscript></noscript>`元素中的内容只有在下列情况下才会显示出来:\n* 浏览器不支持脚本;\n* 浏览器支持脚本,但脚本被禁用。\n","source":"_posts/2-在HTML中使用JavaScrpt-2016-01-05.md","raw":"title: 第二章 在HTML中使用JavaScrpt\ndate: 2016-01-05 14:25:13\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\n## script元素\n\n`<script src=\"demo.js\"></script>`尽管`<script>` 标签内没有内容,结束的 `</script>` 标签也是必需的。外部文件一般扩展名为.js,但这不是强制的,不写.js扩展名一样可以运行。\n<!--more-->\n* type 这个属性不是必须的,默认值是 “text /javascript”,表示的是编写代码使用的脚本语言的内容类型(也称为MIME 类型)。服务器在传送js文件时使用的MIME类型,通常是application/x-javascript,但在type中设置这个值可能会导致脚本被忽略,考虑到约定俗成和最大浏览器兼容性,目前type属性的值依旧还是text/javascript。\n* async 只适用外部引入脚本。\n* defer 只适用外部引入脚本。\n* language 已废弃。\n* src\n\n>1. 如果通过<script></script>向页面写入一段可以执行的js代码?。\n>\n <script>\n document.write('<script>alert(0)</script>');//alert(0); 不执行\n document.write('<script>alert(4)</scr'+'ipt>'); //正常弹窗\n document.write('<script>alert(2)<\\/script>'); //正常弹窗\n document.write('<script>alert(3)</script>'); //报错\n </script>\n\n>2. 执行顺序:\n>无论如何包含代码,只要不存在defer 和async 属性,浏览器都会按照`<script>`元素在页面中出现的先后顺序对它们依次进行解析。换句话说,在第一个`<script>`元素包含的代码解析完成后,第二个`<script>`包含的代码才会被解析,然后才是第三个、第四个……\n\n## 标签的位置\n\n <!DOCTYPE html>\n <html>\n <head>\n <title>Example HTML Page</title>\n </head>\n <body>\n <!-- 这里放内容 -->\n <script type=\"text/javascript\" src=\"example1.js\"></script>\n <script type=\"text/javascript\" src=\"example2.js\"></script>\n </body>\n </html>\n## 延迟脚本 defer\n\ndefer这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕(`/HTML`)后再运行。因此,在`<script>`元素中设置defer 属性,相当于告诉浏览器立即下载,但延迟执行。\n>**注意:**\n>多个延迟脚本并不一定会按照顺序执行,因此最好只包含一个延迟脚本。\n>**浏览器支持情况:**\nIE4、Firefox 3.5、Safari 5 和Chrome ,其他浏览器会忽略这个属性。为此,把延迟脚本放在页面底部仍然是最佳选择。\n>**defer执行的脚本都会在DOMContentLoaded之前就执行,defer会阻塞页面加载**\n\n## 异步脚本 async\n\nHTML5 为`<script>`元素定义了async 属性。指定async 属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。为此,建议异步脚本不要在加载期间修改DOM。\n>**注意:**\n>多个延迟脚本并不一定会按照顺序执行,因此最好只包含一个延迟脚本。\n>**浏览器支持情况:**\n- Firefox 3.6、Safari 5 和Chrome。\n- ie系列,async没有任何效果\n- 在chrome下,只有外联脚本,且是在body中引用的,才能生效.\n- **异步执行的表现是,在DOMContentLoaded事件之后,window.loaded事件之前,所以,这个属性处理阻塞的问题是可行的**\n\n## Defer和async的区别\n\n先来试个一句话解释仨,当浏览器碰到 script 脚本的时候:\n+ `<script src=\"script.js\"></script>` 没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。\n+ `<script async src=\"script.js\"></script>` 有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。\n+ `<script defer src=\"myscript.js\"></script>` 有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。\n\n然后从实用角度来说呢,首先把所有脚本都丢到 `</body>` 之前是最佳实践,因为对于旧浏览器来说这是唯一的优化选择,此法可保证非脚本的其他一切元素能够以最快的速度得到加载和解析。\n接着,我们来看一张图咯:\n\n{% asset_img deferAsync.png %}\n\n**此图告诉我们以下几个要点:**\ndefer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)。它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的,关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用。async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics。\n\n### Defer async常规表现(一些高级浏览器)\n\n#### herder\n1. header中行内脚本执行顺序不受defer async影响,顺序执行,会阻塞DOMContentLoaded。\n2. header中引用外部脚本,添加defer async后,浏览器表现情况不统一,async的可能先执行,所以引用外部脚本并不适合加在header中,也不适合添加defer async标示。\n \n#### body\n1. body中图片加载会阻塞window.loaded,不会阻塞DOMContentLoaded。\n2. body中行内脚本执行顺序不受defer async影响,顺序执行,阻塞DOMContentLoaded。\n3. body中引用外部脚本,defer async表现正常,外部脚本应该加在body中,body结束标签上面。\n\n#### ajax\n1. 无论是header还是body中,行内脚本执行的ajax还是外部脚本执行的ajax,都对页面加载没有影响。\n\n#### 总结\n1. defer执行的脚本都会在DOMContentLoaded之前就执行,defer会阻塞页面加载。\n2. async脚本都会在loaded之前执行,它会阻塞window.loaded。\n3. DOMContentLoaded在window.loaded之前执行,阻塞DOMContentLoaded也就会阻塞window.loaded\n4. document ready在DOMContentLoaded之前执行,说明document ready是监听DOMContentLoaded完成的\n \n### IE\n\n1. IE支持defer属性,不支持async属性,从IE9及以上支持onload,支持DOMContentLoaded。\n2. IE6,7支持行内脚本defer属性, 从表现上来看IE6,7,8,9都支持行内脚本的defer,所以我们在ie6,7,8,9中观察到的现象是,行内的先执行async,再执行没加defer async标记的,defer的延迟执行了。\n3. 同时我们又发现IE6,7脚本中ajax影响了页面加载,影响document ready,IE8及以上版本不受影响。\n4. 到了IE8以上,表现和webkit内核浏览器基本相似了。\n\n**不是动态添加的脚本,都会阻塞页DOMContentLoaded,动态添加的脚本应该在document ready后加载,但是也会阻塞loaded**\n>不懂DOMContentLoaded的点[这里](http://www.yangshengdonghome.com/2016/01/08/DOMContentLoaded/)。\n\n## 嵌入代码与外部文件\n\n* 推荐通过`<script>`标签引入外部js文件。\n* 浏览器能够根据具体的设置缓存链接的所有外部JavaScript 文件。也就是说,如果有两个页面都使用同一个文件,那么这个文件只需下载一次。因此,最终结果就是能够加快页面加载的\n速度。\n\n## 文档模式 `<!DOCTYPE *>`\n\nE5.5 引入了文档模式的概念,而这个概念是通过使用文档类型(doctype)切换实现的。\n1. 混杂模式(quirks mode)\n2. 标准模式(standards mode)\n3. 准标准模式(almost standards mode)\n\n如果在文档开始处没有发现文档类型声明,则所有浏览器都会默认开启混杂模式。\n>现在所有的HTML文档都推荐使用HTML5规定的`<!DOCTYPE html>`\n\n## noscript\n\n包含在`<noscript></noscript>`元素中的内容只有在下列情况下才会显示出来:\n* 浏览器不支持脚本;\n* 浏览器支持脚本,但脚本被禁用。\n","slug":"2-在HTML中使用JavaScrpt","published":1,"updated":"2016-06-01T06:28:16.610Z","layout":"post","photos":[],"link":"","_id":"citf9omeh002eskv7hn9gkv38"},{"title":"第一章 JavaScrpt简介","date":"2016-01-05T06:24:40.000Z","comments":1,"_content":"先简要说一下和JavaScript相关的一些背景术语,就不详细讨论JavaScript的历史了,想了解的朋友可以参考原书。\n<!--more-->\n### 专有名词\n+ ECMA:\n欧洲计算机制造商协会(Standard ECMA-262European Computer Manufacturers Association)\n+ TC39:\nECMA第39号技术委员会(Technical Committee#39),由来自一些关注脚本语言发展的公司的程序员组成,负责制定一种通用、跨平台、供应商中立的脚本语言。\n+ ECMAScript:\n由ECMA制定,在ECMA-262中定义的脚本语言标准。ECMAScript只是一个脚本语言标准,你尽可以在自己的环境中取实现它,这个环境,就称为ECMAScript的宿主环境,Web浏览器可以说是ECMAScript目前最重要的宿主环境了,而不同的Web浏览器,对ECMAScript标准的支持也不尽相同。除Web浏览器,Adobe ActionScript也实现了ECMAScript。一般的宿主环境,除了实现ECMAScript标准,也会有各自的扩展,以便与环境更好的交互。\n+ ES3、ES5、ES6:是指ECMAScript 的三个版本,最新版是ES6,已经有部分特性被浏览器支持。[查看各个版本被各大浏览器的支持情况](http://kangax.github.io/compat-table/es5/)。\n\n### DOM\n#### what is DOM?\n通过 JavaScript,您可以重构整个HTML文档。您可以添加、移除、改变或重排页面上的项目。要改变页面的某个东西,JavaScript就需要对HTML文档中所有元素进行访问的入口。这个入口,连同对HTML 元素进行添加、移动、改变或移除的方法和属性,都是通过文档对象模型来获得的(DOM)。在 1998 年,W3C 发布了第一级的 DOM 规范。这个规范允许访问和操作 HTML 页面中的每一个单独的元素。所有的浏览器都执行了这个标准,因此,DOM 的兼容性问题也几乎难觅踪影了。DOM 可被 JavaScript 用来读取、改变 HTML、XHTML 以及 XML 文档。\n+ DOM 被分为不同的部分(核心、XML及HTML)和级别(DOM Level 1/2/3):\n + Core DOM 定义了一套标准的针对任何结构化文档的对象 \n + XML DOM 定义了一套标准的针对 XML 文档的对象 \n + HTML DOM 定义了一套标准的针对 HTML 文档的对象。\n \n#### DOM Level\n+ DOM1级:\n 1. DOM1 级由两个模块组成:DOM核心(DOM Core)和DOM HTML。其中,DOM 核心规定的是如何映射基于XML 的文档结构,以便简化对文档中任意部分的访问和操作。DOM HTML 模块则在DOM 核心的基础上加以扩展,添加了针对HTML 的对象和方法。\n+ DOM2级:\n 1. DOM2 级在原来DOM 的基础上又扩充了(DHTML 一直都支持的)鼠标和用户界面事件、范围、遍历(迭代DOM文档的方法)等细分模块,而且通过对象接口增加了对CSS(Cascading Style Sheets,层叠样式表)的支持。DOM1 级中的DOM核心模块也经过扩展开始支持XML 命名空间。\n+ DOM3级:\n 1. 引入了以统一方式加载和保存文档的方法\n 2. 新增了验证文档的方法\n 3. DOM3 级也对DOM 核心进行了扩展,开始支持XML 1.0 规范,涉及XML Infoset、XPath和XML Base。\n\n#### BOM\n从根本上讲,BOM 只处理浏览器窗口和框架;但人们习惯上也把所有针对浏览器的JavaScript 扩展算作BOM的一部分。下面就是一些这样的扩展:\n1. 弹出新浏览器窗口的功能;\n2. 移动、缩放和关闭浏览器窗口的功能;\n3. 提供浏览器详细信息的navigator 对象;\n4. 提供浏览器所加载页面的详细信息的location 对象;\n5. 提供用户显示器分辨率详细信息的screen 对象;\n6. 对cookies 的支持;\n7. 像XMLHttpRequest 和IE 的ActiveXObject 这样的自定义对象。\n\n\n#### LiveScript、JavaScript、JScript:\nLiveScript是JavaScript的前身,而JScript则是微软为了防止版权冲突而给自己的脚本语言起的名称。他们除了实现了ECMAScript外,还会包括针对浏览器的扩展(BOM:浏览器对象模型)和针对XML/HTML API的扩展(DOM:文档对象模型)。\n+ JavaScript 是一种专为网页交互设计的脚本语言,由下列三个不同的部分组成:\n+ ECMAScript:规定了以下这些内容:语法、类型、语句、关键字、保留字、操作符、对象\n+ DOM:文档对象模型(Document Object Model)\n+ BOM:浏览器对象模型(Browser Object Model)","source":"_posts/1-JavaScrpt简介-2016-01-05.md","raw":"title: 第一章 JavaScrpt简介\ndate: 2016-01-05 14:24:40\ntags:\n- JavaScript\ncomments: true\ncategories:\n- 《JS高程3-笔记》\n---\n先简要说一下和JavaScript相关的一些背景术语,就不详细讨论JavaScript的历史了,想了解的朋友可以参考原书。\n<!--more-->\n### 专有名词\n+ ECMA:\n欧洲计算机制造商协会(Standard ECMA-262European Computer Manufacturers Association)\n+ TC39:\nECMA第39号技术委员会(Technical Committee#39),由来自一些关注脚本语言发展的公司的程序员组成,负责制定一种通用、跨平台、供应商中立的脚本语言。\n+ ECMAScript:\n由ECMA制定,在ECMA-262中定义的脚本语言标准。ECMAScript只是一个脚本语言标准,你尽可以在自己的环境中取实现它,这个环境,就称为ECMAScript的宿主环境,Web浏览器可以说是ECMAScript目前最重要的宿主环境了,而不同的Web浏览器,对ECMAScript标准的支持也不尽相同。除Web浏览器,Adobe ActionScript也实现了ECMAScript。一般的宿主环境,除了实现ECMAScript标准,也会有各自的扩展,以便与环境更好的交互。\n+ ES3、ES5、ES6:是指ECMAScript 的三个版本,最新版是ES6,已经有部分特性被浏览器支持。[查看各个版本被各大浏览器的支持情况](http://kangax.github.io/compat-table/es5/)。\n\n### DOM\n#### what is DOM?\n通过 JavaScript,您可以重构整个HTML文档。您可以添加、移除、改变或重排页面上的项目。要改变页面的某个东西,JavaScript就需要对HTML文档中所有元素进行访问的入口。这个入口,连同对HTML 元素进行添加、移动、改变或移除的方法和属性,都是通过文档对象模型来获得的(DOM)。在 1998 年,W3C 发布了第一级的 DOM 规范。这个规范允许访问和操作 HTML 页面中的每一个单独的元素。所有的浏览器都执行了这个标准,因此,DOM 的兼容性问题也几乎难觅踪影了。DOM 可被 JavaScript 用来读取、改变 HTML、XHTML 以及 XML 文档。\n+ DOM 被分为不同的部分(核心、XML及HTML)和级别(DOM Level 1/2/3):\n + Core DOM 定义了一套标准的针对任何结构化文档的对象 \n + XML DOM 定义了一套标准的针对 XML 文档的对象 \n + HTML DOM 定义了一套标准的针对 HTML 文档的对象。\n \n#### DOM Level\n+ DOM1级:\n 1. DOM1 级由两个模块组成:DOM核心(DOM Core)和DOM HTML。其中,DOM 核心规定的是如何映射基于XML 的文档结构,以便简化对文档中任意部分的访问和操作。DOM HTML 模块则在DOM 核心的基础上加以扩展,添加了针对HTML 的对象和方法。\n+ DOM2级:\n 1. DOM2 级在原来DOM 的基础上又扩充了(DHTML 一直都支持的)鼠标和用户界面事件、范围、遍历(迭代DOM文档的方法)等细分模块,而且通过对象接口增加了对CSS(Cascading Style Sheets,层叠样式表)的支持。DOM1 级中的DOM核心模块也经过扩展开始支持XML 命名空间。\n+ DOM3级:\n 1. 引入了以统一方式加载和保存文档的方法\n 2. 新增了验证文档的方法\n 3. DOM3 级也对DOM 核心进行了扩展,开始支持XML 1.0 规范,涉及XML Infoset、XPath和XML Base。\n\n#### BOM\n从根本上讲,BOM 只处理浏览器窗口和框架;但人们习惯上也把所有针对浏览器的JavaScript 扩展算作BOM的一部分。下面就是一些这样的扩展:\n1. 弹出新浏览器窗口的功能;\n2. 移动、缩放和关闭浏览器窗口的功能;\n3. 提供浏览器详细信息的navigator 对象;\n4. 提供浏览器所加载页面的详细信息的location 对象;\n5. 提供用户显示器分辨率详细信息的screen 对象;\n6. 对cookies 的支持;\n7. 像XMLHttpRequest 和IE 的ActiveXObject 这样的自定义对象。\n\n\n#### LiveScript、JavaScript、JScript:\nLiveScript是JavaScript的前身,而JScript则是微软为了防止版权冲突而给自己的脚本语言起的名称。他们除了实现了ECMAScript外,还会包括针对浏览器的扩展(BOM:浏览器对象模型)和针对XML/HTML API的扩展(DOM:文档对象模型)。\n+ JavaScript 是一种专为网页交互设计的脚本语言,由下列三个不同的部分组成:\n+ ECMAScript:规定了以下这些内容:语法、类型、语句、关键字、保留字、操作符、对象\n+ DOM:文档对象模型(Document Object Model)\n+ BOM:浏览器对象模型(Browser Object Model)","slug":"1-JavaScrpt简介","published":1,"updated":"2016-06-01T02:33:50.407Z","layout":"post","photos":[],"link":"","_id":"citf9omgc002hskv74gylfu49"}],"PostAsset":[{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/body.png","post":"citf9olta0002skv7qjlpe2cb","slug":"body.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/cm-http.png","post":"citf9olta0002skv7qjlpe2cb","slug":"cm-http.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/first.png","post":"citf9olta0002skv7qjlpe2cb","slug":"first.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/getMessage.png","post":"citf9olta0002skv7qjlpe2cb","slug":"getMessage.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/getMessageResponse.png","post":"citf9olta0002skv7qjlpe2cb","slug":"getMessageResponse.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/message.png","post":"citf9olta0002skv7qjlpe2cb","slug":"message.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/userStatus.png","post":"citf9olta0002skv7qjlpe2cb","slug":"userStatus.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/具体作用.png","post":"citf9olta0002skv7qjlpe2cb","slug":"具体作用.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/架构.png","post":"citf9olta0002skv7qjlpe2cb","slug":"架构.png","modified":1},{"_id":"source/_posts/通过XMPP构建即时通信-2016-09-23/结构图.png","post":"citf9olta0002skv7qjlpe2cb","slug":"结构图.png","modified":1},{"_id":"source/_posts/类型检验-2016-02-19/type.jpg","post":"citf9olx3000dskv77fldnkk3","slug":"type.jpg","modified":1},{"_id":"source/_posts/好好学学number-2016-01-29/number.png","post":"citf9olz2000uskv7cvjqsn0x","slug":"number.png","modified":1},{"_id":"source/_posts/好好学学number-2016-01-29/number2.png","post":"citf9olz2000uskv7cvjqsn0x","slug":"number2.png","modified":1},{"_id":"source/_posts/严格模式-2016-01-21/stracit.png","post":"citf9om1e0013skv72exofdel","slug":"stracit.png","modified":1},{"_id":"source/_posts/numberInJavaScript-2016-06-19/10.png","post":"citf9om2q001dskv7vmxgrmzn","slug":"10.png","modified":1},{"_id":"source/_posts/numberInJavaScript-2016-06-19/11.png","post":"citf9om2q001dskv7vmxgrmzn","slug":"11.png","modified":1},{"_id":"source/_posts/numberInJavaScript-2016-06-19/123.png","post":"citf9om2q001dskv7vmxgrmzn","slug":"123.png","modified":1},{"_id":"source/_posts/numberInJavaScript-2016-06-19/32.png","post":"citf9om2q001dskv7vmxgrmzn","slug":"32.png","modified":1},{"_id":"source/_posts/numberInJavaScript-2016-06-19/456.jpg","post":"citf9om2q001dskv7vmxgrmzn","slug":"456.jpg","modified":1},{"_id":"source/_posts/numberInJavaScript-2016-06-19/64.png","post":"citf9om2q001dskv7vmxgrmzn","slug":"64.png","modified":1},{"_id":"source/_posts/numberInJavaScript-2016-06-19/789.jpg","post":"citf9om2q001dskv7vmxgrmzn","slug":"789.jpg","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image1.jpg","post":"citf9om4e001gskv7413vmrx5","slug":"image1.jpg","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image10.png","post":"citf9om4e001gskv7413vmrx5","slug":"image10.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image11.png","post":"citf9om4e001gskv7413vmrx5","slug":"image11.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image12.png","post":"citf9om4e001gskv7413vmrx5","slug":"image12.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image13.png","post":"citf9om4e001gskv7413vmrx5","slug":"image13.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image14.png","post":"citf9om4e001gskv7413vmrx5","slug":"image14.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image2.jpg","post":"citf9om4e001gskv7413vmrx5","slug":"image2.jpg","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image3.jpg","post":"citf9om4e001gskv7413vmrx5","slug":"image3.jpg","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image4.jpg","post":"citf9om4e001gskv7413vmrx5","slug":"image4.jpg","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image5.png","post":"citf9om4e001gskv7413vmrx5","slug":"image5.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image6.png","post":"citf9om4e001gskv7413vmrx5","slug":"image6.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image7.png","post":"citf9om4e001gskv7413vmrx5","slug":"image7.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image8.png","post":"citf9om4e001gskv7413vmrx5","slug":"image8.png","modified":1},{"_id":"source/_posts/js中函数参数都是按值传递的-2016-02-22/image9.png","post":"citf9om4e001gskv7413vmrx5","slug":"image9.png","modified":1},{"_id":"source/_posts/javascript模块化-2016-07-10/guanxi.png","post":"citf9om83001jskv7j6u3ynqj","slug":"guanxi.png","modified":1},{"_id":"source/_posts/javascript模块化-2016-07-10/process.png","post":"citf9om83001jskv7j6u3ynqj","slug":"process.png","modified":1},{"_id":"source/_posts/javascript模块化-2016-07-10/webpack.png","post":"citf9om83001jskv7j6u3ynqj","slug":"webpack.png","modified":1},{"_id":"source/_posts/DOMContentLoaded-2016-01-08/onLoadVSDomContentLoaded.png","post":"citf9omaz0023skv7pdt82cbb","slug":"onLoadVSDomContentLoaded.png","modified":1},{"_id":"source/_posts/DOMContentLoaded-2016-01-08/zongjie.png","post":"citf9omaz0023skv7pdt82cbb","slug":"zongjie.png","modified":1},{"_id":"source/_posts/2-在HTML中使用JavaScrpt-2016-01-05/deferAsync.png","post":"citf9omeh002eskv7hn9gkv38","slug":"deferAsync.png","modified":1}],"PostCategory":[{"post_id":"citf9olwl0005skv792lxs5e0","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9olwq0009skv7um8xi6gx"},{"post_id":"citf9olww000askv773708h1r","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9olwz000bskv7puhitkas"},{"post_id":"citf9olx3000dskv77fldnkk3","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9olx5000eskv70a4p1f78"},{"post_id":"citf9olxz000lskv79hdug5tj","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9oly4000oskv7sgdm8x9h"},{"post_id":"citf9oly7000pskv7h5cdd2w3","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9olyt000qskv7x0vdis1n"},{"post_id":"citf9olz2000uskv7cvjqsn0x","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9olz9000vskv7jll5cu3t"},{"post_id":"citf9om0n000xskv73jk3i19w","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9om0p000yskv7muau0cws"},{"post_id":"citf9om0z0010skv77qm79pbk","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9om140011skv7ph1dr2aj"},{"post_id":"citf9om1e0013skv72exofdel","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9om1i0014skv7v3bhyfd2"},{"post_id":"citf9om1z0017skv7r3rww98j","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9om230018skv7ai6qkb9y"},{"post_id":"citf9om2c001askv783qfs7ip","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9om2e001bskv7tewfkpd6"},{"post_id":"citf9om2q001dskv7vmxgrmzn","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9om2s001eskv7fcrq93iw"},{"post_id":"citf9om4e001gskv7413vmrx5","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9om4g001hskv7m3m1n0y1"},{"post_id":"citf9om83001jskv7j6u3ynqj","category_id":"citf9om86001kskv7l6s91d6b","_id":"citf9om8i001mskv745sfzuiu"},{"post_id":"citf9om99001nskv7tortwzm5","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9om9b001oskv7bdvsb048"},{"post_id":"citf9om9g001qskv7gmf56r5p","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9om9w001rskv7yb1kvabx"},{"post_id":"citf9oma9001uskv7tatmwdjn","category_id":"citf9om86001kskv7l6s91d6b","_id":"citf9omac001vskv7a7wri6ba"},{"post_id":"citf9omag001xskv7wek3cf29","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9omaj001yskv7slfc98hr"},{"post_id":"citf9omaz0023skv7pdt82cbb","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9omb30024skv70hazqspm"},{"post_id":"citf9ombp0027skv7pi7sg23v","category_id":"citf9oly1000mskv79c3ipwal","_id":"citf9ombt0028skv7nrbufsbk"},{"post_id":"citf9ome8002bskv7czjtkqov","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9omea002cskv7xel7idub"},{"post_id":"citf9omeh002eskv7hn9gkv38","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9omej002fskv7bbg5dgbp"},{"post_id":"citf9omgc002hskv74gylfu49","category_id":"citf9olwn0006skv7412h6v76","_id":"citf9omgk002iskv7cooudgru"}],"PostTag":[{"post_id":"citf9olta0002skv7qjlpe2cb","tag_id":"citf9oltu0003skv79sltfb11","_id":"citf9olty0004skv7wmd9vmq8"},{"post_id":"citf9olwl0005skv792lxs5e0","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9olwp0008skv7qtilbxtj"},{"post_id":"citf9olww000askv773708h1r","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9olx0000cskv7nw717xg2"},{"post_id":"citf9olx3000dskv77fldnkk3","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9olx7000fskv79zibcdv5"},{"post_id":"citf9olxk000gskv7u9swgkrd","tag_id":"citf9olxm000hskv74pgxlfna","_id":"citf9olxv000jskv7w0p3ge39"},{"post_id":"citf9olxk000gskv7u9swgkrd","tag_id":"citf9olxu000iskv7271jsvtc","_id":"citf9olxv000kskv7fhbpfyp2"},{"post_id":"citf9olxz000lskv79hdug5tj","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9oly2000nskv74zguviep"},{"post_id":"citf9oly7000pskv7h5cdd2w3","tag_id":"citf9olyv000rskv7d7eanmhh","_id":"citf9olyx000sskv70h8ft1aj"},{"post_id":"citf9oly7000pskv7h5cdd2w3","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9olyy000tskv7304ga41m"},{"post_id":"citf9olz2000uskv7cvjqsn0x","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9olzc000wskv7s8ts0e3g"},{"post_id":"citf9om0n000xskv73jk3i19w","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om0r000zskv7jt1cl0wv"},{"post_id":"citf9om0z0010skv77qm79pbk","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om150012skv7bzk7293d"},{"post_id":"citf9om1e0013skv72exofdel","tag_id":"citf9olyv000rskv7d7eanmhh","_id":"citf9om1l0015skv7azvxt5au"},{"post_id":"citf9om1e0013skv72exofdel","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om1m0016skv7qjgkg82f"},{"post_id":"citf9om1z0017skv7r3rww98j","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om240019skv7ldc5zp89"},{"post_id":"citf9om2c001askv783qfs7ip","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om2g001cskv7mcex1tbs"},{"post_id":"citf9om2q001dskv7vmxgrmzn","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om2t001fskv7l60xdz3d"},{"post_id":"citf9om4e001gskv7413vmrx5","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om4g001iskv7ha2f822h"},{"post_id":"citf9om83001jskv7j6u3ynqj","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om87001lskv73lw4yzuy"},{"post_id":"citf9om99001nskv7tortwzm5","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9om9c001pskv7nr51ncqp"},{"post_id":"citf9om9g001qskv7gmf56r5p","tag_id":"citf9olyv000rskv7d7eanmhh","_id":"citf9om9y001sskv7rm9i8sy9"},{"post_id":"citf9om9g001qskv7gmf56r5p","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9oma0001tskv7ht31rylo"},{"post_id":"citf9oma9001uskv7tatmwdjn","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9omae001wskv7u8rsx8xf"},{"post_id":"citf9omag001xskv7wek3cf29","tag_id":"citf9olyv000rskv7d7eanmhh","_id":"citf9omal001zskv7qrae8sst"},{"post_id":"citf9omag001xskv7wek3cf29","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9omal0020skv7ac5jh34j"},{"post_id":"citf9omao0021skv7k78oiq9d","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9omar0022skv78eb9u9tz"},{"post_id":"citf9omaz0023skv7pdt82cbb","tag_id":"citf9olyv000rskv7d7eanmhh","_id":"citf9omb50025skv7zhp1g3tt"},{"post_id":"citf9omaz0023skv7pdt82cbb","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9omb60026skv7o644y7uk"},{"post_id":"citf9ombp0027skv7pi7sg23v","tag_id":"citf9olyv000rskv7d7eanmhh","_id":"citf9ombu0029skv7gpn0z1x5"},{"post_id":"citf9ombp0027skv7pi7sg23v","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9ombv002askv7tssp180d"},{"post_id":"citf9ome8002bskv7czjtkqov","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9omeb002dskv7k7if3u90"},{"post_id":"citf9omeh002eskv7hn9gkv38","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9omek002gskv7y9bxzgst"},{"post_id":"citf9omgc002hskv74gylfu49","tag_id":"citf9olwo0007skv7zakp94bw","_id":"citf9omgm002jskv70izext3m"}],"Tag":[{"name":"HTTP","_id":"citf9oltu0003skv79sltfb11"},{"name":"JavaScript","_id":"citf9olwo0007skv7zakp94bw"},{"name":"redux","_id":"citf9olxm000hskv74pgxlfna"},{"name":"react","_id":"citf9olxu000iskv7271jsvtc"},{"name":"jQuery","_id":"citf9olyv000rskv7d7eanmhh"}]}}