├── 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 | // one | two |
256 | // three | four |
257 | //
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 | //
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 | //
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 |
--------------------------------------------------------------------------------