├── README.md └── js ├── amd-commonjs-es6modules.js ├── array-concat-push.js ├── array-every-some.js ├── array-filter-map-reduce.js ├── array-foreach.js ├── array-pass-by-val-reference.js ├── array-reduce.js ├── array-slice-splice.js ├── bind-function.js ├── bitwise-operators.js ├── call-apply-function.js ├── closures.js ├── coercion.js ├── conditional-function-declaration.js ├── currying.js ├── dom.js ├── event-bubbling.js ├── event-delegation.js ├── event-handling.js ├── factory-functions.js ├── floating-point-precision.js ├── for-in-with-hasOwnProperty.js ├── getOwnPropertyNames-vs-keys.js ├── getters-setters.js ├── logical-operations-with-string.js ├── method-overloading.js ├── mixins.js ├── new-keyword.js ├── number-maxmin-val.js ├── object-clone.js ├── object-constructor.js ├── object-create.js ├── object-defineProperty.js ├── object-freeze.js ├── object-keys.js ├── object-oriented.js ├── object-prototype.js ├── object-reference.js ├── oloo-pattern.js ├── setTimeout-inside-loop.js ├── shim-polyfill-monkeypatch.js ├── string-methods.js ├── styling.js └── this-keyword.js /README.md: -------------------------------------------------------------------------------- 1 | # js-bits-cn 2 | 3 | > 翻译自 [vasanthk/js-bits](https://github.com/vasanthk/js-bits) 4 | 5 | 通过代码解释 JavaScript 的概念。 6 | 7 | 欢迎`pull request/issue/star`,共同翻译 8 | 9 | ## Menu 10 | 11 | ### 基本 12 | 13 | - [字符串方法](js/string-methods.js) 14 | - [按位操作符](js/bitwise-operators.js) 15 | - [强制类型转换](js/coercion.js) 16 | - [数字的最大和最小值](js/number-maxmin-val.js) 17 | - [带有 string 的逻辑操作](js/logical-operations-with-string.js) 18 | - [JavaScript 中的数字类型(浮点数)](js/floating-point-precision.js) 19 | 20 | ### Array 21 | 22 | - [Array concat() 和 push() 方法](js/array-concat-push.js) 23 | - [Array every() 和 some() 方法](js/array-every-some.js) 24 | - [Array filter()、map() 和 reduce()](js/array-filter-map-reduce.js) 25 | - [Array 的 forEach() 方法](js/array-foreach.js) 26 | - [理解 Array 中的 通过值传递 和 通过引用传递](js/array-pass-by-val-reference.js) 27 | - [Array 的 reduce() 方法](js/array-reduce.js) 28 | - [Array 的 slice() 和 splice() 方法](js/array-slice-splice.js) 29 | 30 | ### DOM 31 | 32 | - [DOM API 操作](js/dom.js) 33 | - [JavaScript 中的样式操作](js/styling.js) 34 | - [事件冒泡](js/event-bubbling.js) 35 | - [事件委托](js/event-delegation.js) 36 | - [事件处理](js/event-handling.js) 37 | 38 | ### 作用域 39 | 40 | - [Apply 和 Call 方法](js/call-apply-function.js) 41 | - [Bind 方法](js/bind-function.js) 42 | - [闭包](js/closures.js) 43 | - [this 关键字](js/this-keyword.js) 44 | 45 | ### 面向对象 46 | 47 | - [关于 new 关键字的一些事](js/new-keyword.js) 48 | - [对象的克隆](js/object-clone.js) 49 | - [详解对象的创建(构造方法和原型链)](js/object-constructor.js) 50 | - [JavaScript 面向对象编程](js/object-oriented.js) 51 | - [对象的原型链](js/object-prototype.js) 52 | - [对象的引用](js/object-reference.js) 53 | - [OLOO 设计模式探索](js/oloo-pattern.js) 54 | 55 | ### 对象的属性 56 | 57 | - [定义属性(Object.defineProperty 方法)](js/object-defineProperty.js) 58 | - [冻结对象 (Object.freeze 方法)](js/object-freeze.js) 59 | - [通过 Object.keys 遍历对象的属性](js/object-keys.js) 60 | - [for..in 循环和 hasOwnProperty](js/for-in-with-hasOwnProperty.js) 61 | - [getter 方法和 setter 方法](js/getters-setters.js) 62 | 63 | ### 其他 64 | 65 | - [柯里化](js/currying.js) 66 | - [AMD、CommonJS 和 ES6 模块机制的使用](js/amd-commonjs-es6modules.js) 67 | - [条件表达式内函数声明](js/conditional-function-declaration.js) 68 | - [工厂方法](js/factory-functions.js) 69 | - [在 for() 循环内 setTimeout()](js/setTimeout-inside-loop.js) 70 | - [Shim vs Polyfill vs Monkey patch](js/shim-polyfill-monkeypatch.js) 71 | - [方法重载](js/method-overloading.js) 72 | - [JavaScript 中的 Mixins](js/mixins.js) 73 | 74 | ## Todo 75 | 76 | - [x] [AMD CommonJS and ES6 Modules Usage](js/amd-commonjs-es6modules.js) 77 | - [x] [Array concat() push()](js/array-concat-push.js) 78 | - [x] [Array every() some()](js/array-every-some.js) 79 | - [x] [Array filter() map() reduce()](js/array-filter-map-reduce.js) 80 | - [x] [Array forEach()](js/array-foreach.js) 81 | - [x] [Array pass by val vs reference](js/array-pass-by-val-reference.js) 82 | - [x] [Array reduce()](js/array-reduce.js) 83 | - [x] [Array slice() splice()](js/array-slice-splice.js) 84 | - [x] [Apply & Call function](js/call-apply-function.js) 85 | - [x] [Bind function](js/bind-function.js) 86 | - [x] [Bitwise operators](js/bitwise-operators.js) 87 | - [x] [Closures](js/closures.js) 88 | - [x] [Coercion](js/coercion.js) 89 | - [x] [Conditional function declaration](js/conditional-function-declaration.js) 90 | - [x] [Currying](js/currying.js) 91 | - [x] [DOM](js/dom.js) 92 | - [x] [Event Bubbling](js/event-bubbling.js) 93 | - [x] [Event Delegation](js/event-delegation.js) 94 | - [x] [Event Handling](js/event-handling.js) 95 | - [x] [Factory Functions](js/factory-functions.js) 96 | - [x] [Floating point precision](js/floating-point-precision.js) 97 | - [x] [for-in with hasOwnProperty](js/for-in-with-hasOwnProperty.js) 98 | - [x] [Getters and Setters](js/getters-setters.js) 99 | - [x] [Logical operations with string](js/logical-operations-with-string.js) 100 | - [x] [Method Overloading](js/method-overloading.js) 101 | - [x] [Mixins](js/mixins.js) 102 | - [x] [new keyword](js/new-keyword.js) 103 | - [x] [Number Max Min val](js/number-maxmin-val.js) 104 | - [x] [Object clone](js/object-clone.js) 105 | - [x] [Object constructor](js/object-constructor.js) 106 | - [x] [Object create()](js/object-create.js) 107 | - [x] [Object defineProperty](js/object-defineProperty.js) 108 | - [x] [Object freeze](js/object-freeze.js) 109 | - [x] [Object keys](js/object-keys.js) 110 | - [x] [Object oriented concepts](js/object-oriented.js) 111 | - [x] [Object prototype](js/object-prototype.js) 112 | - [x] [Object references](js/object-reference.js) 113 | - [x] [OLOO pattern](js/oloo-pattern.js) 114 | - [x] [setTimeout inside a loop](js/setTimeout-inside-loop.js) 115 | - [x] [Shim vs Polyfill vs Monkey patch](js/shim-polyfill-monkeypatch.js) 116 | - [x] [String methods](js/string-methods.js) 117 | - [x] [Styling](js/styling.js) 118 | - [x] [this keyword](js/this-keyword.js) 119 | -------------------------------------------------------------------------------- /js/amd-commonjs-es6modules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 模块(Modules) 3 | * 4 | * 为什么模块化很重要? 5 | * - 它鼓励我们将代码分割成相对独立的小模块,而不是一大坨 6 | * - 提高了代码的可测试性,并且在运行时模块也可是被 mock 替代 7 | * - 提高了可维护性,模块越小越易懂 8 | * 9 | * AMD vs CommonJS vs ES6 Modules 10 | * 11 | * @参考资料: 12 | * http://stackoverflow.com/questions/21021621/difference-between-requirejs-and-commonjs 13 | * https://www.airpair.com/javascript/posts/the-mind-boggling-universe-of-javascript-modules 14 | * http://javascript.tutorialhorizon.com/2014/09/01/understanding-nodejs-module-exports-and-require/ 15 | * http://www.2ality.com/2014/09/es6-modules-final.html 16 | * 17 | * 模块设计模式: 18 | * http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html 19 | */ 20 | 21 | /** 22 | * AMD - 定义异步模块 23 | * 24 | * 专门针对浏览器环境而设计。通过 AMD 模块设计,各模块在应用中只有被需要时才会异步加载。 25 | * 一旦模块被加载完成,它就会被缓存起来,之后再使用时可以直接运行。 26 | * AMD 模块机制使用原生的 JavaScript,因此不依赖于第三方工具就可以正常使用。 27 | * 在 CommonJS 里,你只能 export 一个对象;而在 AMD 里,可以 export 任意 JavaScript 类型的东西。这意味着,你可以暴露一个构造函数,或者一个 array。 28 | * 至于模块的加载,AMD 可以按需加载其他文件,比如:HTML 模板, CSS, Text, JS and Binary files 29 | * 30 | * 鉴于 AMD 模块要及时获取依赖,因此在定义时,需要声明依赖,并将你的函数使用一层 wrapper 包裹起来, 31 | * 在回调中编写你的代表。这在模块定义时写起来会有点蛋疼。 32 | * Since AMD modules need to be able to fetch dependencies just-in-time, they need a callback wrapper around a module 33 | * which produces slightly more overhead in your module definition. 34 | * 35 | * 多个模块可以同时加载。 36 | * 异步加载是一个复杂的方案,如果没有良好的设计,很有可能就会引起各个模块之间的竞争关系。而异步加载模块的执行顺序是无法保障的 37 | * 38 | * 如何使用: 39 | * 编写模块时,在回调里通过 return 进行模块的暴露。 40 | * 在使用时,通过 array 进行模块的引用,而回调函数的参数则代表着调用的模块 41 | * 42 | */ 43 | 44 | // foo.js 45 | // 定义一个叫做 foo 的模块 46 | define('foo', function () { 47 | return { 48 | method: function () { 49 | return 'food method result'; 50 | } 51 | } 52 | }); 53 | 54 | // bar.js 55 | // 定义一个叫做 bar 的模块, 并且依赖于 foo 模块 56 | define('bar', ['foo'], function (Foo) { 57 | return { 58 | barMethod: function () { 59 | return 'bar method result'; 60 | }, 61 | fooMethod: function () { 62 | return Foo.method(); 63 | } 64 | }; 65 | }); 66 | 67 | // 加载 bar 模块,并在回调中使用 68 | require(['bar'], function (bar) { 69 | // Do something with fetched dependency 70 | bar.barMethod(); 71 | bar.fooMethod(); 72 | }); 73 | 74 | /** 75 | * CommonJS 76 | * 77 | * CommonJS 最初是针对服务器端环境(NodeJS)而设计的。 78 | * 鉴于 CommonJS 模块制度不需要及时获取到依赖,因此不需要任何回调结构包裹你的模块。 79 | * 这使得模块看起来更加轻盈小巧 80 | * 81 | * CommonJS 不能直接在浏览器环境下运行。 82 | * 反之,它需要被预编译。在这个过程中,调用所需的模块并解析。 83 | * 使用 CommonJS 机制编写的模块在使用时直接引用就好,并且不会立即执行。(而是每个依赖都会造成阻塞) 84 | * CommonJS modules are always included directly and can’t be fetched just-in-time. 85 | * 86 | * CommonJS 是 Node.js 和 NPM 官方采用的方案。 87 | * 这意味着任何使用 CommonJS 定义的模块都关联着 NPM 的核心 88 | * 89 | * 如何使用: 90 | * 无论是否私有,只要通过 module.exports 就会全部暴露出去 91 | * 关于模块的使用,则要通过 require 方法,参数为模块文件的路径 92 | * 93 | */ 94 | 95 | // foo.js 96 | // 定义 foo 模块 97 | var foo = function () { 98 | return 'foo method result'; 99 | }; 100 | 101 | // 将 foo 暴露出去 102 | exports.method = foo; 103 | 104 | // bar.js 105 | // 定义 bar 模块,依赖于 foo 模块 106 | var Foo = require('foo'); 107 | var barMethod = function () { 108 | return 'barMethod result'; 109 | }; 110 | var fooMethod = function () { 111 | return Foo.method(); 112 | }; 113 | 114 | exports.barMethod = barMethod; 115 | exports.fooMethod = fooMethod; 116 | 117 | 118 | // Require bar 模块 119 | var bar = require('bar'); 120 | // Do something with the fetched dependency 121 | bar.barMethod(); 122 | bar.fooMethod(); 123 | 124 | /** 125 | * Hybrid 126 | * 127 | * 一些 AMD loaders 提供了基于 AMD 和 CommonJS 之间的混合方式。 128 | * 这种方法在运行时进行检测,并确定哪些模块需要预加载。因此虽然看起来像是同步的,但实际上不是。 129 | * 130 | */ 131 | 132 | define(function (require, exports, module) { 133 | var math = require('lib/math'); 134 | exports.max = math.max; 135 | exports.add = math.add; 136 | }); 137 | 138 | 139 | /** 140 | * ES6 Modules 141 | * 142 | * ES6 的模块机制支持同步/异步两种方法, 143 | * 更棒的是,它也同时支持浏览器端和服务端两种环境。 144 | */ 145 | 146 | // 暴露模块 147 | // exporter.js 148 | export function someMethod() { 149 | // Do some stuff 150 | } 151 | 152 | export var another = {}; 153 | 154 | // 引用模块 155 | // importer.js 156 | import { someMethod, another as newName } from './exporter'; 157 | 158 | someMethod(); 159 | // typeof newName == 'object'; 160 | 161 | 162 | // 暴露/引用单个模块 163 | // export-default.js 164 | export default function foo() { 165 | console.log('foo'); 166 | } 167 | 168 | // import-default.js 169 | import customName from './export-default'; 170 | customName(); // -> 'foo' 171 | 172 | 173 | // ES6 模块机制所支持的所有语法样式 174 | import 'jquery'; // 单纯的引用一个模块 175 | import $ from 'jquery'; // 引用模块里默认的某个命名输出 176 | import { $ } from 'jquery'; // 引用模块里的命名某个输出 177 | import { $ as jQuery } from 'jquery'; // 引用模块里的命名某个输出,并重新定义命名 178 | 179 | export var x = 42; // 暴露一个变量 180 | export function foo() {}; // 暴露一个命名函数 181 | 182 | export default 42; // 暴露一个默认输出 183 | export default function foo() {}; // 暴露一个函数作为默认输出 184 | 185 | var encrypt = {}; 186 | var decrypt = {}; 187 | export { encrypt }; // 暴露一个存在的变量 188 | export { decrypt as dec }; // 暴露一个变量,并重新命名 189 | export { encrypt as en } from 'crypto'; // 从其他模块引用之后重新命名,再暴露出去 190 | export * from 'crypto'; // 从其他模块引用所有的模块,再暴露出去 191 | 192 | import * as crypto from 'crypto'; // 从其他模块引用所有输出,并命名为 crypto 193 | 194 | // 需要注意的是,所有合法的声明都可以暴露出去。在 ES6 语法里,这包括了 class, const 和 let。 195 | -------------------------------------------------------------------------------- /js/array-concat-push.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Array push() and concat() 3 | * 就性能而言:concat() 比 push() 快了约 40% 4 | * 5 | * 参考资料: 6 | * http://gunnariauvinen.com/difference-between-concat-and-push-in-javascript/ 7 | * http://davidwalsh.name/combining-js-arrays 8 | * 9 | * Tip: push() 将元素添加在 array 尾部。如果要添加到头部,则可以使用 unshift() 方法。 10 | * 11 | * 性能比较: 12 | * https://jsperf.com/array-prototype-push-apply-vs-concat/20 13 | */ 14 | 15 | // PUSHES ONE ARRAY INTO ANOTHER 16 | // Array.push() 方法将修改原 array 17 | // 同时该方法返回修改过后的 array 的长度 18 | (function () { 19 | var testArr = [1, 2, 3]; 20 | var res = testArr.push(4, 5, 6); 21 | 22 | console.log(res); // 6 23 | console.log(testArr); // [1, 2, 3, 4, 5, 6] 24 | })(); 25 | 26 | // MERGES ARRAYS 27 | // Array.concat() 返回一个新 array,原 array 保持不变。 28 | // 要注意的是,并没有复制对象到新的 array 中,而是复制了对象的引用。 29 | (function () { 30 | var test = [1, 2, 3]; // [1, 2, 3] 31 | var example = [{test: 'test value'}, 'a', 'b', 4, 5]; 32 | var concatExample = test.concat(example); // [1, 2, 3, { test: 'test value'}, 'a', 'b', 4, 5] 33 | 34 | // 修改一下对象的值 35 | example[0].test = 'a changed value'; 36 | console.log(concatExample[3].test); // 会发现在合并过后的新 array 里,对象的值也发生了改变 Object { test: "a changed value"} 37 | example[1] = 'dog'; 38 | console.log(concatExample[4]); // 'a' 39 | })(); 40 | 41 | // MERGE ARRAY USING push() 42 | // 通过 apply() 和 push() 来合并两个 array 43 | (function () { 44 | var a = [1, 2]; 45 | var b = ['x', 'y']; 46 | 47 | // 不能直接使用 a.push(b) 方式,它仅仅返回 [1, 2, ['x', 'y']] 48 | a.push.apply(a, b); 49 | console.log(a); // [1, 2, 'x', 'y'] 50 | // 相当于: a = a.concat(b); 51 | })(); 52 | -------------------------------------------------------------------------------- /js/array-every-some.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Array every() and some() (而不是使用 forEach) 3 | * 4 | * 有时候,我们可能需要对一个 array 进行循环判断,查看其是否满足一些条件(或者其他的需求)。 5 | * forEach 方法的一个限制是,使用中无法跳出循环,结果,有时候会看见有开发者退而使用了 for 循环,或者在迭代时使用不必要的 array 元素。 6 | * 7 | * 一个更好的选择是使用较为小众的 every() 和 some() 方法进行遍历, 8 | * 每一次的遍历,都会将元素代入回调,最终返回 true 或者 false 9 | * 10 | * 浏览器对 every 和 some 的支持与 forEach 的一样。 11 | * 12 | * @参考资料: 13 | * http://engineering.wix.com/2015/04/21/javascript-the-extra-good-parts/ 14 | * https://coderwall.com/p/_ggh2w/the-array-native-every-filter-map-some-foreach-methods 15 | * 16 | */ 17 | 18 | // 只要有任意一次遍历返回 true,some() 就会中断,并返回 true;否则返回 false 19 | (function () { 20 | var ar = ['Lara', 'Sachin', 'De Villiers']; 21 | ar.some(function (v) { 22 | if (v === 'Sachin') { 23 | return true; 24 | } 25 | console.log('Great cricketers: ' + v); 26 | }); 27 | })(); 28 | 29 | // 只要有任意一次遍历返回 false,every() 就会中断,并返回 false;否则返回 true 30 | (function () { 31 | var ar = ['Hans Zimmer', 'Bill Clinton', 'Clint Mansell']; 32 | ar.every(function (v) { 33 | if (v === 'Bill Clinton') { 34 | return false; 35 | } 36 | console.log('Great Composers: ' + v); 37 | }); 38 | })(); 39 | 40 | // every() 和 some() 实例 41 | (function () { 42 | function isBigEnough(element) { 43 | return element >= 10; 44 | } 45 | 46 | function isBigEnough2(element) { 47 | return element >= 1; 48 | } 49 | 50 | var passed = [2, 5, 8, 1, 4].some(isBigEnough); 51 | console.log('some: For [2, 5, 8, 1, 4] are the values larger or equal to 10 ? ' + passed); 52 | // some: For [2, 5, 8, 1, 4] are the values larger or equal to 10 ? false 53 | 54 | var passed = [12, 5, 8, 1, 4].some(isBigEnough); 55 | console.log('some: For [12, 5, 8, 1, 4] are the values larger or equal to 10 ? ' + passed); 56 | // some: For [12, 5, 8, 1, 4] are the values larger or equal to 10 ? true 57 | 58 | var passed = [12, 5, 8, 1, 4].every(isBigEnough); 59 | console.log('every: For [12, 5, 8, 1, 4] are "ALL" the values larger or equal to 10 ? ' + passed); 60 | // every: For [12, 5, 8, 1, 4] are "ALL" the values larger or equal to 10 ? false 61 | 62 | var passed = [12, 5, 8, 1, 4].every(isBigEnough2); 63 | console.log('every: For [12, 5, 8, 1, 4] are "ALL" the values larger or equal to 1 ? ' + passed); 64 | // every: For [12, 5, 8, 1, 4] are "ALL" the values larger or equal to 1 ? true 65 | 66 | })(); 67 | -------------------------------------------------------------------------------- /js/array-filter-map-reduce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Array filter(), map() and reduce() 3 | * 4 | * @参考资料: 5 | * http://danmartensen.svbtle.com/javascripts-map-reduce-and-filter 6 | * http://elijahmanor.com/reducing-filter-and-map-down-to-reduce/ 7 | * http://cryto.net/~joepie91/blog/2015/05/04/functional-programming-in-javascript-map-filter-reduce/ 8 | * 9 | * 进阶学习: 10 | * JavaScript 内部实现 filter, map, reduce 的方式: 11 | * http://matthewodette.com/map-filter-and-fold-in-javascript/ 12 | */ 13 | 14 | /** 普通的 for() 循环 15 | * for 循环在处理大型数组时依旧有用武之地(例如,拥有 1000 个元素的数组) 16 | * 或者需要在循环时根据条件来中断的话,for 也依旧很好用 17 | */ 18 | (function () { 19 | var array = [1, 2, 3, 4]; 20 | var models = []; 21 | for (var i = 0; i < array.length; i++) { 22 | if (array.indexOf(array[i]) % 2 === 0) { 23 | models.push(array[i]); 24 | } 25 | } 26 | })(); 27 | 28 | /** Array.map() 29 | * 30 | * 什么时候使用: 31 | * 当你想把一个 array 中的所有元素进行转换,并返回新的数组时 32 | * 33 | * map 方法做了什么: 34 | * 从左往右遍历数组,将各元素分别代入回调函数进行调用,并返回回调函数的返回值,最终组成一个新的数组 35 | * 36 | * 举个栗子:把 一组华氏温度 转换成 一组摄氏温度 37 | * 38 | * 语法: 39 | * array.map(function(elem, index, array) { 40 | * ... 41 | * }, thisArg); 42 | * 43 | * elem: array 中的各个元素 44 | * index: 偏移,从左往右递增 45 | * array: 调用 map 方法的数组 46 | * thisArg: 作为回调中的作用域(this) 47 | */ 48 | 49 | (function () { 50 | var farenheit = [0, 32, 45, 55, 67, 79, 94, 105]; 51 | var celcius = farenheit.map(function (elem) { 52 | return Math.round((elem - 32) * 5 / 9); 53 | }); 54 | 55 | console.log(celcius); // [-18, 0, 7, 13, 19, 26, 34, 41] 56 | })(); 57 | 58 | /** Array.filter() 59 | * 60 | * 什么时候使用: 61 | * 从 array 中过滤不需要的元素时 62 | * 63 | * filter 方法做了什么: 64 | * 与 map 方法类似,从左往右遍历数组,将各元素分别代入回调函数进行调用。 65 | * 但回调函数的返回值必须是一个 boolean,以此来确定当前循环的元素是否要过滤掉。返回 false 则过滤,否则保留 66 | * 但要注意的是,在循环完毕之后,将返回一个新的数组,而只有使回调函数返回了 true 的元素才会在新数组里。 67 | * 回调函数的参数和 map() 方法一样。 68 | * 69 | * 举个栗子:移除数组中重复的元素 70 | * 71 | * 语法: 72 | * array.filter(function(elem, index, array) { 73 | * ... 74 | * }, thisArg); 75 | * 76 | * elem: array 中的各个元素 77 | * index: 偏移,从左往右递增 78 | * array: 调用 filter 方法的数组 79 | * thisArg: 作为回调中的作用域(this) 80 | */ 81 | 82 | (function () { 83 | var arr = [1, 2, 3, 4, 5, 3, 7, 2]; 84 | var uniqueArr = arr.filter(function (elem, i, arr) { 85 | return arr.indexOf(elem) === i; 86 | }); 87 | 88 | console.log(uniqueArr); 89 | })(); 90 | 91 | /** Array.reduce() 92 | * 93 | * 什么时候使用: 94 | * 当你想对一个 array 中的元素进行累加或者拼接时 95 | * 96 | * reduce 方法做了什么: 97 | * 与 map 方法类似,从左往右遍历数组,将各元素分别代入回调函数进行调用。 98 | * 但回调函数的返回值会作为下一次遍历时回调函数的参数,在遍历完所有元素之后,返回最终结果 99 | * 100 | * 举个栗子:计算 2014 年各国家发射火箭数目的综合 101 | * 102 | * 语法: 103 | * array.reduce(function(prevVal, elem, index, array) { 104 | * ... 105 | * }, initialValue); 106 | * 107 | * prevVal: 上一个回调返回的结果 108 | * elem: array 中的元素 109 | * index: 偏移,从左往右递增 110 | * array: 调用 reduce 方法的数组 111 | * initialValue: 初始化的值,作为第一个回调的参数 112 | * 113 | */ 114 | (function () { 115 | var rockets = [ 116 | {country: 'Russia', launches: 32}, 117 | {country: 'US', launches: 23}, 118 | {country: 'China', launches: 16}, 119 | {country: 'Europe(ESA)', launches: 7}, 120 | {country: 'India', launches: 4}, 121 | {country: 'Japan', launches: 3} 122 | ]; 123 | 124 | var sum = rockets.reduce(function (prevVal, elem) { 125 | return prevVal + elem.launches; 126 | }, 0); 127 | 128 | console.log(sum); 129 | })(); 130 | -------------------------------------------------------------------------------- /js/array-foreach.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Array foreach() 3 | * 4 | * @参考资料: 5 | * http://stackoverflow.com/questions/23614054/javascript-nuances-of-myarray-foreach-vs-for-loop 6 | * http://javascriptplayground.com/blog/2012/06/writing-javascript-polyfill/ 7 | * http://www.2ality.com/2011/04/iterating-over-arrays-and-objects-in.html 8 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach 9 | * 10 | */ 11 | 12 | // Polyfill for forEach() 13 | /** 14 | * [forEach 函数] 15 | * @method forEach 让数组的每一项都执行一次给定的函数 16 | * @param {Function} callback 每次遍历的回调函数 17 | * @param {Boolean} thisArg 作用域。默认为当前 this 18 | * @return {[type]} 无返回值 19 | */ 20 | 21 | Array.prototype.forEach = function (callback, thisArg) { 22 | if (typeof callback !== 'function') { 23 | throw new TypeError(callback + ' is not a function.'); 24 | } 25 | // this 代表调用 forEach 方法的 array 26 | var len = this.length; // Array length 27 | for (var i = 0; i < len; i++) { 28 | callback.call(thisArg, this[i], i, this); 29 | } 30 | }; 31 | 32 | // 举个栗子 33 | function logArrayElements(currElement, currIndex, originalArray) { 34 | console.log('a[' + currIndex + '] = ' + currElement); 35 | } 36 | 37 | // Note there is no member at 2 so it isn't visited 38 | [2, 5, , 9].forEach(logArrayElements); 39 | // logs: 40 | // a[0] = 2 41 | // a[1] = 5 42 | // a[3] = 9 43 | -------------------------------------------------------------------------------- /js/array-pass-by-val-reference.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 理解 arrays - 通过值传递 vs 通过引用传递 3 | * 4 | * @参考资料: 5 | * http://orizens.com/wp/topics/javascript-arrays-passing-by-reference-or-by-value/ 6 | */ 7 | 8 | // 通过引用传递 9 | // 在默认情况下,array 作为参数传递给函数时,是作为引用传递的 10 | var a = [3, 'my new post', {345}]; 11 | 12 | function renderData(a) { 13 | a.push(4); 14 | } 15 | 16 | renderData(a); 17 | alert(a); // push 方法返回一个新 array [3, 'my new post', {345}, 4] 18 | 19 | // 通过值传递 20 | // 通过调用原生的 array 方法 - slice(),可以达到通过值传递的效果 21 | // 22 | // 一般来说,slice() 会克隆 array,然后使用新 array 的引用。需要注意的是: 23 | // - array 中含有对象时,针对对象的引用(而不是真正的对象),slice 会复制一份引用到新的 array 里 24 | // 25 | // 因此,无论是之前的 array 还是复制出的新 array,都指向相同的对象。如果对象改变了,那么两个 array 内的对象都会改变。 26 | // 27 | // 举栗子: 28 | !function() { 29 | 30 | // object 31 | var obj = {"abc": 456}; 32 | var arr = [obj].slice(); // [{"abc": 456}],复制自 [obj],且 arr 内的 obj 是引用 {"abc": 456} 33 | obj.abc = 4567; // 改变原始对象 34 | console.log(arr, obj); // [{"abc": 4567}] {"abc": 4567} // 会发现 arr 内的 obj 也被改变 35 | 36 | // array 37 | var oldarr = [456]; 38 | var arr = [oldarr].slice(); // [[456]] 39 | oldarr[0] = 4567; 40 | console.log(arr, oldarr); // [[4567]] [4567] 41 | 42 | }() 43 | 44 | // - 而对于 array 中的 String 和 number 类型,则直接复制到新数组里 45 | // 他们的改变互不影响 46 | // 举栗子: 47 | !function() { 48 | 49 | // 数组中有串 String 50 | var oldarr = ['abc']; 51 | var arr = oldarr.slice(); // ['abc'] 52 | oldarr[0] = 'abcd'; 53 | console.log(arr, oldarr); // ['abc'] ['abcd'] // 直接复制的值而不是引用,因此互不影响 54 | 55 | // number in array 56 | var oldarr = [123, 456, 789]; 57 | var arr = oldarr.slice(0, 2); // [123, 456] 58 | oldarr[1] = 123456789; 59 | console.log(arr, oldarr); // [123, 456] [123, 123456789, 789] 60 | 61 | }() 62 | 63 | var a = [3, 'my new post', {345}]; 64 | 65 | function renderData(a) { 66 | a.push(4); 67 | } 68 | 69 | renderData(a.slice()); 70 | alert(a); // [3, 'my new post', {345}] 71 | 72 | // 如果你确实想通过值传递来复制数组中的对象,那么需要使用 JSON.parse(JSON.stringify(array)) 73 | // 注意:在复制 functions/dates 对象的时候会有一些警告 74 | // 更多内容请查看:https://github.com/vasanthk/js-bits/blob/master/js/object-clone.js 75 | var tempArray = JSON.parse(JSON.stringify(mainArray)); 76 | -------------------------------------------------------------------------------- /js/array-reduce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 使用 reduce() 方法迭代数组 3 | * 4 | * 通过 forEach 来迭代数组,是个很好的办法,而且看起来似乎比 for 循环更有函数式的感觉。 5 | * 之所以用 “看起来” 形容,是因为 forEach 循环只能在循环内部通过其他函数的副作用来返回值,或者是修改原有的数组。 6 | * 而更加函数式的方式则是使用 map 或者 reduce 这样的方法,这些方法不依赖于副作用,并不会改动原有数组。 7 | * 8 | * reduce 和 map 方法对浏览器的支持性和 forEach 一样。 9 | * 10 | * 当人们谈论 “Map Reduce” 时,通常是指一种模式:遍历一个集合调用 reduce 11 | * 12 | * @参考资料: 13 | * http://engineering.wix.com/2015/04/21/javascript-the-extra-good-parts/ 14 | * http://danmartensen.svbtle.com/javascripts-map-reduce-and-filter 15 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce 16 | */ 17 | 18 | // 使用 forEach() 19 | (function () { 20 | var ar = [1, 2, 3, 4, 5]; 21 | var sum = 0; 22 | ar.forEach(function (v) { 23 | sum += v; 24 | }); 25 | console.log(sum); 26 | })(); 27 | 28 | // 使用 reduce() 29 | (function () { 30 | var ar = [1, 2, 3, 4, 5]; 31 | // 外部没有名为 sum 的变量 32 | console.log('sum:', ar.reduce(function (sum, v) { 33 | return sum + v; 34 | }, 0)); 35 | // reduce() 语法:arr.reduce(callback()[, initialValue]) 36 | // callback 语法:fn(previousValue, currentValue, index, array) 37 | })(); 38 | -------------------------------------------------------------------------------- /js/array-slice-splice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Array slice() vs splice() 3 | * 4 | * 1. splice() 方法返回由 被删除的元素 组成的 array;slice() 方法在新 array 中返回被选择的元素 5 | * 6 | * 2. splice() 方法更改原有的 array,slice() 则不动原 array 7 | * 8 | * 3. splice() 可接受多个数字作为参数: 9 | * 第一个参数:index,必需,代表起始位置 10 | * 第二个参数:可选,代表移除几个元素。如果不传,从 index 到尾部的元素都会被移除。 11 | * 第三个 ~ 第n个元素:可选。往数组里新添加的元素。 12 | * 13 | * 4. slice() 则可接受两个参数: 14 | * 第一个参数:必需,代表开始选择的位置。 15 | * 第二个参数:可选,代表结束的位置。不传则默认选择到数组尾部。 16 | * 17 | * @参考资料: 18 | * http://www.tothenew.com/blog/javascript-splice-vs-slice/ 19 | */ 20 | 21 | // Array.splice() 22 | // 语法: array.splice(start, deleteCount[, item1[, item2[, ...]]]) 23 | var array = [1, 2, 3, 4, 5]; 24 | console.log(array.splice(2)); 25 | // 返回 [3, 4, 5], 将移除的元素放在新数组中返回 26 | 27 | console.log(array); 28 | // 返回 [1, 2], 原有数组已经被改变 29 | 30 | var array2 = [6, 7, 8, 9, 0]; 31 | console.log(array2.splice(2, 1)); 32 | // [8] 33 | 34 | console.log(array2.splice(2, 0)); 35 | //[] , 没有元素被移除 36 | 37 | console.log(array2); 38 | // [6,7,9,0] 39 | 40 | var array3 = [11, 12, 13, 14, 15]; 41 | console.log(array3.splice(2, 1, "Hello", "World")); 42 | // [13] 43 | 44 | console.log(array3); 45 | // [11, 12, "Hello", "World", 14, 15] 46 | 47 | // -6 -5 -4 -3 -2 -1 -- 倒序 index 48 | // | | | | | | 49 | var array4 = [16, 17, 18, 19, 20]; 50 | // | | | | | | 51 | // 0 1 2 3 4 5 -- 正序 index 52 | 53 | console.log(array4.splice(-2, 1, "me")); 54 | // [19] 55 | 56 | console.log(array4); 57 | // [16, 17, 18, "me", 20] 58 | 59 | 60 | // 如果第一个参数 NaN,则会被当做 0 来对待 61 | var array5 = [21, 22, 23, 24, 25]; 62 | console.log(array5.splice(NaN, 4, "NaN is Treated as 0")); 63 | // [21,22,23,24] 64 | 65 | console.log(array5); 66 | // ["NaN is Treated as 0",25] 67 | 68 | 69 | // 如果第二个参数小于 0,或者是 NaN,则会被当做 0 对待 70 | var array6 = [26, 27, 28, 29, 30]; 71 | console.log(array6.splice(2, -5, "Hello")); 72 | // [] 73 | 74 | console.log(array6); 75 | // [26,27,"Hello",28,29,30] 76 | 77 | console.log(array6.splice(3, NaN, "World")); 78 | // [] 79 | 80 | console.log(array6); 81 | // [26,27,"Hello","World",28,29,30] 82 | 83 | 84 | // 如果第一或者第二个参数大于数组的长度,则会使用 数组长度作为参数 85 | var array7 = [31, 32, 33, 34, 35]; 86 | console.log(array7.splice(23, 3, "Add Me")); 87 | // [] 88 | 89 | console.log(array7); 90 | // [31,32,33,34,35,"Add Me"] 91 | 92 | console.log(array7.splice(2, 34, "Add Me Too")); 93 | // [33,34,35,"Add Me"] 94 | 95 | console.log(array7); 96 | // [31,32,"Add Me Too"] 97 | 98 | 99 | // slice() 可以接收两个参数: 100 | // arr.slice([begin[, end]]) 101 | // 获取的结果包含了 begin 位置的值,但不包含 end 位置的值,即 [begin, end) 102 | var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']; 103 | var citrus = fruits.slice(1, 3); 104 | // citrus 为 ['Orange','Lemon'] 105 | 106 | var array = [1, 2, 3, 4, 5]; 107 | console.log(array.slice(2)); 108 | // 返回 [3, 4, 5] 109 | 110 | console.log(array.slice(-2)); 111 | // [4, 5] 112 | console.log(array); 113 | // [1, 2, 3, 4, 5], 原有数组没有受到影响 114 | 115 | var array2 = [6, 7, 8, 9, 0]; 116 | console.log(array2.slice(2, 4)); 117 | // [8, 9] 118 | 119 | console.log(array2.slice(-2, 4)); 120 | // [9] 121 | 122 | console.log(array2.slice(-3, -1)); 123 | // [8, 9] 124 | 125 | console.log(array2); 126 | // [6, 7, 8, 9, 0] 127 | 128 | // 任意一个参数是 NaN 时,都会作为 0 处理。 129 | var array3 = [11, 12, 13, 14, 15]; 130 | console.log(array3.slice(NaN, NaN)); 131 | // [] 132 | 133 | console.log(array3.slice(NaN, 4)); 134 | // [11,12,13,14] 135 | 136 | console.log(array3); 137 | // [11,12,13,14,15] 138 | 139 | // 任意一个参数大于数组长度时,会作为数组长度处理 140 | var array4 = [16, 17, 18, 19, 20]; 141 | console.log(array4.slice(23, 24)); 142 | // [] 143 | 144 | console.log(array4.slice(23, 2)); 145 | // [] 146 | 147 | console.log(array4.slice(2, 23)); 148 | // [18,19,20] 149 | 150 | console.log(array4); 151 | // [16,17,18,19,20] 152 | 153 | // 第一个参数 undefined 时,作为 0 处理 154 | var array5 = [21, 22, 23, 24, 25]; 155 | console.log(array5.slice(undefined, 2)); 156 | // [21, 22] 157 | 158 | // 通过不传参,可以起到复制数组的作用 159 | var array6 = array5.slice(); 160 | console.log(array6); 161 | // [21, 22, 23, 24, 25] 162 | -------------------------------------------------------------------------------- /js/bind-function.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function.prototype.bind() 3 | * 4 | * bind 方法实际上返回了一个新的函数,新函数里的 this,由 bind 时传入的参数决定 5 | * 6 | * 针对低版本浏览器也有兼容性的实现 (< IE9) 7 | * 8 | * @参考资料: 9 | * https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/ 10 | * http://stackoverflow.com/a/10115970/1672655 11 | * http://ejohn.org/apps/learn/#86 12 | * 13 | * Complex Scenario with promises: 14 | * http://adgllorente.com/2016/03/to-bind-or-not-to-bind/ 15 | */ 16 | 17 | // Polyfill for bind() 18 | Function.prototype.bind = function () { 19 | var fn = this; 20 | var args = Array.prototype.slice.call(arguments); 21 | // 获取第一个参数 22 | var context = args.shift(); 23 | 24 | return function () { 25 | return fn.apply(context, args.concat(Array.prototype.slice.call(arguments))); 26 | }; 27 | }; 28 | 29 | // 我们想通过 bind 解决什么问题? 30 | var myObj = { 31 | specialFunction: function () { 32 | }, 33 | anotherSpecialFunction: function () { 34 | }, 35 | getAsyncData: function (cb) { 36 | cb(); 37 | }, 38 | render: function () { 39 | var that = this; 40 | this.getAsyncData(function () { 41 | that.specialFunction(); 42 | that.anotherSpecialFunction(); 43 | }); 44 | } 45 | }; 46 | 47 | myObj.render(); 48 | // 如果只是通过 this.specialFunction() 调用方法,则会收到如下的错误信息: 49 | // Uncaught TypeError: Object [object global] has no method 'specialFunction' 50 | 51 | 52 | // 通过 bind() 来解决它: 53 | // 当回调函数被调用时,需要保证它所在的上下文和 myObj 对象的上下文一致。 54 | // 重构一下: 55 | var myObj = { 56 | specialFunction: function () { 57 | }, 58 | anotherSpecialFunction: function () { 59 | }, 60 | getAsyncData: function (cb) { 61 | cb(); 62 | }, 63 | render: function () { 64 | this.getAsyncData(function () { 65 | this.specialFunction(); 66 | this.anotherSpecialFunction(); 67 | }.bind(this)); 68 | } 69 | }; 70 | 71 | // 需要注意: 72 | // 如果你对原型链上的方法调用了 bind,则会创建一个实例化的方法(即该方法从原型链的层面转变为了实例的层面),这样就无法利用原型的优势。 73 | 74 | // 使用实例 75 | // 76 | // 1) 无论在哪儿调用回调方法(比如点击事情,或者 setTimeout),只要通过 bind() 绑定,在回调函数被调用时,都可以获取到外部的 this 作用域 77 | var Button = function (content) { 78 | this.content = content; 79 | }; 80 | 81 | Button.prototype.click = function () { 82 | console.log(this.content + ' clicked'); 83 | }; 84 | 85 | var myButton = new Button('OK'); 86 | myButton.click(); // OK clicked 87 | 88 | var looseClick = myButton.click; 89 | looseClick(); // undefined clicked,找不到 this.content,回调函数内的 this 并不指向 myButton 对象,而是全局对象 90 | var boundClick = myButton.click.bind(myButton); 91 | boundClick(); // OK clicked,bind 之后, this 指向 myButton 92 | 93 | // 有时候为了追踪点击事件,可能需要我们在一个对象内储存数据: 94 | var logger = { 95 | x: 0, 96 | updateCount: function () { 97 | this.x++; 98 | console.log(this.x); 99 | } 100 | }; 101 | 102 | document.querySelector('button').addEventListener('click', function () { 103 | logger.updateCount(); 104 | }); 105 | 106 | // 使用实例 107 | // 108 | // 2) 除了传递作用域,你还可以像函数中预先添加参数 109 | 110 | // 在 bind 时传递参数 111 | var sum = function (a, b) { 112 | return a + b; 113 | }; 114 | 115 | var add5 = sum.bind(null, 5); 116 | console.log(add5(10)); // 15 117 | 118 | 119 | // 比较下面三种方法 120 | // bind() vs call() vs apply() 121 | 122 | // bind 是什么? 123 | // bind() 实际上创建了一个新函数,并且可以预先给它提供一系列参数,在函数真正被调用时这些参数也会被代入 124 | // 125 | // call 是什么? 126 | // call() 代入一系列参数来调用函数 127 | // 128 | // apply 是什么? 129 | // apply() 将参数作为 array (或者类 array 对象) 代入调用 130 | // 131 | // 首先,我们来比较 call 和 apply: 132 | // 语法( call ): 133 | // fun.call(thisArg[, arg1[, arg2[, ...]]]) 134 | // 语法( apply ): 135 | // fun.apply(thisArg, [argsArray]) 136 | // 137 | // ============= 138 | // 相同点: 139 | // this 参数。如果函数在 non-strict 条件下,null 和 undefined 会被全局对象替代 140 | // 不同点: 141 | // 其他的参数。call 接收多个对象,apply 则是一个 array(或者类 array 对象) 142 | 143 | // call 的实例: 144 | // 计算圆面积 145 | var π = 3.14; 146 | var s = function(r) { 147 | return this.π*r*r; 148 | } 149 | 150 | function pi() { 151 | this.π = Math.PI; 152 | return this; 153 | } 154 | s(1); // 3.14 155 | s.call(pi(), 1); // 3.141592653589793… 156 | 157 | // 大致通过如下方式使用 158 | function toArray() { 159 | return [].slice.call(arguments); 160 | } 161 | toArray(1, 2, 3); 162 | 163 | // apply 实例: 164 | // This method is learned in lodash. 165 | !function() { 166 | function apply(fun, thisArg, args) { 167 | var length = args.length; 168 | switch() { 169 | case 0: return fun.call(thisArg); 170 | case 1: return fun.call(thisArg, args[0]); 171 | case 2: return fun.call(thisArg, args[0], args[1]); 172 | case 3: return fun.call(thisArg, args[0], args[1], args[2]); 173 | } 174 | return fun.apply(thisArg, args); 175 | } 176 | }() 177 | 178 | // 在 jquery 中也有一个 bind 179 | // $(document).bind('click', function() { 180 | // console.log(document.title); 181 | // }) 182 | // 183 | 184 | // 但我们讲的这个 bind 是指 Function.prototype.bind() 185 | // Partial Functions (分离函数) 186 | !function() { 187 | function list() { 188 | return Array.prototype.slice.call(arguments); 189 | } 190 | 191 | var list1 = list(1, 2, 3); // [1, 2, 3] 192 | 193 | // 将 37 作为第一个参数,新建一个函数 194 | var leadingThirtysevenList = list.bind(undefined, 37); 195 | 196 | var list2 = leadingThirtysevenList(); // [37] 197 | var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3] 198 | } 199 | 200 | // @参考资料 201 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind 202 | // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind 203 | -------------------------------------------------------------------------------- /js/bitwise-operators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript 中的按位操作符 3 | * 4 | * @参考资料: 5 | * http://michalbe.blogspot.com/2013/03/javascript-less-known-parts-bitwise.html 6 | * http://www.w3schools.com/jsref/jsref_operators.asp 7 | * http://stackoverflow.com/questions/654057/where-would-i-use-a-bitwise-operator-in-javascript 8 | */ 9 | 10 | // 罗列各种按位操作符 11 | // 注: 12 | // 按位操作符 将 操作数 当作32位的比特序列(由0和1组成),而不是十进制、十六进制或八进制数值 13 | var a = 5; 14 | var b = 13; 15 | 16 | // a | b - 或 17 | // 任意一个为 1 时结果为 1 18 | console.log('or', a | b); // 13 19 | 20 | // a & b - 与 21 | // 两个同时为 1 时结果为 1 22 | console.log('and', a & b); // 5 23 | 24 | // a ^ b - 异或 25 | // 有且只有一个为 1 时,结果才为 1,否则为 0 26 | console.log('xor', a ^ b); // 8 27 | 28 | // ~a - 非 29 | // 反转操作数的比特位,即0变成1,1变成0 30 | console.log('not', ~a); // -6 31 | 32 | // a >> b - 有符号右移 33 | // 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位 34 | console.log('rs', a >> b); // 0 35 | 36 | // a << b - 左移 37 | // 将 a 的二进制形式向左移 b (< 32) 比特位,右边用0填充 38 | console.log('ls', a << b); // 40960 39 | 40 | // a >>> b - 无符号右移 41 | // 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充 42 | console.log('zfrs', a >>> b); // 0 43 | 44 | 45 | // 一些关于位操作的使用实例 46 | var hex = 'ffaadd'; 47 | var rgb = parseInt(hex, 16); // 1675421 48 | 49 | // & 0xFF 确保了结果的字节数小于 8 位,多出来的将会被清空 50 | // http://stackoverflow.com/a/14713134/1672655 51 | var red = (rgb >> 16) & 0xFF; // returns 255 52 | var green = (rgb >> 8) & 0xFF; // 170 53 | var blue = rgb & 0xFF; // 221 54 | 55 | // 在 JavaScript,你可以使用两个 非(~~n)来替代 Math.floor(n)(如果 n 是正数)或者 parseInt(n, 10) 函数 56 | // 除此以外,n|n 和 n&n 与 ~~n 的作用相同。 57 | var n = Math.PI; 58 | n; // 3.141592653589793 59 | Math.floor(n); // 3 60 | parseInt(n, 10); // 3 61 | ~~n; // 3 62 | n | n; // 3 63 | n & n; // 3 64 | 65 | // 面对负数时,~~n 同样可以替代 parseInt() 66 | ~~(-n); // -3 67 | (-n) | (-n); // -3 68 | (-n) & (-n); // -3 69 | parseInt(-n, 10); // -3 70 | // 但是不能替代 Math.floor() 71 | Math.floor(-n); // -4 72 | 73 | // 将正数转换为二进制 74 | // 使用 .toString() 方法,并将 2 作为参数代入 75 | var number = 5; 76 | console.log(number.toString(2)); // 101 77 | 78 | // 交换参数的值(使用 异或 ^) 79 | // 更多内容可见: http://en.wikipedia.org/wiki/XOR_swap_algorithm 80 | var a = 73; 81 | var b = 89; 82 | a^=b; // a 16, b 89 83 | b^=a; // b 73, a 16 84 | a^=b; // a 89, b 73 85 | console.log('a', a); // a 89 86 | console.log('b', b); // b 73 87 | -------------------------------------------------------------------------------- /js/call-apply-function.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function.prototype.call & Function.prototype.apply 3 | * 4 | * 通过特定的上下文和参数来执行函数 5 | * 6 | * @参考文献: 7 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply 8 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call 9 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments 10 | * http://javascriptissexy.com/javascript-apply-call-and-bind-methods-are-essential-for-javascript-professionals/ 11 | */ 12 | 13 | function logFullName(firstName, lastName) { 14 | console.log(firstName + lastName); 15 | } 16 | 17 | // 通过 apply 执行 logFullName 函数时,需要将参数作为一个 array 代入 18 | logFullName.apply(undefined, ['Jhon', 'Doe']) // Jhon Doe 19 | 20 | // 通过 call 执行 logFullName 函数时,各参数按照普通方式依次代入 21 | logFullName.call(undefined, 'jhon', 'doe') // jhon doe 22 | 23 | // call && apply 方法的第一个参数代表函数执行时的上下文环境 24 | function logFullNameWithContext() { 25 | console.log(this.firstName, this.lastName); 26 | } 27 | 28 | var me = { 29 | firstName: 'Jhon', 30 | lastName: 'Doe', 31 | fullName: function() { 32 | logFullNameWithContext.call(this); 33 | } 34 | }; 35 | 36 | me.fullName() // 'Jhon Doe' 37 | 38 | // 我们甚至可以利用这两个方法,编写一个同时支持 array 形式和普通形式参数的函数 39 | function sumAll() { 40 | // arguments 为 sumAll 所接收到的所有参数组成的一个类数组对象 41 | if (!arguments.length) return; 42 | 43 | if (Array.isArray(arguments[0])) { 44 | // 如果参数是数组,则通过 apply 调用 45 | return sumAll.apply(this, arguments[0]); 46 | } 47 | // arguments 是一个类数组对象,先通过 Array.prototype.slice 进行调用来获取一个 array 48 | return Array.prototype.slice.call(arguments).reduce(function(prev, curr) { 49 | return prev + curr; 50 | }); 51 | } 52 | 53 | sumAll([1,2,3]) // 6 54 | sumAll(1,2,3) // 6 55 | sumAll.call(undefined, 1, 2, 3) // 6 56 | 57 | // 我们可以暴露一个方法,让用户自己选择回调函数的上下文 58 | function requestSomething(cb, context) { 59 | var something = 'something!'; 60 | 61 | // 如果没有提供上下文,则 context 是 undefined,cb 函数内部也就无法获取到 requestSomething 内的作用域 62 | cb.call(context, something); 63 | } 64 | 65 | requestSomething(function(something) { 66 | console.log(something); 67 | console.log(this); 68 | }, { hello: 'World!'}); // this prints: something! Object 69 | 70 | 71 | // 除此以外,通过 apply 和 call,我们可以借用到其他的方法 72 | 73 | // 正常情况下字符串是没有 forEach 方法的 74 | Array.prototype.forEach.call('Jhon', function(char) { 75 | console.log(char); 76 | }); // 会将字符串中的各个字符分别打印出来 77 | 78 | // 同样的,apply 可以使我们进行一些便捷操作 79 | 80 | // 比如找到一个数组中的最小值 81 | Math.min.apply(undefined, [1,2,3,5]) // 1 82 | 83 | // 或者合并两个数组 84 | var a = [1,2,3,4]; 85 | a.concat.apply(a, [5,6,7,8]) // [1,2,3,4,5,6,7,8] 86 | -------------------------------------------------------------------------------- /js/closures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 闭包 3 | * 4 | * 闭包是指,当函数在其作用域外部的位置调用时,也还能 “记住” 定义时的作用域 ~ Kyle Simpson 5 | * 6 | * 当你想要隐藏函数在功能上的实现,但依旧展现其接口和扩展性时,闭包就能发挥很好的作用。 7 | * Closures are useful in hiding the implementation of functionality while still revealing the interface. 8 | * 9 | * 为什么要使用闭包? 10 | * http://howtonode.org/why-use-closure 11 | * 12 | * @参考资料: 13 | * http://stackoverflow.com/questions/2728278/what-is-a-practical-use-for-a-closure-in-javascript 14 | * https://medium.freecodecamp.com/lets-learn-javascript-closures-66feb44f6a44#.lwnf9bay4 15 | * http://www.bennadel.com/blog/2134-a-random-exploration-of-closure-use-cases-in-javascript.htm 16 | * https://medium.com/written-in-code/practical-uses-for-closures-c65640ae7304#.ukk9dpjxs 17 | * https://medium.com/@nickbalestra/javascripts-lexical-scope-hoisting-and-closures-without-mystery-c2324681d4be#.bg7fk0chp 18 | * https://www.safaribooksonline.com/library/view/javascript-the-good/9780596517748/ch04s15.html 19 | */ 20 | 21 | // 例1 22 | function foo() { 23 | var bar = 'bar'; 24 | 25 | function baz() { 26 | console.log(bar); 27 | } 28 | 29 | bam(baz); 30 | } 31 | 32 | function bam(baz) { 33 | // 输出 'bar' 34 | // baz() 在 bam 方法内调用,而 bam 可以获取到 foo 内的作用域 35 | baz(); // bar 36 | } 37 | foo(); 38 | 39 | // 例2 40 | (function foo() { 41 | var bar = 'bar'; 42 | 43 | setTimeout(function () { 44 | console.log(bar); // 输出 `bar` -- 由于闭包的作用,setTimout 的回调函数内可以获取到 foo 里的作用域 45 | }, 1000) 46 | })(); 47 | 48 | // 例3 49 | (function foo() { 50 | var bar = 'bar'; 51 | 52 | $('#btn').click(function () { 53 | console.log(bar); // 输出 `bar` 54 | }); 55 | })(); 56 | 57 | 58 | // 实际用例 59 | 60 | // 1. 执行 public/private 方法. [Classic Module Pattern] 61 | 62 | /** 63 | * 如你所见,a 是一个对象,并拥有一个公共方法( a.publicFunction ), 64 | * 而 a.publicFunction() 则调用私有方法 privateFunction,privateFunction 内就可以获取到封闭的作用域 65 | * 66 | * 你无法直接调用 privatefunction 方法(比如这样:a.privatefunction() ) 67 | */ 68 | var a = (function () { 69 | var privateFunction = function () { 70 | console.log('Accessed private method'); 71 | }; 72 | 73 | return { 74 | publicFunction: function () { 75 | privateFunction(); 76 | } 77 | } 78 | })(); 79 | a.publicFunction(); // Accessed private method. 80 | 81 | /** 82 | * 假设你在写一个关于日期的类,可以让用户通过 index 来获取周的名称,但同时也不想让用户修改周名称组成的数组 83 | * 84 | * 在 dateUtil() 里,days 数组可以作为对象的属性存在,但是这样的话它就可以被用户轻易获取到,并能够被随意更改。 85 | * 但如果通过一个匿名函数的闭包,它就只能在 weekdayShort 函数内部被调用,而外界无法获取并干扰。 86 | */ 87 | 88 | var dateUtil = { 89 | weekdayShort: (function () { 90 | var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; 91 | return function (x) { 92 | if ((x != parseInt(x)) || (x < 1) || (x > 7)) { 93 | throw new Error("invalid weekday number"); 94 | } 95 | return days[x - 1]; 96 | }; 97 | }()) 98 | }; 99 | 100 | // 2. 储存数据 101 | 102 | /** 103 | * 你可能已经听说过,甚至已经实际使用过斐波纳契数列函数了。 104 | * 105 | * 闭包可以轻易的创建一个强大的斐波纳契数列函数 106 | * 107 | * 我们先了来看下传统的 斐波纳契数列函数:fibonacci 108 | */ 109 | var fibonacci = function (n) { 110 | return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); 111 | }; 112 | 113 | /** 114 | * fibonacci 工作起来没问题,但效率极其低下。它将同样的值计算了很多遍 115 | * 116 | * 我们可以利用一个储存器来将获取的结果储存起来(通过闭包) 117 | */ 118 | var fibonacci = (function ( ) { 119 | var memo = [0, 1]; 120 | var fib = function (n) { 121 | var result = memo[n]; 122 | if (typeof result !== 'number') { 123 | result = fib(n - 1) + fib(n - 2); 124 | memo[n] = result; 125 | } 126 | return result; 127 | }; 128 | return fib; 129 | }( )); 130 | 131 | console.log(fibonacci(100)); 132 | /** 133 | * Check Crockford's (book)[https://www.safaribooksonline.com/library/view/javascript-the-good/9780596517748/ch04s15.html "Safari Books Online"] 134 | * to find a way to generalize this function into one that memoizes other recursive functions. 135 | */ 136 | -------------------------------------------------------------------------------- /js/coercion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 强制类型转换: 3 | * 将某个值的类型转换成其他类型通常叫做 “类型转换”,在执行过程中,转换会隐式强制执行 4 | * 5 | * == vs === 6 | * 除了 恒等操作 (===) 会对双方类型验证,相等操作 (==) 会对双方进行强制类型转换以外,在其他方面的表现一致 7 | * 8 | * == 会对双方进行必要的强制类型转换,之后再比较 value 9 | * === 不会进行转换,因此如果两个值的类型不同则直接返回 false 10 | * 因此,=== 的比较会更快,而且可能和 == 的结果不一样 11 | * 12 | * 参考资料: 13 | * http://stackoverflow.com/questions/359494/does-it-matter-which-equals-operator-vs-i-use-in-javascript-comparisons 14 | * http://davidwalsh.name/fixing-coercion#isnt-coercion-already-dead 15 | * http://bytearcher.com/articles/equality-comparison-operator-javascript/ 16 | * http://rainsoft.io/the-legend-of-javascript-equality-operator/ 17 | * http://bytearcher.com/articles/equality-comparison-operator-javascript/ 18 | * 19 | * 译者注: 20 | * 补充一份资料: 21 | * 一张图彻底搞懂JavaScript的==运算 22 | * https://zhuanlan.zhihu.com/p/21650547 23 | */ 24 | 25 | // JS 中的强制类型转换 26 | (function () { 27 | var x = 42; 28 | var y = x + ""; // 隐式转换 29 | console.log(y); // "42" 30 | 31 | var z = String(x); // 显式转换 32 | console.log(z); // "42" 33 | })(); 34 | 35 | // Equality checks - Crazyyy Sh*t!!! 我也觉得😂 36 | (function () { 37 | console.log('' == '0'); // false 38 | console.log(0 == ''); // true 39 | console.log(0 == '0'); // true 40 | 41 | console.log(false == 'false'); // false 42 | console.log(false == '0'); // true 43 | 44 | console.log(false == undefined); // false 45 | console.log(false == null); // false 46 | console.log(null == undefined); // true 47 | 48 | console.log(' \t\r\n ' == 0); // true 49 | 50 | // Array 51 | var a = [1, 2, 3]; 52 | var b = [1, 2, 3]; 53 | 54 | console.log(a == b); // false 55 | console.log(a === b); // false 56 | 57 | // Object 58 | var c = {x: 1, y: 2}; 59 | var d = {x: 1, y: 2}; 60 | 61 | console.log(c == d); // false 62 | console.log(c === d); // false 63 | 64 | // String 65 | var e = "text"; 66 | var f = "te" + "xt"; 67 | 68 | console.log(e == f); // true 69 | console.log(e === f); // true 70 | 71 | // == 操作检查两个对象的值,并返回 true 72 | // === 检测到两者不是同样的对象,返回 false 73 | console.log("abc" == new String("abc")); // true 74 | console.log("abc" === new String("abc")); // false 75 | })(); 76 | -------------------------------------------------------------------------------- /js/conditional-function-declaration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 条件表达式内函数声明 3 | * 4 | * @TLDR: 在 js 中不科学(无效),尽量避免使用! 5 | * 6 | * @Info: 7 | * ECMA-262 spec: A Block is defined as one or more Statements, and a FunctionDeclaration is not a Statement. 8 | * 因此,在 if/else 中进行函数声明是无效的 9 | * 10 | * @Note: 11 | * - 浏览器对其的处理方式各不相同。有些可以支持但也有一些不行(仅仅当做普通的函数表达式)。 12 | * - 在严格模式下('strict')会报错 13 | * 14 | * @参考资料: 15 | * ECMA-262: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf#page=98 16 | */ 17 | 18 | // Case 1 19 | (function() { 20 | if (false) { 21 | function test () { 22 | alert("Works"); 23 | } 24 | } 25 | test(); // "Works"... 函数还是被声明了! 26 | // 在多数浏览器里,会进行变量提升(但在条件表达式中声明的函数依旧被正常声明了) 27 | }()); 28 | 29 | 30 | // Case 2 31 | (function() { 32 | if (false) { 33 | var test = function () { 34 | alert("Works"); 35 | } 36 | } 37 | test(); // Error: 'undefined' is not a function 38 | // 抛出一个错误。因为 test 变量被提升了,但是没有赋值。 39 | // Warning - Named function expressions are still hoisted in < IE9 (IE bug/inconsistency). 40 | }()); 41 | -------------------------------------------------------------------------------- /js/currying.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 柯里化(终于到这章了!😤) 3 | * 柯里化可以将一个需要接受很多参数的函数,转换为接受少量参数的函数 4 | * 5 | * 简而言之,柯里化是构造函数的一种方式,它可以将函数分离成各个小型函数,将参数依次分段传入 6 | * 这意味着你既可以传递一堆参数给函数,然后获取返回值;也可以依次传入参数,分别获取返回的函数,然后接收剩下的参数。 7 | * 8 | * 柯里化(Currying)vs 分段函数(Partial Application) 9 | * “Currying is the decomposition of a polyadic function into a chain of nested unary functions. 10 | * Thus decomposed, you can partially apply one or more arguments, although the curry operation itself does not apply any arguments to the function.” 11 | * 12 | * “Partial application is the conversion of a polyadic function into a function taking fewer arguments arguments by providing one or more arguments in advance.” 13 | * 14 | * @参考资料: 15 | * http://www.sitepoint.com/currying-in-functional-javascript/ 16 | * http://www.2ality.com/2011/09/currying-vs-part-eval.html 17 | * https://medium.com/@kbrainwave/currying-in-javascript-ce6da2d324fe#.nhp2e7pcm 18 | * https://medium.com/@kevincennis/currying-in-javascript-c66080543528#.bnk4cy1m0 19 | * http://raganwald.com/2013/03/07/currying-and-partial-application.html 20 | * http://ejohn.org/blog/partial-functions-in-javascript/ 21 | * http://stackoverflow.com/questions/113780/javascript-curry-what-are-the-practical-applications 22 | * http://conceptf1.blogspot.com/2014/03/currying-in-javascript.html 23 | * https://www.youtube.com/watch?v=iZLP4qOwY8I 24 | * https://egghead.io/lessons/javascript-what-is-currying 25 | * https://hughfdjackson.com/javascript/why-curry-helps/ 26 | */ 27 | 28 | // 一个没有柯里化的函数 29 | var greet = function (greeting, name) { 30 | console.log(greeting + ', ' + name); 31 | }; 32 | greet('Hello', 'Vasa'); // 'Hello, Vasa' 33 | 34 | // 上一个函数柯里化之后的版本 35 | var greetCurried = function (greeting) { 36 | return function (name) { 37 | console.log(greeting + ', ' + name); 38 | } 39 | }; 40 | 41 | // 柯里化之后,我们可以通过第一次调用传入不同参数,来创建不同功能的函数 42 | var greetHello = greetCurried("Hello"); 43 | greetHello("Vasa"); //"Hello, Vasa" 44 | greetHello("Vignesh"); //"Hello, Vignesh" 45 | 46 | // 或者也可以直接在原有柯里化函数上直接进行两次调用: 47 | greetCurried("Hi there")("Vasa"); //"Hi there, Vasa" 48 | 49 | 50 | // 将函数柯里化的通用函数 -- 简陋版本 51 | // 52 | // 构建这种函数的问题是语法。既然你在构建一个可柯里化其他函数的函数,那么需要不断在内部返回方法,该方法接收一定参数,然后再返回其他方法。重复多次后就会一片混乱。 53 | // 54 | // 我们先快速创建一个简陋版本。它接受一个函数作为参数,也不会有层层嵌套的返回 55 | // A currying function would need to pull out the list of arguments for that function, and use those to return a curried version of the original function: 56 | 57 | // 分离函数 -- 最初只需要少量参数来初始化,之后可以传入剩余的参数 58 | function curryIt(uncurriedFn) { 59 | // 忽略第一个参数(uncurriedFn) 60 | var parameters = Array.prototype.slice.call(arguments, 1); 61 | return function () { 62 | return uncurriedFn.apply(this, parameters.concat( 63 | Array.prototype.slice.call(arguments, 0) 64 | )); 65 | }; 66 | } 67 | 68 | // Usage 69 | var greeter = function (greeting, separator, emphasis, name) { 70 | console.log(greeting + separator + name + emphasis); 71 | }; 72 | var greetHello = curryIt(greeter, "Hello", ", ", "."); 73 | greetHello("Heidi"); //"Hello, Heidi." 74 | greetHello("Eddie"); //"Hello, Eddie." 75 | 76 | 77 | // 将函数柯里化的通用函数 -- 高级版本 78 | // 参考自: https://medium.com/@kevincennis/currying-in-javascript-c66080543528#.bnk4cy1m0 79 | function curryIt(fn) { 80 | // 通过 fn.length 得知 fn 函数期待多少个参数 81 | var arity = fn.length; 82 | return (function resolver() { 83 | // 保存一份 resolver 函数接收到的参数,并转换为数组 84 | var memory = Array.prototype.slice.call(arguments); 85 | return function () { 86 | // 复制一份 memory,并将新参数 push 进去 87 | var local = memory.slice(), next; 88 | // 此时的 arguments 为返回的匿名函数接收到的参数 89 | Array.prototype.push.apply(local, arguments); 90 | // 所有参数的长度 >= fn 期待的参数个数时,调用 fn,否则递归 91 | next = local.length >= arity ? fn : resolver; 92 | return next.apply(null, local); 93 | }; 94 | }()); 95 | } 96 | 97 | // 栗子 98 | var l = 2, b = 3, h = 4; 99 | var curriedVol = curryIt(vol); 100 | var area = curriedVol(l)(b); 101 | var volume = area(h); 102 | console.log('Volume: ', volume); 103 | 104 | function vol(l, b, h) { 105 | return l * b * h; 106 | } 107 | 108 | // 将函数柯里化的通用函数 -- 我自己的版本 109 | function curryIt(fn) { 110 | var arity = fn.length; 111 | var params = []; 112 | return function handler() { 113 | var args = Array.prototype.slice.call(arguments); 114 | Array.prototype.push.apply(params, args); // OR params.push.apply(this, args); 115 | 116 | if (params.length === arity) { 117 | return fn.apply(this, params); 118 | } else { 119 | return handler; 120 | } 121 | } 122 | } 123 | 124 | // ES6 实例 125 | const one = document.getElementById('one'); 126 | const two = document.getElementById('two'); 127 | const three = document.getElementById('three'); 128 | 129 | const f = a => b => c => a.addEventListener(b, (event) => { 130 | event.target.style.backgroundColor = c; 131 | }); 132 | 133 | const oneEventColor = f(one); 134 | const twoEventColor = f(two); 135 | 136 | oneEventColor('mouseover')('blue'); 137 | twoEventColor('mouseout')('green'); 138 | 139 | // Currying challenge: 140 | // https://github.com/frantic/friday/blob/master/currying.js 141 | // http://blog.vjeux.com/2015/javascript/140byt-es-curried-add-function.html 142 | function add() { 143 | var s = [].reduce.call(arguments, function (sum, curr) { 144 | return sum + curr; 145 | }); 146 | var f = function () { 147 | return add.apply(0, [s].concat([].slice.call(arguments))) 148 | }; 149 | f.valueOf = function () { 150 | return s 151 | }; 152 | return f; 153 | } 154 | -------------------------------------------------------------------------------- /js/dom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DOM API 3 | * 4 | * DOM,即将 document 看做一个树,由 父节点-子节点 关系组成,每个父节点都含有一个或多个子节点 5 | * DOM 认为每个节点都是一个对象,我们可以获取并改变它的属性。 6 | * 7 | * @参考资料: 8 | * http://javascript.info/tutorial/dom 9 | * http://www.quirksmode.org/dom/ 10 | * http://domenlightenment.com/ 11 | */ 12 | 13 | // 基本的元素选择 14 | document.getElementById("IDName"); // 选择相应 ID 的第一个元素。尽管在 HTML 里多个重复的 ID 不合法,但如果真那么写了,则只选择第一个 15 | document.getElementsByClassName("ClassName"); // 根据 class 名称选择,返回一个 array 16 | document.getElementsByName("Name"); // 根据 Node name 选择,返回一个 array 17 | document.getElementsByTagName("TagName"); // 根据 TagName 选择,返回一个 array 18 | document.querySelector("#IDName or .ClassName"); // 返回匹配的第一个元素 19 | document.querySelectorAll("#IDName or .ClassName"); // 返回一个匹配的 array 20 | 21 | // 获取根节点元素 22 | console.log(document.documentElement); 23 | 24 | // 在 DOM 世界里,“element not found” 或者 “no such element” 总是代表 null 25 | // 不可能引用还没有渲染出来的 DOM 元素 26 | // 例如,如果你在页面加载时,在 内获取 document.body,将会返回 null,因为此时 还没有加载 27 | 28 | // 子元素 29 | 30 | // childNodes 31 | // 返回当前元素所有的子节点,甚至包括空格 32 | console.log(document.body.childNodes); 33 | 34 | // children 35 | // 有时候我们只需要获取到 DOM 节点元素,而不需要文字节点等元素 36 | console.log(document.body.children); 37 | 38 | // firstChild - 获取到第一个节点,包括空格 39 | // lastChild - 获取到最后一个节点 40 | // 它们分别相当于 childNodes 中的第一个/最后一个元素 41 | console.log(document.body.firstChild); 42 | console.log(document.body.lastChild); 43 | 44 | // firstElementChild - 获取到第一个 DOM 节点元素 45 | // lastElementChild - 获取到最后一个 DOM 节点元素 46 | // 它们分别相当于 children 中的第一个/最后一个元素 47 | console.log(document.body.firstElementChild); 48 | console.log(document.body.lastElementChild); 49 | 50 | // parentNode, previousSibling and nextSibling 51 | console.log(document.body.parentNode); 52 | // 上一个节点元素 53 | console.log(document.body.previousSibling); 54 | // 下一个节点元素 55 | console.log(document.body.nextSibling); 56 | // 上一个 DOM 节点元素 57 | console.log(document.body.previousElementSibling); 58 | // 下一个 DOM 节点元素 59 | console.log(document.body.nextElementSibling); 60 | 61 | // 结构和属性 62 | 63 | // nodeType 表示某节点的类型,可用于区分不同类型的节点,比如 DOM 元素、文本、注释 64 | // 尤其要注意的是,DOM 元素的 nodeType 为 1,文本节点的 nodeType 为 3 65 | // 更多资料可参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType 66 | var childNodes = document.body.childNodes; 67 | console.log(childNodes[0].nodeType != 1); 68 | 69 | // nodeName, tagName 70 | // nodeName 和 tagName 都返回节点的名称 71 | // 在 HTML 里,任意节点的名称都是大写的 72 | // 对于 DOM 节点而言,nodeName 和 tagName 是一致的 73 | // 从 DOM 层次来看,nodeName 是 node 接口上的property,而 tagName 是 element 接口上的property 74 | // 所有节点都继承了 node 接口,而只有 DOM 节点才继承了 element 接口 75 | console.log(document.body.tagName); // BODY 76 | 77 | // innerHTML 78 | // 它可以获取到 node 内部的内容,且只有 DOM 节点才可以正常使用 79 | // 注意: `innerHTML` 无法被追加 80 | // 正常来看,或许可以通过 elem.innerHTML += "New text" 的方式,直接在 innerHTML 后面进行追加, 81 | // 但这种行为实际上做的是: 82 | // 1) 拼接新内容 83 | // 1) 清除旧内容 84 | // 2) 重新加载拼接后的新内容 85 | // 所以比较耗费资源 86 | document.body.innerHTML += "
Hi !
"; 87 | document.body.innerHTML += "How you doing?"; 88 | 89 | // nodeValue 90 | // 只有 DOM 节点元素才有 innerHTML 属性,而对于其他类型,则通过 nodeValue 获取值 91 | // eg. 文字节点 和 注释节点 92 | document.body.childNodes[i].nodeValue = 'Test'; 93 | 94 | // Properties 95 | 96 | // DOM 节点其实就是一个对象,如同其他 js 对象一样,可以保存属性和方法 97 | // 自定义的 DOM 属性: 98 | // 1) 对属性的大小写敏感 99 | // 2) 不影响 HTML 100 | // 3) 可以在 for..in 的遍历中获取到 101 | document.body.sayHi = function () { 102 | alert(this.nodeName); 103 | }; 104 | document.body.sayHi(); // BODY 105 | 106 | document.body.custom = 5; 107 | var list = []; 108 | for (var key in document.body) { 109 | list.push(key); 110 | } 111 | alert(list.join('\n')); 112 | 113 | // Attributes 114 | 115 | // DOM 节点有一些可以获取到 HTML 属性的方法: 116 | // elem.hasAttribute(name) - 检查属性是否存在 117 | // elem.getAttribute(name) - 获取属性 118 | // elem.setAttribute(name, value) - 设置属性 119 | // elem.removeAttribute(name) - 移除属性 120 | // 121 | // 和 properties 对比,attributes: 122 | // 1) 可能只有 string 123 | // 2) 命名不是大小写敏感,因为 HTML 属性名称并不在乎大小写 124 | // 3) 可以被 innerHTML 获取(除非是老版本 IE) 125 | // 4) 你可以把所有的 attributes 作为一个类数组对象列出来 126 | var div = document.body.children[0]; 127 | alert(div.getAttribute('ABOUT')); // 不区分大小写 128 | div.setAttribute('Test', 123); 129 | alert(document.body.innerHTML); 130 | 131 | /** 132 | * 译者注: 133 | * 除此以外,他们获取属性的方式也有所不同 134 | * node.getAttribute(xxx) 获取的是 attribute 135 | * node.xxx 获取的是 property 136 | */ 137 | 138 | // PROPERTIES 和 ATTRIBUTES 的同步 139 | 140 | // 每种 DOM 节点都有标准的 properties 141 | // 标准的 DOM properties 会与 attributes 保持同步 142 | 143 | // id 144 | document.body.setAttribute('id', 'la-la-la'); 145 | alert(document.body.id); // la-la-la 146 | 147 | // href 148 | var a = document.body.children[0]; 149 | a.href = '/'; 150 | alert('attribute:' + a.getAttribute('href')); // '/' 151 | alert('property:' + a.href); // IE: '/', others: full URL 152 | 153 | // input.checked 的 property 要么是 true 要么是 false,但 attribute 却是由 你的输入决定 154 | var input = document.body.children[0]; 155 | alert(input.checked); // true 156 | alert(input.getAttribute('checked')); // empty string 157 | 158 | // value 159 | // 有一些内置的 properties 只是单向的同步 160 | // 比如 input.value,由 attribute 来决定同步 161 | var input = document.body.children[0]; 162 | input.setAttribute('value', 'new'); 163 | alert(input.value); // 'new', input.value changed 164 | 165 | // 当 property 的 "value" 更新以后,attribute 还是会保持原有的值。 166 | // 例如,用户输入某些东西。原有的值保存在 attribute 里,既可以用来检查 value 是否发生了改变,也可以用来重置。 167 | var input = document.body.children[0]; 168 | input.value = 'new'; 169 | alert(input.getAttribute('value')); // 'markup', not changed! 170 | 171 | // class/className 172 | // 因为 class 是 JavaScript 中的预留单词,因此在属性里叫做 className 173 | // 为了避免 IE 下的问题,尽量使用 property 而不是 attribute 管理 className 174 | document.body.setAttribute('class', 'big red bloom'); 175 | alert(document.body.className); // big red bloom 176 | 177 | // 除非非要使用 attribute,否则尽量一直使用 properties 178 | // 179 | // 而这种时候你确实需要使用 attribute: 180 | // 1) 获取自定义的 HTML attribute(不和 DOM property 同步) 181 | // 2) 获取某些 HTML attribute 的原始值,比如 182 | 183 | // Attributes as DOM nodes 184 | // 每个 attribute 都表现的像特殊的 DOM 节点一样,有自己的名称、属性(properties)和 值 185 | var span = document.body.children[0]; 186 | alert(span.attributes['style'].value); // "color:blue;" 187 | alert(span.attributes['id'].value); // "my" 188 | 189 | // MODIFYING THE DOCUMENT 190 | 191 | // 创建元素 192 | // 1) 创建一个 DOM 节点: 193 | var div = document.createElement('div'); 194 | // 2) 创建一个文本节点 195 | var textElem = document.createTextNode('Robin was here'); 196 | // 克隆 197 | // 元素可以被克隆 198 | textElem.cloneNode(true); // 深度拷贝 199 | textElem.cloneNode(false); // 浅拷贝,只复制 attributes,不复制子元素 200 | 201 | // 新增元素 202 | // 203 | // appendChild 在父节点末尾插入 204 | document.body.appendChild(textElem); 205 | 206 | // parentElem.insertBefore(elem, nextSibling) 207 | // 在某个子节点(nextSibling)之前插入 208 | // Link: http://stackoverflow.com/a/2007473/1672655 209 | var div = document.body.children[0]; 210 | var span = document.createElement('span'); 211 | span.innerHTML = 'A new span!'; 212 | div.insertBefore(span, div.firstChild); 213 | // 如果 insertBefore 方法的第二个参数是 null,则其表现的和 appendChild 一致 214 | elem.insertBefore(newElem, null); // same as 215 | elem.appendChild(newElem); 216 | 217 | // 移除节点 218 | // 219 | // 一般有两种方法可以从 DOM 移除节点: 220 | // parentElem.removeChild(elem) - 直接从 parentElem 中移除 elem 221 | // parentElem.replaceChild(elem, currentElem) - 移除 elem,并用 currentElem 替换它 222 | 223 | // 注:当你想要移动一个节点的时候,并不需要先移除它。 224 | // elem.appendChild/insertBefore 方法会先移除 DOM 225 | // 下面这个例子将最后一个元素插入到首位: 226 | var first = document.body.children[0]; 227 | var last = document.body.children[1]; 228 | document.body.insertBefore(last, first); 229 | // 当针对一个已经有父节点的元素调用这些方法时,会先自动移除掉它 230 | 231 | // 自定义 insertAfter 方法 232 | var elem = document.createElement('div'); 233 | elem.innerHTML = '**Child**'; 234 | function insertAfter(elem, refElem) { 235 | return elem.parentNode.insertBefore(elem, refElem.nextSibling); 236 | } 237 | insertAfter(elem, document.body.firstChild); 238 | insertAfter(elem, document.body.lastChild); 239 | 240 | // Gotcha 241 | // 对任意的 document,尝试做如下事: 242 | var aList1 = document.getElementsByTagName('a'); // 若 DOM 改变,则数据也会改变 243 | var aList2 = document.querySelectorAll('a'); // 获取之后,新增 DOM 不会改变其数据 244 | document.body.appendChild(document.createElement('a')); 245 | alert(aList1.length - aList2.length); // 1 246 | 247 | // 输入为 1 248 | // 为毛是这样的输出? 249 | // Solution 250 | // getElementsByTagName 是会动态变化的,在新增一个 a DOM 之后,它会自动增加 1 251 | // 反之,querySelector 返回一个静态数据,在获取到结果之后,无论我们对 DOM 进行什么操作,结果不会再改变 252 | 253 | // TABLE 254 | // 255 | // 256 | // 257 | //
one two
three four
258 | 259 | var table = document.body.children[0]; 260 | alert(table.rows[0].cells[0].innerHTML); // "one" 261 | 262 | // FORMS 263 | 264 | //Select option 265 | //
266 | // 270 | //
271 | var form = document.forms.my; 272 | var select = form.elements.genre; 273 | var value = select.options[select.selectedIndex].value; 274 | alert(value); // blues 275 | 276 | // SELECT 提供了名为 selectedIndex 的属性,代表当前被选择的 option 所处的 index。当只存在单个 select 的时候很好用 277 | //
278 | // 282 | //
283 | 284 | var form = document.forms.temp; 285 | var select = form.elements.genre; 286 | var value = select.options[select.selectedIndex].value; 287 | alert(value); // blues 288 | -------------------------------------------------------------------------------- /js/event-bubbling.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 事件冒泡和捕获 3 | * 4 | * @参考资料: 5 | * http://javascript.info/tutorial/bubbling-and-capturing 6 | * http://stackoverflow.com/questions/4616694/what-is-event-bubbling-and-capturing 7 | * http://javascript.info/tutorial/mouse-events 8 | * 9 | */ 10 | 11 | // 阻止事件冒泡 12 | element.onclick = function (event) { 13 | event = event || window.event; // 确保跨浏览器兼容性 14 | if (event.stopPropagation) { 15 | // W3C 标准 16 | event.stopPropagation() 17 | } else { 18 | // IE 标准 19 | event.cancelBubble = true 20 | } 21 | }; 22 | 23 | // 如果某个元素对于一个事件绑定了多个相应函数,则它们各个独立,并且在触发事件时,都会被执行。 24 | // 例如,某个链接上绑定了两个不同的点击事件,那么其中一个阻止事件冒泡,对另外一个没有影响。 25 | // 同样的,浏览器也无法保障它们调用的顺序。 26 | 27 | // 事件捕获 28 | // 在除了 23 | //
  • 24 | // 25 | //
  • 26 | //
  • 27 | // 28 | //
  • 29 | //
  • 30 | //
  • 31 | // 32 | //
  • 33 | // 34 | 35 | 36 | // HELPER FUNCTION 37 | function delegate(criteria, listener) { 38 | return function (e) { 39 | var el = e.target; 40 | do { 41 | if (!criteria(el)) { 42 | continue; 43 | } 44 | e.delegateTarget = el; 45 | listener.call(this, e); 46 | return; 47 | } while ((el = el.parentNode)); 48 | }; 49 | } 50 | 51 | // Example of Event Delegation 52 | // Custom filter to check for required DOM elements 53 | var buttonsFilter = function (elem) { 54 | return (elem instanceof HTMLElement) && elem.matches(".btn"); 55 | // OR 56 | // For < IE9 57 | // return elem.classList && elem.classList.contains('btn'); 58 | }; 59 | 60 | var buttonHandler = function (e) { 61 | // 获取正在处理当前事件的元素 62 | var button = e.delegateTarget; 63 | // 通过 button.classList 获取到所有的 className 64 | // 并通过 contains 进行判断 65 | var hasActiveClass = button.classList.contains('active'); 66 | 67 | if (!hasActiveClass(button)) { 68 | button.classList.add('active'); 69 | } else { 70 | button.classList.remove('active'); 71 | } 72 | }; 73 | 74 | // 通过事件委托,不需要在每个节点上绑定事件 75 | // 类似于 jQuery 的 $(xxx).on('click', DOM, callback) 76 | document.addEventListener("click", delegate(buttonsFilter, buttonHandler)); 77 | -------------------------------------------------------------------------------- /js/event-handling.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 事件处理 3 | * 4 | * @参考资料: 5 | * http://gomakethings.com/ditching-jquery/#event-listeners 6 | * http://www.quirksmode.org/dom/events/index.html 7 | * http://www.jstips.co/en/DOM-event-listening-made-easy/ 8 | */ 9 | 10 | 11 | var elem = document.querySelector('.some-class'); 12 | elem.addEventListener('click', function (e) { 13 | // Do stuff 14 | }, false); // 最后一个参数表面事件处理函数是否在事件捕获时触发 15 | 16 | // 给事件处理函数传递多个参数 17 | var elem = document.querySelector('.some-class'); 18 | var someFunction = function (var1, var2, var3, event) { 19 | // do stuff 20 | }; 21 | elem.addEventListener('click', someFunction.bind(null, var1, var2, var3), false); 22 | elem.addEventListener('mouseover', someFunction.bind(null, var1, var2, var3), false); 23 | 24 | // 将事件委托给 document 25 | var eventHandler = function () { 26 | // 获取点击到的元素 27 | var toggle = event.target; 28 | 29 | // 如果是目标元素,则触发函数 30 | if (toggle.hasAttribute('data-example') || toggle.classList.contains('sample-class')) { 31 | event.preventDefault(); // 阻止默认事件 32 | someMethod(); 33 | } 34 | }; 35 | 36 | document.addEventListener('click', eventHandler, false); 37 | 38 | // 更好的委托机制 39 | function delegate(criteria, listener) { 40 | return function (e) { 41 | var el = e.target; 42 | do { 43 | if (!criteria(el)) { 44 | continue; 45 | } 46 | e.delegateTarget = el; 47 | listener.call(this, e); 48 | return; 49 | } while ((el = el.parentNode)); 50 | }; 51 | } 52 | 53 | // 单击的处理函数 - ES6 54 | function handleEvent(eventName, {onElement, withCallback, useCapture = false} = {}, thisArg) { 55 | const element = onElement || document.documentElement; 56 | 57 | function handler(event) { 58 | if (typeof withCallback === 'function') { 59 | withCallback.call(thisArg, event) 60 | } 61 | } 62 | 63 | handler.destroy = function () { 64 | return element.removeEventListener(eventName, handler, useCapture) 65 | }; 66 | 67 | element.addEventListener(eventName, handler, useCapture); 68 | return handler; 69 | } 70 | 71 | // Anytime you need 72 | const handleClick = handleEvent('click', { 73 | onElement: element, 74 | withCallback: (event) => { 75 | console.log('Tada!') 76 | } 77 | }); 78 | 79 | // And anytime you want to remove it 80 | handleClick.destroy(); 81 | -------------------------------------------------------------------------------- /js/factory-functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 工厂方法 3 | * 4 | * @参考资料: 5 | * https://www.youtube.com/watch?v=ImwrezYhw4w 6 | * http://atendesigngroup.com/blog/factory-functions-javascript 7 | * 8 | */ 9 | 10 | // ES6 的 class vs 工厂方法 11 | // With classes -- 使用时要小心 12 | class Dog { 13 | constructor() { 14 | this.sound = 'woof'; 15 | } 16 | 17 | talk() { 18 | console.log(this.sound); 19 | } 20 | } 21 | 22 | const sniffles = new Dog(); 23 | sniffles.talk(); // 输出: 'woof' 24 | 25 | // 但这样使用会有问题 26 | $('button').click(sniffles.talk); // 此时无法像上面那样正常工作。因为方法内的 this 作用域已经改变,成为了 $(button) 27 | 28 | // 修复措施 -- 使用 bind 29 | $('button').click(sniffles.talk.bind(sniffles)); 30 | 31 | // 或者 ES6 语法 -- 箭头函数内的 this 作用域总是指向外层 32 | $('button').click(() => sniffles.talk()); 33 | 34 | 35 | // 工厂方法 36 | const dog = () => { 37 | const sound = 'woof'; 38 | return { 39 | talk: () => console.log(sound) // 不使用 this 40 | }; 41 | }; 42 | 43 | const sniffles = dog(); 44 | sniffles.talk(); // 输出: 'woof' 45 | 46 | $('button').click(sniffles.talk); // 可正常工作 -- 输出: 'woof' 47 | 48 | 49 | 50 | // 构造函数 vs 工厂方法 51 | 52 | // 最基本的不同点是,构造函数需要通过 new 关键字调用(这会使 js 自动创建一个新对象,并将 this 作用域赋给对象,最后返回这个对象): 53 | var objFromConstructor = new ConstructorFunction(); 54 | 55 | // 而工厂方法则如同正常的函数一样调用: 56 | var objFromFactory = factoryFunction(); 57 | // 但是既然它被叫做 “工厂”,那么需要在调用时,返回一些对象的实例, 58 | // 不能因为某个方法返回 boolean 或者其他东西就叫它 “工厂方法”。 59 | // 而返回实例这件事不会像 new 的方式一样自动调用,但也由此带来了一些灵活性。 60 | // 举个简单的栗子: 61 | 62 | function ConstructorFunction() { 63 | this.someProp1 = "1"; 64 | this.someProp2 = "2"; 65 | } 66 | ConstructorFunction.prototype.someMethod = function() { /* whatever */ }; 67 | 68 | function factoryFunction() { 69 | var obj = { 70 | someProp1 : "1", 71 | someProp2 : "2", 72 | someMethod: function() { /* whatever */ } 73 | // 对象内的 someMethod() 会导致每次返回的对象都有一份 someMethod 不同拷贝,而我们可能并不想这样。 74 | // 此时如果在工厂方法内使用 new 和 prototype 就会避免这个问题 75 | }; 76 | 77 | // other code to manipulate obj in some way here 78 | return obj; 79 | } 80 | 81 | // 工厂方法:封装使用内部的私有属性 82 | function Car () { 83 | // 私有变量 84 | var location = 'Denver'; // 私有 85 | function year() { // 私有 86 | self.year = new Date().getFullYear(); 87 | } 88 | 89 | var self = { 90 | make: 'Honda', 91 | model: 'Accord', 92 | color: '#cc0000', 93 | paint: function(color){ 94 | self.color = color; 95 | } 96 | }; 97 | 98 | if (!self.year){ 99 | year(); 100 | } 101 | 102 | return self; 103 | } 104 | 105 | var myCar = Car(); 106 | 107 | 108 | // 工厂方法:动态对象 109 | // 鉴于工厂方法内可以使用私有/公开函数,我们可以通过 if/else 来控制对象的构造 110 | // 这给予了我们极大的灵活性,可以通过一些参数来决定工厂方法最终返回的实例对象 111 | function Address (param) { 112 | var self = {}; 113 | 114 | if (param === 'dev'){ 115 | self = { 116 | state: 'Colorado', 117 | saveToLog: function(){ 118 | // write info to a log file 119 | } 120 | }; 121 | } else { 122 | self = { 123 | state: 'Colorado' 124 | }; 125 | } 126 | 127 | return self; 128 | } 129 | 130 | var devAddress = Address('dev'); 131 | var productionAddress = Address(); 132 | -------------------------------------------------------------------------------- /js/floating-point-precision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript 中的数字类型(浮点数) 3 | * 4 | * @太长别看:有一种数字类型 - 64位浮点数(就像 Java 里的 double)- 经常会出问题 5 | * 6 | * @资料 7 | * - The crux of the problem is that numbers are represented in this format as a whole number times a power of two. 8 | * rational numbers (such as 0.1, which is 1/10) whose denominator is not a power of two cannot be exactly represented. 9 | * - 0.1 cannot be represented as accurately in base-2 as in base-10 due to the missing prime factor of 5. 10 | * Just as 1/3 takes an infinite number of digits to represent in decimal, but is "0.1" in base-3, 11 | * 0.1 takes an infinite number of digits in base-2 where it does not in base-10. 12 | * - For 0.1 in the standard binary64 format, the representation can be written exactly as 13 | * 0.1000000000000000055511151231257827021181583404541015625 in decimal 14 | * - In contrast, the rational number 0.1, which is 1/10, can be written exactly as 0.1 in decimal. 15 | * 16 | * @Note: 17 | * - 一个关于处理浮点数的最好的建议:使用第三方库去操作他们,比如 sinfuljs,mathjs 或者 BigDecimal.js 18 | * - 另一个建议是对数字使用内置的 toPrecision() 和 toFixed() 方法。但一个需要注意的问题是:这两个方法返回 string 19 | * 20 | * @参考资料 21 | * http://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript 22 | * http://stackoverflow.com/questions/588004/is-floating-point-math-broken 23 | * 24 | */ 25 | 26 | (function() { 27 | console.log(0.1 + 0.2); // prints 0.30000000000000004 28 | console.log((0.1 + 0.2) === 0.3); // prints false 29 | 30 | // Workaround: 使用一定的位数格式化计算结果,比如: 31 | // (Math.floor(y/x) * x).toFixed(2) OR parseFloat(a).toFixed(2) 32 | })(); 33 | -------------------------------------------------------------------------------- /js/for-in-with-hasOwnProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * for... in 方法 3 | * 4 | * for...in 方法会遍历对象内的可遍历(enumerable)属性 5 | * 6 | * 当你对数组调用这个方法时: 7 | * Array 的 index 是可遍历的属性,只是名称为数字,否则的话和普通对象属性一样。 8 | * 并不能保证 for...in 循环能够按照特定的顺序进行,并且它将返回所有的可遍历属性,包括继承的属性,和名称不是数字的属性 9 | * 因为迭代的顺序与实现相关,所以每次遍历一个数组时,访问各元素的顺序可能不同 10 | * Because the order of iteration is implementation-dependent, iterating over an array may not visit elements in a consistent order. 11 | * 因此,当遍历数组时,最好使用带有数字 index 的 for 循环(或者 Array.prototype.forEach()、for...of) 12 | * 13 | */ 14 | 15 | // 下面的方法对一个对象进行遍历,并依次获取到对象的可遍历属性,并以此获取到属性对应的值。 16 | var obj = {a:1, b:2, c:3}; 17 | 18 | for (var prop in obj) { 19 | console.log("obj." + prop + " = " + obj[prop]); 20 | } 21 | 22 | // Output: 23 | // "obj.a = 1" 24 | // "obj.b = 2" 25 | // "obj.c = 3" 26 | 27 | 28 | // 下面这个函数在遍历的过程中使用 hasOwnProperty() 进行检查,排除继承的属性 29 | var triangle = {a:1, b:2, c:3}; 30 | 31 | function ColoredTriangle() { 32 | this.color = "red"; 33 | } 34 | 35 | ColoredTriangle.prototype = triangle; 36 | 37 | var obj = new ColoredTriangle(); 38 | 39 | for (var prop in obj) { 40 | if( obj.hasOwnProperty( prop ) ) { 41 | console.log("obj." + prop + " = " + obj[prop]); 42 | } 43 | } 44 | 45 | // 输出: 46 | // "obj.color = red" 47 | -------------------------------------------------------------------------------- /js/getOwnPropertyNames-vs-keys.js: -------------------------------------------------------------------------------- 1 | /** 2 | * What's the difference between Object.getOwnPropertyNames and Object.keys 3 | * 4 | * There is a little difference. 5 | * Object.getOwnPropertyNames(a) returns all own properties of the object a. 6 | * Object.keys(a) returns all enumerable own properties. 7 | * 8 | * It means that if you define your object properties without making some of them enumerable: false these two methods will give you the same result. 9 | */ 10 | 11 | var a = {}; 12 | Object.defineProperties(a, { 13 | one: {enumerable: true, value: 'one'}, 14 | two: {enumerable: false, value: 'two'} 15 | }); 16 | Object.keys(a); // ["one"] 17 | Object.getOwnPropertyNames(a); // ["one", "two"] 18 | 19 | // If you define a property without providing property attributes descriptor (meaning you don't use Object.defineProperties), for example: 20 | a.test = 21; 21 | // then such property becomes an enumerable automatically and both methods produce the same array. -------------------------------------------------------------------------------- /js/getters-setters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Getters 和 Setters 3 | * 4 | * 从第一天起,getter 和 setter 就被 chrome 支持,而 Firefox2 及以上,Safari3 及以上,IE9 及以上,和所有的手机浏览器也可以支持。 5 | * 6 | * @参考资料 7 | * http://engineering.wix.com/2015/04/21/javascript-the-extra-good-parts/ 8 | * http://javascriptplayground.com/blog/2013/12/es5-getters-setters/ 9 | */ 10 | 11 | // 显式的使用 getter 和 setter 12 | (function () { 13 | function wrapValue(value) { 14 | return { 15 | getValue: function () { 16 | return value; 17 | }, 18 | setValue: function (newValue) { 19 | value = newValue; 20 | } 21 | }; 22 | } 23 | 24 | var x = wrapValue(5); 25 | console.log(x.getValue()); // 输出 5 26 | x.setValue(7); 27 | console.log(x.getValue()); // 输出 7 28 | })(); 29 | 30 | // getter 和 setter 的传统使用方式 31 | (function () { 32 | function wrapValue(_value) { 33 | return { 34 | get value() { 35 | return _value; 36 | }, 37 | set value(newValue) { 38 | _value = newValue; 39 | } 40 | }; 41 | } 42 | 43 | var x = wrapValue(5); 44 | console.log(x.value); // 输出 5 45 | x.value = 7; 46 | console.log(x.value); // 输出 7 47 | })(); 48 | 49 | // 通过 Object.defineProperty 定义 getter 和 setter 50 | // 当你通过 Object.defineProperty 来定义属性时,不仅仅可以定义 setter 和 getter,还可以传入其他 key: 51 | // configurable(默认为 false):如果为 true,则这个属性的配置在定义之后可以被修改 52 | // enumerable(默认为 false):如果为 true,则这个属性可以在遍历时获取到(比如 for...in) 53 | (function() { 54 | var person = { 55 | firstName: 'Jimmy', 56 | lastName: 'Smith' 57 | }; 58 | 59 | Object.defineProperty(person, 'fullName', { 60 | get: function() { 61 | return firstName + ' ' + lastName; 62 | }, 63 | set: function(name) { 64 | var words = name.split(' '); 65 | this.firstName = words[0] || ''; 66 | this.lastName = words[1] || ''; 67 | } 68 | }); 69 | })(); 70 | -------------------------------------------------------------------------------- /js/logical-operations-with-string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 带有 string 的逻辑操作 3 | */ 4 | 5 | (function () { 6 | var a = true; 7 | var b = 'Yes'; 8 | var c = 'It\'s me'; 9 | 10 | console.log(a && b); // 输出 'Yes' 11 | console.log(a && b && c); // 输出 'It's me' 12 | console.log(a && b || c); // 输出 'Yes' 13 | })(); 14 | -------------------------------------------------------------------------------- /js/method-overloading.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 方法重载 3 | * 一个函数,在调用时根据其接受参数个数的不同,执行不同的方法。 4 | * 译者注:不建议使用 5 | * 6 | * @参考资料: 7 | * http://ejohn.org/blog/javascript-method-overloading/ 8 | * http://ejohn.org/apps/learn/#90 9 | * 10 | * 解释说明: 11 | * http://stackoverflow.com/a/30989908/1672655 12 | * http://stackoverflow.com/a/18122417/1672655 13 | * 14 | * 最佳实践: http://stackoverflow.com/questions/456177/function-overloading-in-javascript-best-practices 15 | */ 16 | 17 | function addMethod(object, name, fn) { 18 | 19 | var old = object[name]; 20 | // 通过 name 获取旧的方法 21 | // 当第一次调用时可能为 undefined 22 | // Get the old function corresponding to this name. Will be "undefined" 23 | // the first time "addMethod" is called. 24 | 25 | object[name] = function () { 26 | // 然后给 object[name] 赋予新方法 27 | // 非常重要的一点是,原有的方法被缓存起来了(old),并且随时可以被调用到 28 | 29 | if (fn.length == arguments.length) { 30 | // 如果新函数接收到的参数数目和 fn 所需的参数数目一样,则调用 fn 方法 31 | return fn.apply(this, arguments); 32 | } else if (typeof old == 'function') { 33 | // 否则,如果之前缓存的 old 是 function,则调用 34 | return old.apply(this, arguments); 35 | } 36 | }; 37 | } 38 | 39 | function Ninjas() { 40 | var ninjas = ["Dean Edwards", "Sam Stephenson", "Alex Russell"]; 41 | addMethod(this, "find", function () { 42 | return ninjas; 43 | }); 44 | addMethod(this, "find", function (name) { 45 | var ret = []; 46 | for (var i = 0; i < ninjas.length; i++) 47 | if (ninjas[i].indexOf(name) == 0) 48 | ret.push(ninjas[i]); 49 | return ret; 50 | }); 51 | addMethod(this, "find", function (first, last) { 52 | var ret = []; 53 | for (var i = 0; i < ninjas.length; i++) 54 | if (ninjas[i] == (first + " " + last)) 55 | ret.push(ninjas[i]); 56 | return ret; 57 | }); 58 | } 59 | 60 | 61 | // USAGE 62 | // 63 | // var ninjas = new Ninjas(); 64 | // ninjas.find().length == 3 65 | // ninjas.find("Sam").length == 1 66 | // ninjas.find("Dean", "Edwards").length == 1 67 | // ninjas.find("Alex", "X", "Russell") == null 68 | -------------------------------------------------------------------------------- /js/mixins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript Mixins - What are they? 3 | * 4 | * 在计算机科学领域,mixin 代表着一个 class,其内部定义这一些列与某一类型相关的方法。 5 | * Mixins 的 class 通常是抽象化的,不会被实例化, 6 | * 反之,他们的方法被实例化的类所拷贝,类似于 “继承” ,但两者之间不必有什么联系。 7 | * 8 | * 但是在 JavaScript 中,没有类的概念(起码 ES5 以下没有)。但这实际上是件好事,因为我们可以使用对象(实例)来替代,并对它们清晰灵活的特点加以利用: 9 | * js 中的 mixin 可以是一个普通的对象,一个原型,一个方法或者其他什么东西,而且 mixin 的过程也会更加清晰、显而易见。 10 | * 11 | * 12 | * @参考资料: 13 | * https://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/ 14 | * https://lostechies.com/derickbailey/2012/10/07/javascript-mixins-beyond-simple-object-extension/ 15 | * http://raganwald.com/2014/04/10/mixins-forwarding-delegation.html 16 | * http://bob.yexley.net/dry-javascript-with-mixins/ 17 | * https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.osy5v7ih0 18 | * http://addyosmani.com/resources/essentialjsdesignpatterns/book/#mixinpatternjavascript 19 | */ 20 | 21 | // 创建一个函数,让目标接收 mixin 22 | // target:接收 mixin 的目标 23 | // source:mixin 24 | // methodNames:多个方法名/属性名,对应的方法/属性要被传递给 target 25 | function mixInto(target, source, methodNames) { 26 | 27 | // 通过 arguments 去除 target 和 source,确保之后的参数就是要传递的方法/属性 28 | var args = Array.prototype.slice.apply(arguments); 29 | target = args.shift(); 30 | source = args.shift(); 31 | methodNames = args; 32 | 33 | var method; 34 | var length = methodNames.length; 35 | for (var i = 0; i < length; i++) { 36 | method = methodNames[i]; 37 | 38 | // 围绕着 source 创建一个闭包函数 39 | // source 的 method 赋予给 target 的 method,但通过 apply,调用时的作用域还是 source 40 | target[method] = function () { 41 | var args = Array.prototype.slice(arguments); 42 | source[method].apply(source, args); 43 | } 44 | 45 | } 46 | 47 | } 48 | 49 | // make use of the mixin function 50 | var myApp = new Marionette.Application(); 51 | mixInto(myApp, Marionette.EventBinder, "bindTo", "unbindFrom", "unbindAll"); 52 | 53 | 54 | /** 55 | * Mixin 设计模式 56 | * 57 | * jsfiddle 链接:http://jsfiddle.net/quFa9/21/ 58 | */ 59 | 60 | // 戳这里可见 JavaScript 中 Mixin 设计模式的详细解释: 61 | // http://addyosmani.com/resources/essentialjsdesignpatterns/book/#mixinpatternjavascript 62 | 63 | /* Car Class */ 64 | var Car = function (settings) { 65 | this.model = settings.model || 'no model provided'; 66 | this.colour = settings.colour || 'no colour provided'; 67 | }; 68 | 69 | /* Mixin Class */ 70 | var Mixin = function () { }; 71 | Mixin.prototype = { 72 | driveForward: function () { 73 | console.log('drive forward'); 74 | }, 75 | driveBackward: function () { 76 | console.log('drive backward'); 77 | } 78 | }; 79 | 80 | /* 使用其他 class 的方法扩展一个已有的 class */ 81 | function augment(receivingClass, givingClass) { 82 | /* 提供一定数目的方法名 */ 83 | if (arguments[2]) { 84 | var i, len = arguments.length; 85 | for (i = 2; i < len; i++) { 86 | receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; 87 | } 88 | } 89 | /* 如果只有两个参数,则是把 givingClass 类内的全部方法都赋予给 receivingClass */ 90 | else { 91 | var methodName; 92 | for (methodName in givingClass.prototype) { 93 | // 要确保没有重复的名称 94 | if (!receivingClass.prototype[methodName]) { 95 | receivingClass.prototype[methodName] = givingClass.prototype[methodName]; 96 | } 97 | } 98 | } 99 | } 100 | 101 | /* Augment the Car class to have the methods 'driveForward' and 'driveBackward' */ 102 | augment(Car, Mixin, 'driveForward', 'driveBackward'); 103 | 104 | /* Create a new Car */ 105 | var vehicle = new Car({model: 'Ford Escort', colour: 'blue'}); 106 | 107 | /* Test to make sure we now have access to the methods */ 108 | vehicle.driveForward(); 109 | vehicle.driveBackward(); 110 | -------------------------------------------------------------------------------- /js/new-keyword.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 关于 new 关键字的一些事 3 | * 4 | * 当通过 new 来实例化一个构造函数时: 5 | * new ConstructorFunction(arg1, arg2); 6 | * 7 | * 它做了四件事: 8 | * 1. 创建一个新对象,仅仅是一个简单的对象 9 | * 2. 将对象的原型链 [[prototype]] 指向构造函数 prototype 属性上 10 | * 3. 使用新创建的对象(的作用域)来执行 ConstructorFunction 11 | * 4. 返回新创建的对象,除非构造方法返回的是一个原始值,那样的话就返回原始值 12 | * 13 | * 做好这些事以后,如果向新建的对象请求一个不存在的属性,则将会检查其原型链 [[prototype]] 上的对象 14 | * Functions, in addition to the hidden [[prototype]] property, also have a property called prototype, and it is this that you can access, and modify, to provide inherited properties and methods for the objects you make. 15 | * 16 | * 整个过程中,最难的部分就是第二步。所有的对象(包括函数)都有一个内置属性叫做原型链 [[prototype]] 17 | * 它只有在对象创建的时候才会进行设置,即当通过 new、Object.create,或者一些字面语句(方法依赖于 Function.prototype,数字依赖于 Number.prototype 等) 18 | * 原型链可以通过 Object.getPrototypeOf(someObject) 或者 __proto__ 或者 this.constructor.prototype 获取到 19 | * 20 | * @相关资料: 21 | * http://stackoverflow.com/questions/1646698/what-is-the-new-keyword-in-javascript 22 | * http://zeekat.nl/articles/constructors-considered-mildly-confusing.html 23 | * https://css-tricks.com/understanding-javascript-constructors/ 24 | * https://john-dugan.com/object-oriented-javascript-pattern-comparison/ 25 | */ 26 | 27 | // 当我们这样时 28 | function Foo() { 29 | this.kind = 'foo'; 30 | } 31 | 32 | var foo = new Foo(); 33 | foo.kind; //=> ‘foo’ 34 | 35 | // 在这背后其实有一系列的操作 36 | function Foo() { 37 | // 实际上是不合法的,这样做只是为了讲解 38 | var this = {}; // Step 1 39 | this.__proto__ = Foo.prototype; // Step 2 40 | this.kind = 'foo'; // Step 3 41 | return this; // Step 4 42 | } 43 | 44 | 45 | // 栗子 46 | ObjMaker = function() { 47 | this.a = 'first'; 48 | }; 49 | // ObjMaker 仅仅是个函数,没啥特别的 50 | 51 | 52 | ObjMaker.prototype.b = 'second'; 53 | // 跟其他函数一样,ObjMaker 有一个可以获取并改变的原型 prototype 属性 54 | // 但也与其他对象一样,ObjMaker 还有一个我们不能获取/改变的 [[prototype]] 原型链属性 55 | 56 | 57 | obj1 = new ObjMaker(); 58 | // 创建一个名为 obj1 的对象,一开始 obj1 和 {} 一样 59 | // 然后 obj1 的 [[prototype]] 属性设置为 ObjMaker.prototype 60 | // 注: 61 | // 即便 ObjMaker.prototype 之后指向一个新的值,obj1 的 [[prototype]] 也不会变 62 | // 但你可以通过添加 ObjMaker.prototype 中的属性,并将其加入到 obj1 的 prototype 和 [[prototype]] 中 63 | // ObjMaker 方法执行完成之后,obj1.a 将指向 'first' 64 | 65 | obj1.a; 66 | // first 67 | 68 | obj1.b; 69 | // obj1 中并没有叫做 b 的属性,因此 JavaScript 将会检查其原型链,即检查 ObjMaker.prototype 70 | // 而 ObjMaker.prototype 中有名为 b 的属性,所以返回 second 71 | 72 | // 这就像是类的继承,任何你通过 new ObjMaker() 创建的实例都会继承 b 属性 73 | // 如果你想要一个子类,可以这么干: 74 | // If you want something like a subclass, then you do this: 75 | SubObjMaker = function () {}; 76 | SubObjMaker.prototype = new ObjMaker(); // 注:不赞成使用这样的方式! 77 | // 当我们使用 new 来调用时,SubObjMaker.prototype 的原型链 [[prototype]] 属性指向 ObjMaker.prototype 78 | // 而更好的做法则是使用 ECMAScript 5 中的 Object.create() 方法: 79 | // SubObjMaker.prototype = Object.create(ObjMaker.prototype); 80 | 81 | SubObjMaker.prototype.c = 'third'; 82 | obj2 = new SubObjMaker(); 83 | // obj2 的 [[prototype]] 属性指向 SubObjMaker.prototype 84 | // 但请记住,SubObjMaker.prototype 的 [[prototype]] 指向 ObjMaker.prototype 85 | // obj2 ---> SubObjMaker.prototype ---> ObjMaker.prototype 86 | 87 | obj2.c; 88 | // returns 'third', from SubObjMaker.prototype 89 | 90 | obj2.b; 91 | // returns 'second', from ObjMaker.prototype 92 | 93 | obj2.a; 94 | // returns 'first', from SubObjMaker.prototype 95 | // 因为 SubObjMaker.prototype 是通过 ObjMaker 创建的,已经拥有了 a 属性 96 | -------------------------------------------------------------------------------- /js/number-maxmin-val.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 原生 JS 3 | * 4 | * Number.MAX_VALUE and Number.MIN_VALUE 5 | * 6 | * Number.MAX_VALUE: 1.7976931348623157e+308 7 | * Number.MIN_VALUE: 5e-324 8 | */ 9 | 10 | (function () { 11 | console.log(Number.MAX_VALUE > 0); // true 12 | console.log(Number.MIN_VALUE < 0); // false... WTF 13 | })(); 14 | -------------------------------------------------------------------------------- /js/object-clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 对象的克隆 3 | * Object copy by value (Clone) 4 | * 5 | * @参考资料: 6 | * http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object/ 7 | * http://stackoverflow.com/a/728694/1672655 8 | */ 9 | 10 | (function () { 11 | var obj = { 12 | name: 'vasa', 13 | role: 'Ninja' 14 | }; 15 | 16 | // 一个克隆对象的小 trick 17 | var clonedObj = JSON.parse(JSON.stringify(obj)); 18 | 19 | // ES6 20 | var clone = Object.assign({}, obj); 21 | 22 | // With jQuery 23 | // 浅拷贝 24 | var copiedObjShallow = jQuery.extend({}, obj); 25 | // 深度拷贝 26 | var copiedObjDeep = jQuery.extend(true, {}, obj); 27 | 28 | // Object.assign() polyfill 29 | // http://stackoverflow.com/a/34283281/1672655 30 | // 译者注: 31 | // 或者可以去查看 object-assign 这个 polyfill 的兼容性实现: 32 | // https://github.com/ecmadao/code-analysis/blob/master/analysis/object-assign.js 33 | 34 | if (!Object.assign) { 35 | Object.defineProperty(Object, 'assign', { 36 | enumerable: false, 37 | configurable: true, 38 | writable: true, 39 | value: function (target) { 40 | 'use strict'; 41 | if (target === undefined || target === null) { 42 | throw new TypeError('Cannot convert first argument to object'); 43 | } 44 | 45 | var to = Object(target); 46 | // 第一个参数为 target,之后的参数都是要拷贝进第一个对象的 47 | for (var i = 1; i < arguments.length; i++) { 48 | var nextSource = arguments[i]; 49 | if (nextSource === undefined || nextSource === null) { 50 | continue; 51 | } 52 | nextSource = Object(nextSource); 53 | 54 | var keysArray = Object.keys(nextSource); 55 | for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { 56 | var nextKey = keysArray[nextIndex]; 57 | // Object.getOwnPropertyDescriptor() 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性) 58 | var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); 59 | if (desc !== undefined && desc.enumerable) { 60 | to[nextKey] = nextSource[nextKey]; 61 | } 62 | } 63 | } 64 | return to; 65 | } 66 | }); 67 | } 68 | })(); 69 | -------------------------------------------------------------------------------- /js/object-constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 详解对象的创建 3 | * 对象就是一个包含原始类型数据(或者引用数据)的无序列表,并以键-值对的形式储存起来。 4 | * 列表中的每个元素都叫做属性(如果是函数的话则叫做方法) 5 | * 6 | * @参考资料: 7 | * http://javascriptissexy.com/javascript-objects-in-detail/ 8 | * https://css-tricks.com/understanding-javascript-constructors/ 9 | * http://stackoverflow.com/a/14172862/1672655 10 | */ 11 | 12 | // 对象的属性有自己的属性( Attributes ) 13 | // 对象的每个属性(用来储存数据)不仅仅是一个键值对,而且还包含了三个属性(默认情况下这三个属性都设置为 true): 14 | // - Configurable:如果是 false,该属性不可删除,且不可修改其他属性(改变 Writable,Configurable 或者 Enumerable 属性) 15 | // - Enumerable:如果是 true,则该属性可通过 for..in 或其他类似方法遍历 16 | // - Writable:如果是 false,则属性的值可修改 17 | 18 | 19 | // 通过构造模式创建对象 20 | function Fruit (theColor, theSweetness, theFruitName, theNativeToLand) { 21 | 22 | this.color = theColor; 23 | this.sweetness = theSweetness; 24 | this.fruitName = theFruitName; 25 | this.nativeToLand = theNativeToLand; 26 | 27 | this.showName = function () { 28 | console.log("This is a " + this.fruitName); 29 | }; 30 | 31 | this.nativeTo = function () { 32 | this.nativeToLand.forEach(function (eachCountry) { 33 | console.log("Grown in:" + eachCountry); 34 | }); 35 | }; 36 | } 37 | 38 | // 在这种模式下,可以快速便捷的创建对象 39 | var mangoFruit = new Fruit ("Yellow", 8, "Mango", ["South America", "Central America", "West Africa"]); 40 | mangoFruit.showName(); // This is a Mango.​ 41 | mangoFruit.nativeTo(); 42 | // Grown in:South America​ 43 | // Grown in:Central America​ 44 | // Grown in:West Africa​ 45 | 46 | var pineappleFruit = new Fruit ("Brown", 5, "Pineapple", ["United States"]); 47 | pineappleFruit.showName(); // This is a Pineapple. 48 | // 如果要更改 showName 方法,则要更改每个实例的 showName 49 | 50 | // 可以被继承的属性要定义成对象原型的属性,例如: 51 | someObject.prototype.firstName = 'rich'; 52 | 53 | // 而某个对象独有的属性则定义在对象内部,例如: 54 | // 先来创建一个新对象: 55 | var aMango = new Fruit (); 56 | // 现在我们利用 Fruit 实例化了一个 aMango 对象,然后直接赋予它 mangoSpice 属性 57 | // 而因为我们直接在 aMango 上定义了 mangoSpice 属性,所以这个属性是 aMango 独有的,不是一个继承的属性 58 | aMango.mangoSpice = 'some value'; 59 | 60 | 61 | // 通过原型模式创建对象 62 | function Fruit () { 63 | 64 | } 65 | 66 | Fruit.prototype.color = "Yellow"; 67 | Fruit.prototype.sweetness = 7; 68 | Fruit.prototype.fruitName = "Generic Fruit"; 69 | Fruit.prototype.nativeToLand = "USA"; 70 | 71 | Fruit.prototype.showName = function () { 72 | console.log("This is a " + this.fruitName); 73 | }; 74 | 75 | Fruit.prototype.nativeTo = function () { 76 | console.log("Grown in:" + this.nativeToLand); 77 | }; 78 | 79 | // 实例化的方式和构造模式一样 80 | var mangoFruit = new Fruit (); 81 | mangoFruit.showName(); //​ 82 | mangoFruit.nativeTo(); 83 | // This is a Generic Fruit​ 84 | // Grown in:USA 85 | 86 | 87 | // 获取到继承的属性 88 | // 从 Object.prototype 继承到的属性不可遍历,因此不会在 for/in 循环里出现 89 | // 但继承的其他可遍历的属性则会在循环中出现,例如: 90 | 91 | var school = {schoolName:"MIT", schoolAccredited: true, schoolLocation:"Massachusetts"}; 92 | // 通过 for/in 循环获取 school 对象的属性 93 | for (var eachItem in school) { 94 | console.log(eachItem); // Prints schoolName, schoolAccredited, schoolLocation​ 95 | } 96 | 97 | // 新建一个 HigherLearning 方法,并让 school 对象继承自它 98 | function HigherLearning () { 99 | this.educationLevel = "University"; 100 | } 101 | /* SIDE NOTE: 102 | 实际上通过 HigherLearning 的构造方式创建的对象,是不会继承 educationLevel 属性的; 103 | 反之,educationLevel 属性会作为一个新的属性,添加到通过构造方式新创建的对象里的。 104 | 之所以不会被继承,是因为我们是通过 this 关键字来定义的这个属性。 105 | */ 106 | 107 | // 使用构造方式创建 HigherLearning 的实例 108 | var school = new HigherLearning (); 109 | school.schoolName = "MIT"; 110 | school.schoolAccredited = true; 111 | school.schoolLocation = "Massachusetts"; 112 | 113 | // 通过 for/in 循环获取到 school 对象的属性 114 | for (var eachItem in school) { 115 | console.log(eachItem); // Prints educationLevel, schoolName, schoolAccredited, and schoolLocation​ 116 | } 117 | 118 | 119 | // 删除对象的属性 120 | // 如果要删除一个对象的属性,则需要通过 delete 操作 121 | // 你可以删除继承的属性,但是不能删除 configurable 设置为 false 的属性。 122 | // 但是你必须通过原型对象来删除继承的属性,而且,你也不能删除全局对象的属性(通过 var 关键字定义) 123 | // 如果删除成功,则会返回 true; 124 | // 而且,如果要删除的属性本身就不存在,或者属性不能被删除(non-configurable),或者属性不属于这个对象,删除操作也会返回 true 125 | 126 | var christmasList = {mike:"Book", jason:"sweater" }; 127 | delete christmasList.mike; // deletes the mike property​ 128 | 129 | for (var people in christmasList) { 130 | console.log(people); 131 | } 132 | // 只输出 jason​ 133 | // mike 属性被删除了 134 | 135 | delete christmasList.toString; 136 | // 返回 true, 但因为 toString 是个继承到的方法,没有被删除 137 | // 因此再次调用 toString 方法也一切正常 138 | christmasList.toString(); //"[object Object]"​ 139 | 140 | // 实例自身拥有的属性可以被删除 141 | // 例如下面,我们删除 school 对象实例的 educationLevel 属性,因为 educationLevel 属性是在 HigherLearning 方法内通过 this 关键字定义的,所以在实例化时成为了 school 实例的独有属性 142 | 143 | console.log(school.hasOwnProperty("educationLevel")); // true 144 | // educationLevel 是 school 对象独有的,因此可以被删除​ 145 | delete school.educationLevel; // true 146 | console.log(school.educationLevel); // undefined 147 | 148 | // 但是 educationLevel 属性还会在 HigherLearning 方法内 149 | var newSchool = new HigherLearning (); 150 | console.log(newSchool.educationLevel); // University​ 151 | 152 | // 如果我们在 HigherLearning 方法的原型上定义一个例如 educationLevel2 的属性: 153 | HigherLearning.prototype.educationLevel2 = "University 2"; 154 | // 因此,educationLevel2 不是 HigherLearning 内部的属性,所以实例化的 school 对象也没有自己的 educationLevel2 属性 155 | console.log(school.hasOwnProperty("educationLevel2")); // false 156 | console.log(school.educationLevel2); // University 2​ 157 | 158 | // 试着删除原型链上的 educationLevel2 属性 159 | delete school.educationLevel2; // true (跟之前说的一样,删除不属于这个对象的属性,也依旧返回 true) 160 | 161 | // 但 educationLevel2 没有被删除 162 | console.log(school.educationLevel2); // University 2​ 163 | 164 | 165 | // Object.defineProperty 方法 166 | // Object.defineProperty() 可以用于构造器内,用来辅助属性的创建 167 | 168 | function Book(name) { 169 | Object.defineProperty(this, 'name', { 170 | get: function() { 171 | return 'Book: ' + name; 172 | }, 173 | set: function(newName) { 174 | name = newName; 175 | }, 176 | configurable: false 177 | }); 178 | } 179 | 180 | var myBook = new Book('Single Page Web Applications'); 181 | console.log(myBook.name); // Book: Single Page Web Applications 182 | 183 | // 无法删除 name 属性,因为它的 configurable 是 false 184 | delete myBook.name; 185 | console.log(myBook.name); // Book: Single Page Web Applications 186 | 187 | // 但我们可以更改 name 属性的值 188 | myBook.name = "Testable JavaScript"; 189 | console.log(myBook.name); // Book: Testable JavaScript 190 | // 在上述代码中,我们通过 Object.defineProperty() 使用了访问器属性 191 | // 访问器属性除了 getter 和 setter 方法外,不包含其他属性和方法。 192 | // 其中,getter 方法会在你引用这个属性的时候调用,而 setter 方法则在你更改这个属性的值时调用。 193 | // getter 方法会返回一个值,而 setter 则接受一个值,并将其赋予给目标属性 194 | // 这个构造函数允许我们设置或更改实例的属性,但无法删除它。 195 | -------------------------------------------------------------------------------- /js/object-create.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Object.create() 3 | * 4 | * JavaScript provides multiple methods for creating new object instances. 5 | * To this day, the new operator appears to remain the most popular method, 6 | * even though it’s arguably the most problematic and least flexible approach. 7 | * 8 | * The Object.create method provides an improved alternative to the new operator, 9 | * with the following benefits: 10 | * - You can explicitly specify, at object creation time, the object that will be the prototype of the newly created object. 11 | * - You can create objects that have no prototype by specifying null as the prototype. 12 | * This is something that can’t otherwise be done. This can be useful when using an object as a dictionary, for example. 13 | * - You can easily specify the properties of the newly created object, 14 | * including their descriptors: configurable, enumerable, and writable. 15 | * 16 | * Enumerable: I can access to all of them using a for..in loop. Also, enumerable property keys of an object are returned using Object.keys method. 17 | * Writable: I can modify their values, I can update a property just assigning a new value to it: ob.a = 1000; 18 | * Configurable: If set to false, the properties can't be removed using the delete operator. I can modify the behavior of the property, so I can make them non-enumerable, non-writable or even non-configurable if I feel like doing so. 19 | * 20 | * Object.create() has been available in all browsers since IE9. 21 | * 22 | * @Reference: 23 | * http://engineering.wix.com/2015/04/21/javascript-the-extra-good-parts/ 24 | * http://arqex.com/967/javascript-properties-enumerable-writable-configurable 25 | * http://stackoverflow.com/questions/2709612/using-object-create-instead-of-new 26 | */ 27 | 28 | (function () { 29 | // Object.create. 30 | // When you first encounter this method, you might wonder why JavaScript needs another way to create objects, when it already has the object literal syntax and constructor functions? 31 | // Where Object.create differs from those options is that lets you provide, as the first argument to the method, an object that will become the new object’s prototype. 32 | // Remember that there is a difference between an object’s public prototype property and its internal [[Prototype]] property. 33 | // When JavaScript is looking up properties on an object, it uses the latter, but traditionally the only standardised way to control it for a new object has been to use the pattern applied by __extends. 34 | // You create a new function with a public prototype property, then apply the new operator on the function to create a new object. 35 | // When the new operator is used with a function, the runtime sets the [[Prototype]] property of the new object to the object referenced by the public prototype property of the function. 36 | // While this approach to controlling the [[Prototype]] works, it is a little opaque and wasteful, requiring the declaration of a new function simply for the purpose of controlling this internal property. 37 | // With Object.create, the extra function is no longer required, as the [[Prototype]] can be controlled directly. 38 | // A dead simple example would be. 39 | 40 | var animal = {legs: 4}; 41 | var dog; 42 | 43 | dog = Object.create(animal); 44 | dog.legs == 4; // True 45 | })(); 46 | 47 | 48 | (function () { 49 | var x = Object.create(null, {prop: {value: 3, writable: false}}); 50 | console.log(x.prop); // output 3 51 | x.prop = 5; 52 | console.log(x.prop); // still output 3, since writable is false 53 | })(); 54 | 55 | 56 | // Traditional constructor usage - `new` keyword. 57 | (function () { 58 | var UserA = function (nameParam) { 59 | this.name = nameParam; 60 | this.studentYear = 'sophomore'; 61 | }; 62 | 63 | UserA.prototype.sayHello = function () { 64 | console.log('Hello ' + this.name); 65 | }; 66 | 67 | var bob = new UserA('bob'); 68 | bob.sayHello(); 69 | })(); 70 | 71 | // With Object.create() 72 | // http://stackoverflow.com/a/2709811/1672655 73 | (function () { 74 | var userB = { 75 | sayHello: function () { 76 | console.log('Hello ' + this.name); 77 | } 78 | }; 79 | 80 | // Object.create() lets you initialize object properties using its second argument 81 | var bob = Object.create(userB, { 82 | 'studentYear': { 83 | value: 'sophomore', 84 | enumerable: true // writable:false, configurable(deletable):false by default 85 | }, 86 | 'name': { 87 | value: 'Bob', 88 | enumerable: true 89 | } 90 | }); 91 | })(); 92 | 93 | /** 94 | * Simple Object.create() polyfill 95 | */ 96 | /** 97 | * Object.create() 98 | * 99 | * The crux of the matter with this Object.create method is that you pass into it an object that you want to inherit from, 100 | * and it returns a new object that inherits from the object you passed into it. 101 | */ 102 | Object.create = function (o) { 103 | //It creates a temporary constructor F()​ 104 | function F() { 105 | } 106 | 107 | //And set the prototype of the this constructor to the parametric (passed-in) o object​ 108 | //so that the F() constructor now inherits all the properties and methods of o​ 109 | F.prototype = o; 110 | 111 | //Then it returns a new, empty object (an instance of F())​ 112 | //Note that this instance of F inherits from the passed-in (parametric object) o object. ​ 113 | //Or you can say it copied all of the o object's properties and methods​ 114 | return new F(); 115 | }; -------------------------------------------------------------------------------- /js/object-defineProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Object.defineProperty() 3 | * 4 | * Enumerable: 5 | * 设置为 true 时,可以通过 for..in 循环 和 Object.keys 方法获取到该属性 6 | * 7 | * Writable: 8 | * 可以更改属性的值 9 | * 10 | * Configurable: 11 | * 可以配置属性的行为。Configurable 是唯一一个可以通过 delete 删除掉的属性 12 | * 13 | * @参考资料: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty 14 | * http://x-team.com/2015/02/es5-object-defineproperty-method/ 15 | * http://arqex.com/967/javascript-properties-enumerable-writable-configurable 16 | */ 17 | 18 | var ob = {}; 19 | // 通过 Object.defineProperty 给 ob 对象新建一个属性 20 | Object.defineProperty(ob, 'c', { 21 | value: 3, 22 | enumerable: false, 23 | writable: false, 24 | configurable: false 25 | }); 26 | 27 | console.log(ob.c); // => 3 28 | 29 | Object.getOwnPropertyDescriptor(ob, 'c'); 30 | // => {value: 3, enumerable: false, writable: false, configurable: false} 31 | 32 | // 除此以外,还可以通过 Object.create( prototype, properties ) ,在创建对象的时候赋予其属性。 33 | // 使用方式如下: 34 | var ob = Object.create(Object.prototype, { 35 | a: {writable: true, enumerable: true, value: 1}, 36 | b: {enumerable: true, value: 2} 37 | }); 38 | console.log(ob); // => {a:1, b:2} 39 | 40 | var ob = Object.create(Object.prototype, { 41 | a: {writable: true, enumerable: true, value: 1}, 42 | b: {enumerable: true, value: 2} 43 | }); 44 | console.log(ob); // => {a:1, b:2} 45 | 46 | // 我们的目标是创建一个 Person 构造函数,它接收两个参数:firstName 和 lastName 47 | // 这个对象会暴露出四个属性:firstName, lastName, fullName 和 species 48 | // 前三个属性都是可变的,而最后一个则是常量 “human” 49 | 50 | // Object.defineProperty (> IE8) 51 | var Person = function (first, last) { 52 | this.firstName = first; 53 | this.lastName = last; 54 | }; 55 | 56 | Object.defineProperty(Person, 'species', { 57 | writable: false, 58 | value: 'human' 59 | }); 60 | 61 | Object.defineProperty(Person, 'fullName', { 62 | get: function () { 63 | return this.firstName + ' ' + this.lastName; 64 | }, 65 | set: function (value) { 66 | var splitString = value.trim().split(' '); 67 | 68 | if (splitString.length === 2) { 69 | this.firstName = splitString[0]; 70 | this.lastName = splitString[1]; 71 | } 72 | } 73 | }); 74 | 75 | var woman = new Person('Kate', 'Khowalski'); 76 | 77 | console.log(woman.firstName); // 'Kate' 78 | console.log(woman.lastName); // 'Khowalski' 79 | console.log(woman.fullName); //'Kate Khowalski 80 | console.log(woman.species); // human 81 | 82 | /* 83 | * Change name 84 | */ 85 | 86 | woman.firstName = 'Yulia'; 87 | console.log(woman.firstName); // 'Yulia' 88 | console.log(woman.lastName); // 'Khowalski' 89 | console.log(woman.fullName); // 'Yulia Khowalski' 90 | woman.species = 'fish'; 91 | console.log(woman.species); // human - 因为 writable 为 false,所以无法修改 92 | 93 | /* 94 | * Change fullName 95 | */ 96 | 97 | woman.fullName = 'Joana Stevens'; 98 | console.log(woman.firstName); //Joana 99 | console.log(woman.lastName); //Stevens 100 | -------------------------------------------------------------------------------- /js/object-freeze.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Object.freeze() 3 | * 这个方法将冻结一个对象,这意味着,被冻结的对象无法添加新属性;无法移除已有属性;无法修改属性的属性(可遍历性/配置性/可写性) 4 | * 从本质上讲就是让对象不可变了。 5 | * 6 | * 注意: 7 | * 如果一个冻结的对象内部有属性的值是其他对象,则那些对象还是可以修改的,除非他们也被冻结。即是说,freeze 方法只是浅冻结。 8 | * 9 | * @参考资料: 10 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze 11 | * http://adripofjavascript.com/blog/drips/immutable-objects-with-object-freeze.html 12 | * 13 | */ 14 | 15 | var obj = { 16 | prop: function() {}, 17 | foo: 'bar' 18 | }; 19 | 20 | // 加入新属性、修改旧属性,并删除了一个旧属性 21 | obj.foo = 'baz'; 22 | obj.lumpy = 'woof'; 23 | delete obj.prop; 24 | 25 | // 冻结对象 26 | Object.freeze(obj); 27 | 28 | // 检查是否已冻结 29 | console.log(Object.isFrozen(obj) === true); // True 30 | 31 | // 然后之前的操作就没有作用了(在严格模式下会抛出错误) 32 | obj.foo = 'quux'; // 没用哒 33 | obj.quaxxor = 'the friendly duck'; // 也是没用哒 34 | 35 | 36 | 37 | /** 38 | * freeze 是浅冻结,接下来我们尝试创建一个可以深度冻结的方法 39 | * deepFreeze() 40 | */ 41 | 42 | obj1 = { 43 | internal: {} 44 | }; 45 | 46 | Object.freeze(obj1); 47 | obj1.internal.a = 'aValue'; 48 | 49 | console.log(obj1.internal.a); // aValue 50 | 51 | // 如果要深度冻结一个对象,则需要冻结对象上的每一个对象 52 | function deepFreeze(obj) { 53 | // 获取 obj 自身拥有的属性 54 | var propNames = Object.getOwnPropertyNames(obj); 55 | 56 | // 在冻结对象之前,依次冻结它内部的每个对象 57 | propNames.forEach(function (name) { 58 | var prop = obj[name]; 59 | 60 | // 如果属性值是个对象,则冻结通过递归来它 61 | if (typeof prop == 'object' && prop !== null && !Object.isFrozen(prop)) { 62 | deepFreeze(prop); 63 | } 64 | }); 65 | 66 | return Object.freeze(obj); 67 | } 68 | 69 | // 测试 deepFreeze 70 | var obj2 = { 71 | internal: {} 72 | }; 73 | 74 | deepFreeze(obj2); 75 | obj2.internal.a = 'anotherValue'; 76 | console.log(obj2.internal.a); // undefined 77 | -------------------------------------------------------------------------------- /js/object-keys.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 通过 Object.keys() 遍历对象的属性 3 | * 4 | * 遍历对象的属性在 JavaScript 中是一个常见操作了,通过 for...in 原生方法可以完成它, 5 | * 但 for...in 是一个稍有问题方法,需要配合 hasOwnProperty 一起使用,剔除掉原型链上的属性。 6 | * 而更好更干净的方法是通过 Object.keys 获取到对象的键组成的数组,然后遍历数组。因此你可以自己筛选或者修改数组。 7 | * 8 | * Object.keys 被 IE9 之后的浏览器兼容 9 | * 10 | * @参考资料: 11 | * http://engineering.wix.com/2015/04/21/javascript-the-extra-good-parts/ 12 | */ 13 | 14 | 15 | // 使用 for..in 方法 16 | (function () { 17 | var x = {hello: 1, there: 2, world: 3}; 18 | for (var key in x) { 19 | if (x.hasOwnProperty(key)) { 20 | console.log(key, x[key]); 21 | } 22 | } 23 | // 输出三条结果:hello 1, there 2, world 3 24 | })(); 25 | 26 | // 使用 Object.keys() 方法 27 | (function () { 28 | var x = {hello: 1, there: 2, world: 3}; 29 | Object.keys(x).forEach((function (key) { 30 | console.log(key, x[key]); 31 | })); 32 | // 输出三条结果: hello 1, there 2, world 3 33 | })(); 34 | -------------------------------------------------------------------------------- /js/object-oriented.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript 面向对象编程 3 | * 4 | * 基于原型链的编程模式其实就是一种面向对象的模式,只是没有运用类的概念。但通过装饰或扩展原型链上的对象,可对方法和属性进行复用(也就相当于含有类的语言中,继承的概念)。因此,JavaScript 中的面向对象编程,也被叫做无类的、面向原型链的基于实例的编程。 5 | * 6 | * 继承 7 | * 继承是基于一个或多个类(JavaScript 中只支持单继承),创建自己的类的过程。 8 | * 新建的类一般叫做子类,而被继承的类则叫做父类。 9 | * 在 JavaScript 中,只要把父类的实例声明给子类就可以创建成功。在现代浏览器中,你可以通过 Object.create 方法来完成继承。 10 | * 11 | * 多态 12 | * 多态是多种数据类型的接口展示。 13 | * 例如,integer、float、double 数据就是隐式的多态:且不管它们不同的三种类型,它们都可以被求和、相减、求积等等。 14 | * 在面向对象的理念里,每个类只对自己拥有的方法和代码负责,而通过多态,可以让每个类都有自己的方法。 15 | * 16 | * 封装 17 | * 封装是将数据和方法打包进一个组件里(例如一个类),然后通过这个类控制其行为。 18 | * 因此,使用这个类的时候,只需要知道它的接口就行(也就是说,必要的属性和方法要对外暴露)。 19 | * 它使得我们可以编写高内聚的抽象化代码。 20 | * 21 | * 为什么要封装? 22 | * 当你只是想创建一个储存数据的简单对象,而且这个对象只有它自己一类时,按照普通的方式来创建对象就好。 23 | * 这种情况十分常见,你肯定也经常这么干。 24 | * 但当你想创建有相似功能的对象(一样的属性和方法),你可以把主要功能封装到一个函数里,利用函数的构造方法去创建对象。这就是封装的本质了。 25 | * 26 | * @参考资料: 27 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript 28 | * http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/ 29 | * http://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor 30 | * http://www.toptal.com/javascript/javascript-prototypes-scopes-and-performance-what-you-need-to-know 31 | */ 32 | 33 | /** 34 | * 封装 35 | */ 36 | 37 | // 混合 构造方法/原型链 两种模式 38 | function User (theName, theEmail) { 39 | this.name = theName; 40 | this.email = theEmail; 41 | this.quizScores = []; 42 | this.currentScore = 0; 43 | } 44 | 45 | // 复写原型对象 -- 并不建议,因为这样就打断了原型链。但为了加深理解我们就这么干吧。 46 | User.prototype = { 47 | // 复写原型链的缺点之一就是,对象的构造方法已不再指向原型,因此我们必须手动进行设置。 48 | constructor: User, 49 | saveScore:function (theScoreToAdd) { 50 | this.quizScores.push(theScoreToAdd) 51 | }, 52 | showNameAndScores:function () { 53 | var scores = this.quizScores.length > 0 ? this.quizScores.join(",") : "No Scores Yet"; 54 | return this.name + " Scores: " + scores; 55 | }, 56 | changeEmail:function (newEmail) { 57 | this.email = newEmail; 58 | return "New Email Saved: " + this.email; 59 | } 60 | }; 61 | 62 | // A User ​ 63 | firstUser = new User("Richard", "Richard@examnple.com"); 64 | firstUser.changeEmail("RichardB@examnple.com"); 65 | firstUser.saveScore(15); 66 | firstUser.saveScore(10); 67 | 68 | firstUser.showNameAndScores(); //Richard Scores: 15,10​ 69 | 70 | // Another User​ 71 | secondUser = new User("Peter", "Peter@examnple.com"); 72 | secondUser.saveScore(18); 73 | secondUser.showNameAndScores(); //Peter Scores: 18 74 | 75 | /** 76 | * Object.create() 77 | * 78 | * Object.create 接收一个对象作为被继承的对象,并返回一个全新的对象 79 | */ 80 | Object.create = function (o) { 81 | // 创建一个临时的构造方法 F() 82 | function F() { 83 | } 84 | // 并将构造方法的原型指向传入的参数 -- o 对象 85 | // 因此 F() 构造方法现在继承了 o 的所有属性和方法 86 | F.prototype = o; 87 | 88 | // 最后,返回一个全新的对象(F 的实例) 89 | // 要记住的是,F 的实例继承自传入的 o 对象 90 | // 或者你可以说,它完全拷贝了 o 的属性和方法 91 | return new F(); 92 | }; 93 | 94 | // 使用案例 95 | // 一个简单的 cars 对象 96 | var cars = { 97 | type:"sedan", 98 | wheels:4 99 | }; 100 | 101 | // 我们想要继承 cars 对象,所以: 102 | var toyota = Object.create (cars); // 现在 toyota 继承了 cars​ 103 | console.log(toyota.type); // sedan 104 | 105 | 106 | /** 107 | * 面向对象编程的例子 108 | */ 109 | // 定义 Person 构造方法 110 | var Person = function(firstName) { 111 | this.firstName = firstName; 112 | }; 113 | 114 | // 给 Person.prototype 增加方法 115 | Person.prototype.walk = function(){ 116 | console.log("I am walking!"); 117 | }; 118 | 119 | Person.prototype.sayHello = function(){ 120 | console.log("Hello, I'm " + this.firstName); 121 | }; 122 | 123 | // 定义 Student 构造方法 124 | function Student(firstName, subject) { 125 | // 调用父类的构造方法,并利用 Function#call 来确保 this 作用域正确 126 | Person.call(this, firstName); 127 | 128 | // 初始化属性 129 | this.subject = subject; 130 | } 131 | 132 | // Student.prototype 对象继承自 Person.prototype 133 | // 注意: 134 | // 一个常见的错误是,利用 new Person() 来创建 Student.prototype 135 | // 这犯了几个错,其中一个就是,我们此时还不会给 Person 传入参数,因为无法通过 new 来创建 136 | Student.prototype = Object.create(Person.prototype); 137 | 138 | // 将原型链上的 constructor 指向 Student 139 | Student.prototype.constructor = Student; 140 | 141 | // 覆写 sayHello 方法 142 | Student.prototype.sayHello = function(){ 143 | console.log("Hello, I'm " + this.firstName + ". I'm studying " 144 | + this.subject + "."); 145 | }; 146 | 147 | // 新增 sayGoodBye 方法 148 | Student.prototype.sayGoodBye = function(){ 149 | console.log("Goodbye!"); 150 | }; 151 | 152 | // Example usage: 153 | var student1 = new Student("Janet", "Applied Physics"); 154 | student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." 155 | student1.walk(); // "I am walking!" 156 | student1.sayGoodBye(); // "Goodbye!" 157 | 158 | // 检查原型链 159 | console.log(student1 instanceof Person); // true 160 | console.log(student1 instanceof Student); // true 161 | -------------------------------------------------------------------------------- /js/object-prototype.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript 的 .prototype 是如何工作的? 3 | * 4 | * 每个 JavaScript 对象都有一个内置的属性,叫做 [[Prototype]] 5 | * 如果你无法通过 obj.propName 或者 obj['propName'] 获取到某个属性属性的,但可以通过 obj.hasOwnProperty('propName') 检查到的话,那么它就是在 [[Prototype]] 上了。 6 | * 如果对象的原型对象也没有这个属性,那么会继续查找原型对象的原型对象,即遍历原型链来查找对应的属性 7 | * 8 | * 有时候通过非标准的属性 __proto__ ,可以直接获取到 [[Prototype]] 属性 9 | * 简而言之,只有在创建对象的时候才能设置对象的原型: 10 | * 如果你通过 new Func() 新建对象,对象的 [[Prototype]] 属性会直接设置为 Func.prototype 11 | * 因此,尽管 JavaScript 中的继承系统是基于原型而不是类,我们还是可以在 JavaScript 中模拟出类的概念: 12 | * 13 | * 将构造方法看做是类,原型的属性看做是共有的成员。 14 | * 在基于类的系统中,对于从某个类构造的各个实例而言,方法的执行都是一样的,因此在 JavaScript 中,通用的方法都会放在原型中,而不同对象内自身的属性和方法都是独特的。 15 | * 16 | * 两种东西可以被看做是“原型”: 17 | * 1. 原型属性,obj.prototype里的 18 | * 2. 原型内部的属性,在 ES5 中用 [[Prototype]] 表示 19 | * - 它可以通过 ES5 的 Object.getPrototypeOf() 方法获取到 20 | * - 在 Firefox 中,可以通过 __proto__ 属性获取到 21 | * 22 | * Two different things can be called "prototype": 23 | * the prototype property, as in obj.prototype 24 | * the prototype internal property, denoted as [[Prototype]] in ES5. 25 | * - It can be retrieved via the ES5 Object.getPrototypeOf(). 26 | * - Firefox makes it accessible through the __proto__ property as an extension. ES6 now mentions some optional requirements for __proto__. 27 | * 28 | * __proto__ is used for the dot . property lookup as in obj.property. 29 | * .prototype is not used for lookup directly, only indirectly as it determines __proto__ at object creation with new. 30 | * 31 | * 看一看创建对象时的顺序: 32 | * 1. 通过 obj.p = ... 或者 Object.defineProperty(obj, ...) 创建对象属性 33 | * 2. 创建 obj.__proto__ 34 | * 3. 创建 obj.__proto__.__proto__,并以此继续下去 35 | * 4. 如果 某个 __proto__ 是 null,则返回 undefined (注:在原型链的最顶层,Object.__proto__ 就是 null) 36 | * 37 | * 这就叫做原型链。 38 | * 你可以通过 obj.hasOwnProperty('key') 和 Object.getOwnPropertyNames(f) 获取到只属于对象的属性和方法 39 | * 40 | * Object.prototype 属性被所有的对象继承 41 | * JavaScript 中的所有对象都从 Object.prototype 继承了属性和方法,这些属性和方法有 constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf 42 | * ECMAScript 5 还在 Object.prototype 中添加了 4 个访问器方法 43 | * 44 | * @参考资料: 45 | * http://stackoverflow.com/questions/572897/how-does-javascript-prototype-work/23877420 46 | * https://medium.com/@will_gottchalk/javascript-interview-questions-javascript-is-a-prototypal-language-what-do-i-mean-by-this-76937a9aa42a#.23dpi96xy 47 | * https://css-tricks.com/understanding-javascript-constructors/ 48 | * http://javascriptissexy.com/javascript-prototype-in-plain-detailed-language/ 49 | * http://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/ 50 | * https://davidwalsh.name/javascript-objects 51 | * http://stackoverflow.com/a/32740085/1672655 52 | */ 53 | 54 | // 有两种方式可以设置 obj.__proto__ : 55 | 56 | // 1. new: 57 | var F = function() {}; 58 | var f = new F(); 59 | // imagine: 60 | // f.__proto__ = F.prototype; 61 | 62 | // then new has set: 63 | f.__proto__ === F.prototype; 64 | 65 | //This is where .prototype gets used. 66 | 67 | 68 | 69 | // 2. Object.create: 70 | var g = Object.create(proto); 71 | // imagine: 72 | // g.__proto__ = proto 73 | 74 | // sets: 75 | g.__proto__ === proto; 76 | 77 | 78 | 79 | // 1. 原型属性 80 | // 81 | // 每一个 JavaScript 函数都有一个原型属性(默认为空),当你想实现继承的时候,可以在原型属性上获取到属性和方法。 82 | // 原型属性不可遍历,因此也就无法在 for/in 循环中获取到;它也主要用于继承;除此以外,你还可以给原型属性添加属性或方法,由此可以让创建出来的实例都可以使用那些属性/方法 83 | function PrintStuff (myDocuments) { 84 | this.documents = myDocuments; 85 | } 86 | 87 | // 我们给 PrintStuff 的原型属性添加一个 print 方法,因此其他的实例对象也可以使用到 88 | PrintStuff.prototype.print = function () { 89 | console.log(this.documents); 90 | }; 91 | 92 | // 通过 PrintStuff 的构造方法创建一个新对象,这个新对象继承了 PrintStuff 的属性和方法 93 | var newObj = new PrintStuff ("I am a new Object and I can print."); 94 | 95 | // 因此 newObj 可以直接调用 print 方法 96 | newObj.print (); //I am a new Object and I can print. 97 | 98 | 99 | 100 | // 2. 原型的属性 Attribute 101 | // 102 | // 将原型的属性看做是对象的特征;这个特征可以让我们知道当前对象的父对象是谁。 103 | // 简而言之:对象的原型属性(attribute)指向该对象的父对象,也就是从哪里继承的属性 104 | // The prototype attribute is normally referred to as the prototype object, and it is set automatically when you create a new object. 105 | // Every object inherits properties from some other object, and it is this other object that is the object’s prototype attribute or “parent.” 106 | // (You can think of the prototype attribute as the lineage or the parent). 107 | 108 | // 对象的原型属性在通过对象继承或者 new Object () 的时候创建 109 | 110 | // userAccount 继承自一个空对象 111 | var userAccount = new Object (); 112 | 113 | var userAccount = {name: 'Mike'}; 114 | 115 | 116 | // 通过构造方法来创建原型 117 | function Account () { 118 | 119 | } 120 | var userAccount = new Account (); 121 | // 通过 Account 的构造方法初始化 userAccount ,它的原型指向 Account.prototype 122 | 123 | 124 | // 原型链 -- 模拟多继承 125 | // @参考资料: http://markdalgleish.com/2012/10/a-touch-of-class-inheritance-in-javascript/ 126 | 127 | // Our 'actor' object has some properties... 128 | var actor = { 129 | canAct: true, 130 | canSpeak: true 131 | }; 132 | 133 | // silentActor 继承自 actor 134 | var silentActor = Object.create(actor); 135 | silentActor.canSpeak = false; 136 | 137 | // busterKeaton 继承自 silentActor 138 | var busterKeaton = Object.create(silentActor); 139 | 140 | Object.getPrototypeOf(busterKeaton); // silentActor 141 | Object.getPrototypeOf(silentActor); // actor 142 | Object.getPrototypeOf(actor); // Object 143 | 144 | // 修改原型链 145 | 146 | // 有趣的是,在运行时如果修改 actor 或者 silentActor,还是可以影响到已经初始化过的原型链 147 | // 如果所有的演员(actor)丢了工作: 148 | silentActor.isEmployed = false; 149 | 150 | // 那么.. 151 | busterKeaton.isEmployed; // false 152 | 153 | // Setting up Multiple inheritance using the `new` keyword 154 | // Set up Actor 155 | function Actor() {} 156 | Actor.prototype.canAct = true; 157 | 158 | // Set up SilentActor to inherit from Actor: 159 | function SilentActor() {} 160 | SilentActor.prototype = Object.create(Actor.prototype); 161 | 162 | // We can now add new properties to the SilentActor prototype: 163 | SilentActor.prototype.canSpeak = false; 164 | 165 | // So instances can act, but can't speak: 166 | var charlie = new SilentActor(); 167 | charlie.canAct; // true 168 | charlie.canSpeak; // false 169 | -------------------------------------------------------------------------------- /js/object-reference.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 对象的引用 3 | * 4 | * @参考资料: 5 | * http://ejohn.org/apps/learn/#13 6 | * http://ejohn.org/apps/learn/#14 7 | * http://stackoverflow.com/questions/22216159/an-object-null-and-behaviour-in-javascript 8 | */ 9 | 10 | // Program 1 11 | (function () { 12 | var ninja = { 13 | yell: function (n) { 14 | return n > 0 ? ninja.yell(n - 1) + "a" : "hiy"; 15 | } 16 | }; 17 | console.log(ninja.yell(4) == "hiyaaaa"); 18 | 19 | var samurai = {yell: ninja.yell}; 20 | var ninja = null; 21 | 22 | try { 23 | console.log(samurai.yell(4)); 24 | } catch (e) { 25 | console.log(false, "Uh, this isn't good! Where'd ninja.yell go?"); 26 | } 27 | // 这段代码无法正常工作,因为在 ninja.yell 方法内,又再次引用了 ninja 对象: 28 | // return n > 0 ? ninja.yell(n-1) + "a" : "hiy"; 29 | // 因此,之后将 null 赋予给 ninja 后,这段代码就会抛出错误,因为 null 没有 yell 属性 30 | })(); 31 | 32 | // Program 2 33 | (function () { 34 | var ninja = { 35 | yell: function yell(n) { // 使用一个命名函数 36 | return n > 0 ? yell(n - 1) + "a" : "hiy"; // 使用 yell 替代 ninja.yell 37 | } 38 | }; 39 | console.log(ninja.yell(4) == "hiyaaaa"); 40 | 41 | var samurai = {yell: ninja.yell}; // 在创建 ninja 对象之前 ninja.yell 就已经声明好了(是因为声明了一个命名函数 yell) 42 | var ninja = null; 43 | 44 | try { 45 | console.log(samurai.yell(4)); 46 | } catch (e) { 47 | console.log(false, "Uh, this isn't good! Where'd ninja.yell go?"); 48 | } 49 | // Program 2 可以正常工作,因为创建了一个命名函数,然后将 ninja.yell 引用到了这个函数上 50 | // Program 2 works because, instead of referring to the object that holds the function (ninja), 51 | // you are giving the function a name and directly refer to that name. 52 | })(); 53 | -------------------------------------------------------------------------------- /js/oloo-pattern.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OLOO 设计模式探索 3 | * (OLOO,将对象链接到其他对象上,即 objects linked to other objects) 4 | * 5 | * @参考资料: 6 | * https://gist.github.com/getify/d0cdddfa4673657a9941 7 | * https://gist.github.com/getify/5572383 8 | * https://gist.github.com/getify/9895188 9 | * https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/ch6.md 10 | */ 11 | 12 | // 构造函数 vs OLOO 13 | 14 | // 构造函数方式 15 | function Foo() { 16 | } 17 | Foo.prototype.y = 11; 18 | 19 | function Bar() { 20 | } 21 | // Object.create(proto[, propertiesObject]) method creates a new object with the specified prototype object and properties. 22 | Bar.prototype = Object.create(Foo.prototype); 23 | Bar.prototype.z = 31; 24 | 25 | var x = new Bar(); 26 | console.log(x.y + x.z); // 42 27 | 28 | 29 | // OLOO 方式 30 | var FooObj = {y: 11}; 31 | 32 | var BarObj = Object.create(FooObj); 33 | BarObj.z = 31; 34 | 35 | var x = Object.create(BarObj); 36 | console.log(x.y + x.z); // 42 37 | 38 | 39 | /** 40 | * Class 语法 vs OLOO 41 | */ 42 | 43 | // ES6 Class 44 | class Foo { 45 | constructor(x, y, z) { 46 | // Object.assign 可用于拷贝对象,将多个目标对象拷贝到 target 对象中,并返回 target 47 | Object.assign(this, {x, y, z}); 48 | } 49 | 50 | hello() { 51 | console.log(this.x + this.y + this.z); 52 | } 53 | } 54 | 55 | var instances = []; 56 | for (var i = 0; i < 500; i++) { 57 | instances.push( 58 | new Foo(i, i * 2, i * 3) 59 | ); 60 | } 61 | instances[37].hello(); // 222 62 | 63 | 64 | // OLOO 65 | function Foo(x, y, z) { 66 | return { 67 | hello() { 68 | console.log(this.x + this.y + this.z); 69 | }, 70 | x, 71 | y, 72 | z 73 | }; 74 | } 75 | 76 | var instances = []; 77 | 78 | for (var i = 0; i < 500; i++) { 79 | instances.push( 80 | Foo(i, i * 2, i * 3) 81 | ); 82 | } 83 | instances[37].hello(); // 222 84 | -------------------------------------------------------------------------------- /js/setTimeout-inside-loop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 在 for() 循环内 setTimeout() 3 | * 4 | * @TLDR:如果每次循环所需的时间小于 setTimeout 等待的时间,那么 setTimeout 只有在循环执行完毕之后才会执行 5 | * 同样,只有在循环执行完成之后,setTimeout 才能拿到所需的变量。 6 | * 7 | * @Info:使用立即执行函数(IIFE)来创建闭包,锁住变量 8 | * 9 | * @参考资料: 10 | * Best explanation out there on Event loops: 11 | * https://www.youtube.com/watch?v=8aGhZQkoFbQ 12 | * 13 | */ 14 | 15 | (function () { 16 | 17 | // setTimeout() inside a loop. 18 | for (var i = 1; i <= 3; i++) { 19 | setTimeout(function () { 20 | console.log(i); // 输出 4 4 4 21 | }, 1000); 22 | } 23 | 24 | // 如果用 ES6 的 let 就不会有这种问题 25 | for (let i = 1; i <= 3; i++) { 26 | setTimeout(function () { 27 | console.log(i); // 输出 1 2 3 28 | }, 1000); 29 | } 30 | 31 | // 使用 IIFE,在闭包内锁住变量 32 | for (var i = 1; i <= 3; i++) { 33 | (function (index) { 34 | setTimeout(function () { 35 | console.log(index); // 输出 1 2 3 36 | }, 1000); 37 | })(i); 38 | } 39 | 40 | // 注:当 IIFE 在 setTimeout 内部时,还是可以正常工作,但不等倒计时结束就会马上打印出值, 41 | // setTimeout 本质上也就是无效的。 42 | for (var i = 1; i <= 3; i++) { 43 | setTimeout((function (index) { 44 | console.log(index); // 输出 1 2 3 45 | })(i), 1000); 46 | } 47 | 48 | // 但只要在 setTimeout 的回调内返回一个函数,则 setTimeout 内部配合 IIFE 也可以正常使用 49 | for (var i = 1; i <= 3; i++) { 50 | setTimeout((function (index) { 51 | return function () { 52 | console.log(index); // prints 1 2 3 53 | }; // 需要在 IIFE 内返回一个函数,这样 setTimeout 就能正常运行 54 | })(i), 1000); 55 | } 56 | 57 | // 注: 58 | // setTimeout 和 setInterval 还接收一个参数,将传递给回调函数 59 | // Thanks: https://twitter.com/WebReflection/status/701091345679708161 60 | for (var i = 0; i < 10; i++) { 61 | setTimeout(function (i) { 62 | console.log(i); 63 | // 将会输出 0 1 2 3 4 5 6 7 8 9 64 | }, 1000, i) 65 | } 66 | 67 | // 还有一种方法是将函数拆分出去 68 | for (var i = 0; i < 10; i++) { 69 | registerTimeout(i); 70 | } 71 | function registerTimeout (i) { 72 | setTimeout(function () { 73 | console.log(i); 74 | // 将会输出 0 1 2 3 4 5 6 7 8 9 75 | }, 1000); 76 | } 77 | })(); 78 | -------------------------------------------------------------------------------- /js/shim-polyfill-monkeypatch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shim vs Polyfill vs Monkey Patch 3 | * 4 | * Shim: 5 | * 在英语里,shim 意味着 -- 一排可能会调用的参数,使其整体具有兼容性 6 | * shim 是多段 API 调用的组合,它们的目的是为了兼容不同的环境: 7 | * 两个不同的浏览器可能对同一种 API 的实现方式不一样,你可以拦截 API 的调用,然后通过 shim 实现不同浏览器间的一致性; 8 | * 或者可能某个浏览器对一些 API 有 bug,你也可能通过这种方式来避免 bug 9 | * 例:https://github.com/es-shims/es5-shim 10 | * 11 | * Polyfill: 12 | * polyfill 是一段提供给开发者的代码(或插件),用来处理浏览器对某些 API 的兼容情况。 13 | * 因此,polyfill 可以说是针对浏览器 API 的 shim。你可以检查浏览器是否支持某种 API,如果不支持,则加载对应的 polyfill 14 | * 15 | * Monkey Patching: 16 | * 一个最佳实践是,永远不要去自己修改一个线上正使用的库。随意升级改造的库可能引发一系列噩梦,并难以维护。 17 | * 所以如果库有一个 bug的话,你可以使用 monkey patch 的方式去修补。 18 | * monkey patch 是什么?其实就是方法的覆盖,以此“修复”原生的代码。 19 | * 20 | * @参考资料: 21 | * https://github.com/vasanthk/simple-polyfill-array-find-es6 22 | * https://remysharp.com/2010/10/08/what-is-a-polyfill 23 | * http://addyosmani.com/blog/writing-polyfills/ 24 | * http://www.codeproject.com/Articles/369858/Writing-polyfills-in-Javascript 25 | * http://blog.respoke.io/post/111278536998/javascript-shim-vs-polyfill 26 | * https://davidwalsh.name/monkey-patching 27 | * http://benno.id.au/blog/2010/01/01/monkey-patching-javascript 28 | * http://me.dt.in.th/page/JavaScript-override/ 29 | * http://benno.id.au/blog/2010/01/01/monkey-patching-javascript 30 | */ 31 | 32 | /** 33 | * SHIM 34 | * Shim layer for requestAnimationFrame with setTimeout fallback 35 | * 36 | * @Reference: 37 | * http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ 38 | */ 39 | 40 | window.requestAnimFrame = (function () { 41 | return window.requestAnimationFrame || 42 | window.webkitRequestAnimationFrame || 43 | window.mozRequestAnimationFrame || 44 | function (callback) { 45 | window.setTimeout(callback, 1000 / 60); 46 | }; 47 | })(); 48 | 49 | // Usage: 50 | // Instead of setInterval(render, 16) 51 | (function animloop() { 52 | requestAnimFrame(animloop); 53 | render(); 54 | })(); 55 | // Place the rAF *before* the render() to assure as close to 60fps with the setTimeout fallback. 56 | 57 | 58 | /** 59 | * POLYFILL 60 | * 一个针对 Array.prototype.forEach() 的简单 polyfill 61 | * 62 | * @参考资料: 63 | * http://javascriptplayground.com/blog/2012/06/writing-javascript-polyfill/ 64 | * 65 | */ 66 | 67 | Array.prototype.forEach = function (callback, thisArg) { 68 | if (typeof(callback) !== 'function') { 69 | throw new TypeError(callback + ' is not a function'); 70 | } 71 | var len = this.length; 72 | for (var i = 0; i < len; i++) { 73 | // this[i] 代表每次遍历获取的元素,i 则是其 index 74 | // this 代表调用 forEach 方法的 array 75 | callback.call(thisArg, this[i], i, this); 76 | } 77 | }; 78 | 79 | // Usage 80 | var arr = [1, 2, 3]; 81 | arr.forEach(function (item, index, th) { 82 | console.log(item, index, th); 83 | }); 84 | 85 | // Output 86 | // 1 0 [ 1, 2, 3 ] 87 | // 2 1 [ 1, 2, 3 ] 88 | // 3 2 [ 1, 2, 3 ] 89 | 90 | 91 | /** 92 | * MONKEY PATCHING 93 | * Simple example to monkey patch a method in an object 94 | * 95 | * @Reference: 96 | * https://gist.github.com/vasanthk/5edd3a1f5f1231221fa4 97 | */ 98 | 99 | // 原生的方法 100 | var object = { 101 | method: function (x, y) { 102 | return x + y; 103 | } 104 | }; 105 | 106 | // 使用 monkey patch 对其进行覆盖 107 | // 但要注意的是使用了一个闭包,已确保 object.method 内的作用域传递到了新的方法里 108 | object.method = (function (original) { 109 | return function (x, y) { 110 | // before 111 | // we could here modify 'arguments' to alter original input 112 | console.log(x, '+', y, '?'); 113 | 114 | // 调用原生方法 115 | var result = original.apply(this, arguments); 116 | 117 | // after 118 | // here we could work on result to alter original output 119 | console.log('=', result); 120 | 121 | // aaaand the result 122 | return result; 123 | } 124 | })(object.method); 125 | -------------------------------------------------------------------------------- /js/string-methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 字符串方法 3 | * 4 | * @参考资料: 5 | * http://www.w3schools.com/js/js_string_methods.asp 6 | * http://techiejs.com/Blog/Post/Essential-JavaScript-String-Functions 7 | * https://rapd.wordpress.com/2007/07/12/javascript-substr-vs-substring/ 8 | * http://www.bennadel.com/blog/2159-using-slice-substring-and-substr-in-javascript.htm 9 | * 10 | */ 11 | 12 | // String.prototype.charAt() 13 | var myName = "Oleg Shalygin"; 14 | // 使用方式: myName.charAt(index) 15 | var letter = myName.charAt(6); // h 16 | 17 | 18 | // String.prototype.indexOf() 19 | var fullName = "Oleg Shalygin"; 20 | // 使用方式: fullName.indexOf(searchTerm, startingIndex) 21 | var index = fullName.indexOf("Oleg"); // 0 22 | index = fullName.indexOf("Shalygin"); // 5 23 | index = fullName.indexOf("l"); // 1 24 | index = fullName.indexOf("l",4); // 8 25 | index = fullName.indexOf("Girlfriend"); // -1 26 | 27 | 28 | // String.prototype.lastIndexOf() 29 | var storyMode = "Once upon a time, there was a magical foobar that was controlling the universe..."; 30 | console.log(storyMode.lastIndexOf(",")); // 16 31 | console.log(storyMode.lastIndexOf(".")); // 80 32 | console.log(storyMode.lastIndexOf("!")); // -1 33 | console.log(storyMode.lastIndexOf("foobar")); // 38 34 | 35 | 36 | // String.prototype.match() 37 | var someString = "Hello there, my name is aararand and I am a magical foobarus creature"; 38 | var resultsArray = someString.match(/a{2}/); 39 | // resultsArray = ["aa", index: 24, input: "Hello there, my name is aararand and I am a magical foobarus creature"] 40 | var someOtherResultsArray = someString.match(/b{2}/); 41 | // someOtherResultsArray = null 42 | 43 | 44 | // String.prototype.replace() 45 | var someString = "Hello there, my name is aararand and I am a magical foobarus creature"; 46 | var newString = someString.replace(/a{2}/, "lol"); 47 | // newString = "Hello there, my name is lolrarand and I am a magical foobarus creature" 48 | 49 | 50 | // String.prototype.slice() 51 | var story = "Foobarus is a magical unicorn with an ID of 21313 which flies higher than all other unicorns. Unicorns fly? Regardless!"; 52 | var previewStory = story.slice(0, 40); // Foobarus is a magical unicorn with an ID 53 | 54 | 55 | // String.prototype.split() 56 | var story = "Foobarus is a magical unicorn with an ID of 21313 which flies higher than all other unicorns. Unicorns fly? Regardless!"; 57 | var previewStory = story.split("."); 58 | console.log(previewStory[0]); // Foobarus is a magical unicorn with an ID of 21313 which flies higher than all other unicorns 59 | console.log(previewStory[1]); // Unicorns fly? Regardless! 60 | 61 | 62 | // String.prototype.substring() 63 | // 注: 64 | // substring 方法的第二个参数代表截取停止位置的 index(不包含最后这个值) 65 | // 而 substr 的第二份参数则代表截取长度 66 | // 语法: string.substr(start, length); 67 | // 语法: string.substring(start, stop); 68 | // 69 | // 除此以外,slice() 和 substring() 类似,但不同之处在于 slice 可以接收负的 index,即从数组末尾开始操作 70 | var story = "Foobarus is a magical unicorn with an ID of 21313 which flies higher than all other unicorns. Unicorns fly? Regardless!"; 71 | var theLastFewCharacters = story.substring(story.length - 20); 72 | console.log("..." + theLastFewCharacters); // ...ns fly? Regardless! 73 | 74 | 75 | // String.prototype.toLowerCase() 和 String.prototype.toUpperCase() 76 | var story = "Foobarus is a magical unicorn with an ID of 21313 which flies higher than all other unicorns. Unicorns fly? Regardless!"; 77 | var allUpperCase = story.toUpperCase(); 78 | var allLowerCase = story.toLowerCase(); 79 | var foobarus = allUpperCase.match(/FOOBARUS/); // ["FOOBARUS", index: 0, input: "FOOBARUS IS A MAGICAL UNICORN WITH AN ID OF 21313 …N ALL OTHER UNICORNS. UNICORNS FLY? REGARDLESS!] 80 | 81 | 82 | // String.prototype.trim() 83 | var fullName = " Oleg Shalygin "; 84 | var trimmedFullName = fullName.trim(); // Oleg Shalygin 85 | 86 | 87 | // String.prototype.concat() 88 | var firstName = "Oleg"; 89 | var lastName = "Shalygin"; 90 | var ID = 12321312; 91 | // 使用方式: firstName.concat(string2, string 3, string 3, ...); 92 | var fullIdentification = firstName.concat(lastName, ":", ID); 93 | console.log(fullIdentification); // OlegShalygin:12321312 94 | -------------------------------------------------------------------------------- /js/styling.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript 中的样式操作 3 | * 4 | * @参考资料: 5 | * http://javascript.info/tutorial/view-and-position 6 | * http://stackoverflow.com/a/21064102/1672655 7 | * https://developer.mozilla.org/en/docs/Web/API/Element/classList 8 | * 9 | * 译者注: 10 | * 补充一份中文资料: 11 | * http://www.zhangxinxu.com/wordpress/2011/09/cssom%E8%A7%86%E5%9B%BE%E6%A8%A1%E5%BC%8Fcssom-view-module%E7%9B%B8%E5%85%B3%E6%95%B4%E7%90%86%E4%B8%8E%E4%BB%8B%E7%BB%8D/ 12 | */ 13 | 14 | // className 15 | document.body.className += ' class3'; 16 | 17 | // classList 18 | var classList = document.body.classList, // returns *live* DOMTokenList collection 19 | i; 20 | 21 | classList.add('class1', 'class2'); 22 | 23 | if (classList.contains('class1') === true) { 24 | classList.remove('class1'); 25 | } 26 | 27 | for (i = 0; i < classList.length; i++) { 28 | console.log(i, classList.item(i)); 29 | } 30 | 31 | // style 32 | document.body.style.backgroundColor = 'green'; 33 | 34 | // cssText 35 | // style.cssText 是增加 !important 的唯一方式 36 | var div = document.body.children[0]; 37 | div.style.cssText = 'color: red !important; \ 38 | background-color: yellow; \ 39 | width: 100px; \ 40 | text-align: center; \ 41 | blabla: 5; \ 42 | '; 43 | // blabla 将被忽略 44 | alert(div.style.cssText); 45 | 46 | // Reading the style 47 | // Note: The style gives access only to properties set through it, or with "style" attribute. 48 | // 51 | // 52 | // 55 | // 56 | 57 | // getComputedStyle 58 | // 语法:getComputedStyle(element, pseudo) 59 | // element - 要被获取样式的 DOM 元素 60 | // pseudo - A pseudo-selector like ‘hover’ or null if not needed. 61 | var computedStyle = getComputedStyle(document.body, null); 62 | alert(computedStyle.marginTop); 63 | 64 | // CSS盒模型 65 | 66 | // clientWidth/Height 67 | // 静态区域的大小,包含 padding,不包含 scrollbar 68 | 69 | // scrollWidth/Height 70 | // 返回 元素的内容区域宽度/高度 或 元素的本身的宽度/高度中更大的那个值 71 | // scrollWidth/Height 和 clientWidth/Height 很像,不过它包含了整个可滚动的区域 72 | element.style.height = element.scrollHeight + 'px'; 73 | 74 | // scrollTop/scrollLeft 75 | // 垂直和水平方向上滚动的距离,单位为 px 76 | // scrollLeft/scrollTop 是可写的,你可以改变它们的值来改变浏览器的滚动距离 77 | // 在标准模式下,document 的滚动值在 document.documentElement 下 78 | document.documentElement.scrollTop += 10; 79 | 80 | // offsetWidth/Height 81 | // 测量元素的布局宽度/高度,包含 padding 和 border,不包含 margin 82 | 83 | // clientTop/Left 84 | // 内容区域的左上角相对于整个元素左上角的位置(包括边框) 85 | 86 | // offsetParent, offsetLeft/Top 87 | // offsetLeft 和 offsetTop 体现的是一个元素和它 offsetParent 元素的相对偏移量 88 | // 如果一个元素的定位为 绝对定位,则它的 offsetParent 不一定是父元素,而是最近的定位元素(没有的话则是 body) 89 | // 我们可以使用它来检查一个元素是否隐藏 90 | function isHidden(elem) { 91 | return !elem.offsetWidth && !elem.offsetHeight; 92 | } 93 | // 总结 94 | // 戳这幅图: http://javascript.info/files/tutorial/browser/dom/metricSummary.png 95 | // 96 | // clientWidth/clientHeight - 可见区域 border 内的高度/宽度(这部分也叫静态区域,client area),它包含了 padding 但不包含 scrollbar 的宽度 97 | // clientLeft/clientTop - client area 到左上角的偏移量 98 | // scrollWidth/scrollHeight - 内容高度/宽度,包括 padding,不包括 scrollbar 99 | // scrollLeft/scrollTop - 滚动的水平/垂直距离 100 | // offsetWidth/offsetHeight - 测量元素的布局宽度/高度,包含 padding 和 border,不包含 margin 101 | // offsetParent - 返回一个指向最近的包含该元素的定位元素 102 | // offsetLeft/offsetTop - 距离 offsetParent 的偏移量 103 | 104 | // elem.getBoundingClientRect() 105 | // 返回一个包含该元素的矩形,该矩形本质上是一个对象,包含了 top, left, right, bottom 等属性 106 | // 注: 107 | // 其坐标系相对于 window 而不是 document 108 | // 例如,如果你滚动页面,一个 button 滚动到 window 顶部了,那么通过 getBoundingClientRect 获取到的 top 应该接近于 0(因为相对于 window);如果要基于 document 计算的话,就必须把滚动算进去 109 | function showRect(elem) { 110 | var r = elem.getBoundingClientRect(); 111 | alert("Top/Left: " + r.top + " / " + r.left); 112 | alert("Right/Bottom: " + r.right + " / " + r.bottom); 113 | } 114 | 115 | // 计算坐标 116 | // 1) 获取最近的 rectangle 117 | // 2) 计算页面滚动。除 IE9 一下的浏览器外,滚动距离都可以通过 pageXOffset/pageYOffset 获取到;否则通过 scrollTop/scrollLeft 118 | // 3) 在 IE 中,document(html 或 body)会被偏移,需要获取这个偏移量 119 | // 4) 使用元素基于 window 的坐标,再跟之前几步计算出的结果结合起来,求得元素基于 document 的坐标 120 | function getOffsetRect(elem) { 121 | // (1) 122 | var box = elem.getBoundingClientRect(); 123 | 124 | var body = document.body; 125 | var docElem = document.documentElement; 126 | 127 | // (2) 128 | var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop; 129 | var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft; 130 | 131 | // (3) 132 | var clientTop = docElem.clientTop || body.clientTop || 0; 133 | var clientLeft = docElem.clientLeft || body.clientLeft || 0; 134 | 135 | // (4) 136 | var top = box.top + scrollTop - clientTop; 137 | var left = box.left + scrollLeft - clientLeft; 138 | 139 | return {top: Math.round(top), left: Math.round(left)}; 140 | } 141 | -------------------------------------------------------------------------------- /js/this-keyword.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JS 中的 this 关键字 3 | * 4 | * @参考资料: 5 | * http://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work 6 | * http://stackoverflow.com/a/33979892/1672655 7 | * http://stackoverflow.com/a/17514482/1672655 8 | * http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/ 9 | * http://www.sitepoint.com/mastering-javascripts-this-keyword/ 10 | * http://www.quirksmode.org/js/this.html 11 | * https://javascriptweblog.wordpress.com/2010/08/30/understanding-javascripts-this/ 12 | * https://www.codementor.io/javascript/tutorial/understanding--this--in-javascript 13 | * 14 | * 避免使用 this 关键字 15 | * https://luizfar.wordpress.com/2012/04/28/dont-use-this-in-javascript/ 16 | * http://stackoverflow.com/questions/31891931/why-does-jslint-forbid-the-this-keyword 17 | * http://stackoverflow.com/questions/28525393/how-can-i-get-rid-of-the-this-keyword-in-local-functions 18 | * http://stackoverflow.com/questions/30125464/how-to-avoid-the-this-and-new-keywords-in-javascript 19 | * 20 | */ 21 | 22 | // 1. 全局 this 23 | console.log(this === window); // true 24 | var foo = "bar"; 25 | console.log(this.foo); // "bar" 26 | console.log(window.foo); // "bar" 27 | 28 | // 2. 函数里的 this 29 | foo = "bar"; 30 | function testThis() { 31 | this.foo = "foo"; 32 | } 33 | console.log(this.foo); // logs "bar" 34 | testThis(); 35 | console.log(this.foo); // logs "foo" 36 | 37 | // 3. 原型链上的 this 38 | function Thing() { 39 | console.log(this.foo); 40 | } 41 | Thing.prototype.foo = "bar"; 42 | var thing = new Thing(); // logs "bar" 43 | console.log(thing.foo); // logs "bar" 44 | 45 | // 4. 对象里的 this 46 | var obj = { 47 | foo: "bar", 48 | logFoo: function () { 49 | console.log(this.foo); 50 | } 51 | }; 52 | obj.logFoo(); // logs "bar" 53 | 54 | // 5. DOM 事件上的 this 55 | function Listener() { 56 | document.getElementById("foo").addEventListener("click", 57 | this.handleClick); 58 | } 59 | Listener.prototype.handleClick = function (event) { 60 | console.log(this); // logs "
    " 61 | }; 62 | var listener = new Listener(); 63 | document.getElementById("foo").click(); // logs "
    " 64 | 65 | // 6. HTML 里的 this 66 | 67 | //
    68 | // 71 | 72 | // 7. jQuery 里的 this 73 | //
    74 | //
    75 | // 86 | 87 | // 8. 在 call(), apply() and bind() 方法里的 this 88 | 89 | function add(inc1, inc2) { 90 | return this.a + inc1 + inc2; 91 | } 92 | 93 | var o = {a: 4}; 94 | document.write(add.call(o, 5, 6) + "
    "); // 15 95 | // add.call(o,5,6) 将外层 this 作用域代入到了内部 96 | // 调用 add() 时: 97 | // this.a + inc1 + inc2 即 98 | // o.a(4) + 5 + 6 = 15 99 | document.write(add.apply(o, [5, 6]) + "
    "); // 15 100 | // o.a(4) + 5 + 6 = 15 101 | 102 | var g = add.bind(o, 5, 6); // g: `o.a` i.e. 4 + 5 + 6 103 | document.write(g() + "
    "); // 15 104 | 105 | var h = add.bind(o, 5); // h: `o.a` i.e. 4 + 5 + ? 106 | document.write(h(6) + "
    "); // 15 107 | // 4 + 5 + 6 = 15 108 | document.write(h() + "
    "); // NaN 109 | // 没有给 h() 传入参数 110 | // 因此,在 add 内的 inc2 是 undefined 111 | // 4 + 5 + undefined = NaN 112 | --------------------------------------------------------------------------------