You-Dont-Know-JS
2017-05-11 22:48
267 查看
最近在读 You-Dont-Know-JS 这本书,作者由浅及深地讲解了 JS 的基本语法,还提出了很多不为人知的细节。感觉收获颇多。
下面是我自己总结的一些书中的知识,主要是自己以前不太了解的一些细节。
JS中的 prototype 机制本质上来讲就是将对象连接到其他对象 (objects being linked to other objects)。使用行为代理 (behavior delegation) 来描述 prototype 机制更加合适,而不是类。
作者定义了一种新的代风格 “OLOO” (objects-linked-to-other-objects), 来区别与 “OO” (object-oriented).
下面来看看这两种风格的代码具体是怎么表现的。
上面是经典的 OO 风格的代码,子类 Bar 继承父类 Foo,将 Bar.prototype 指向 Foo.prototype,然后实例化为 b1 和 b2。
下面使用 OLOO 风格实现相同功能的代码
可以看到 OOLO 风格的代码中只是将对象连接到其他的对象,而不用处理一些令人疑惑的概念,比如 calss, constructor, prototype, new.
更加详细的介绍可以看 github 上面的原文。
但更精确地说 Promise.resolve() 不是能解析 Promise 对象,而是嗯那个解析 thenable 对象,系包含 then() 函数的对象。例如:
但我实际测试情况是在最新的 chrome 浏览器中,如果 true 输出1,false 则输出2. 也就是说输出结果和直观感觉是一样的。
看下面的代码
bar 已经绑定在 obj1 上了,但是当 new bar(3) 并没有改变 obj1.a 的值,而是改变了 baz.a 的值。 也就是 new 操作改变了使用 bind 绑定的 this。
这是因为 new 操作会新建一个对象,并将 this 指向这个对象,然后调用构造函数。类似于以下的操作:
如果 myObject 已经有一个 foo 的属性,那么就会直接覆盖。
如果没有这个属性,则会往 myObject 的原型链上寻找:
如果在原型链上找到了 foo 这个属性,且属性值不为 writable:false,那么 foo 这个属性会添加到 myObject 上;
如果在原型链上找到了 foo 这个属性,且属性值 writable:false,那就不会在 myObject 上添加 foo 属性;
如果在原型链上找到了 foo 这个属性,且是一个 setter 函数,那么就是调用这个setter 函数。 foo 不会添加到 myObject.
注意下面代码中 anotherObject.b = 4 没有生效
下面代码中 anotherObject.b = 4 也没有生效
但如果想要将 foo 属性添加到 myObject 中,可以使用 Object.defineProperty() 的方法。
再看下面代码
执行 myObject.a++ 的结果实际是 myObject.a = myObject.a + 1 ,也就是获取 myObject.a 是从原型链得到2,而赋值是在 myObject.a 操作的。
使用 bind 传入第二个参数可以实现类似柯里化的操作
在使用 < >比较两个对象时,会首先调用 ToPrimitive(toString 或 valueOf)。所以 a 会变成 [object Object],b 也是 [object Object]。
在使用 == 比较两个对象时,直接比较两个对象的地址,因此也是 false。
而在 a<= b 时,实际是先比较 a > b,然后对其结果取反,所以是true。
打上标记的代码块可以使用 break 来结束执行。如下代码,在 break bar 后面的代码不会被执行。
上面的代码在 chrome 下执行会打印出:
很奇怪的结果,所以想要观察准确的变量值还是使用断点调试为好。
下面是我自己总结的一些书中的知识,主要是自己以前不太了解的一些细节。
行为代理
在 JS 中,我们使用 prototype 来实现面向对象中的“继承”。但作者认为,就是因为我们把 prototype 机制描述为类或是继承,所以才导致了 JS 中的 prototype 难以理解。JS中的 prototype 机制本质上来讲就是将对象连接到其他对象 (objects being linked to other objects)。使用行为代理 (behavior delegation) 来描述 prototype 机制更加合适,而不是类。
作者定义了一种新的代风格 “OLOO” (objects-linked-to-other-objects), 来区别与 “OO” (object-oriented).
下面来看看这两种风格的代码具体是怎么表现的。
function Foo(who) { this.me = who; } Foo.prototype.identify = function() { return "I am " + this.me; }; function Bar(who) { Foo.call( this, who ); } Bar.prototype = Object.create( Foo.prototype ); Bar.prototype.speak = function() { alert( "Hello, " + this.identify() + "." ); }; var b1 = new Bar( "b1" ); var b2 = new Bar( "b2" ); b1.speak(); b2.speak();
上面是经典的 OO 风格的代码,子类 Bar 继承父类 Foo,将 Bar.prototype 指向 Foo.prototype,然后实例化为 b1 和 b2。
下面使用 OLOO 风格实现相同功能的代码
var Foo = { init: function(who) { this.me = who; }, identify: function() { return "I am " + this.me; } }; var Bar = Object.create( Foo ); Bar.speak = function() { alert( "Hello, " + this.identify() + "." ); }; var b1 = Object.create( Bar ); b1.init( "b1" ); var b2 = Object.create( Bar ); b2.init( "b2" ); b1.speak(); b2.speak();
可以看到 OOLO 风格的代码中只是将对象连接到其他的对象,而不用处理一些令人疑惑的概念,比如 calss, constructor, prototype, new.
更加详细的介绍可以看 github 上面的原文。
Thenable
我们都知道 Promise 可以使用 resolve() 来解析普通的变量或是 Promise 对象。例如:Promise.resolve(2).then(console.log); // => 2 Promise.resolve(new Promise(resolve => resolve(2) )).then(console.log); // = > 2
但更精确地说 Promise.resolve() 不是能解析 Promise 对象,而是嗯那个解析 thenable 对象,系包含 then() 函数的对象。例如:
let thenable = { then: function (resolve, reject) { resolve(2) } }; Promise.resolve(thenable).then(console.log); // => 2
变量提升
考虑下面代码,作者写的是在 ES6 以前的环境中,因为变量提示的缘故,所以 if() 中无论是 true 还是 false,第二个始终会覆盖第一个于是打印出 2。 而在 ES6 环境中,则会抛出 ReferenceError。但我实际测试情况是在最新的 chrome 浏览器中,如果 true 输出1,false 则输出2. 也就是说输出结果和直观感觉是一样的。
if(true){ function foo(){ console.log(1) } }else{ function foo(){ console.log(2) } } foo();
bind and new
我们知道 new 和 bind 都会绑定 this,那么 bind 和 new 一起用会发生什么?看下面的代码
function foo(something) { this.a = something; } var obj1 = {}; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // => 2 var baz = new bar( 3 ); console.log( obj1.a ); // => 2 console.log( baz.a ); // => 3
bar 已经绑定在 obj1 上了,但是当 new bar(3) 并没有改变 obj1.a 的值,而是改变了 baz.a 的值。 也就是 new 操作改变了使用 bind 绑定的 this。
这是因为 new 操作会新建一个对象,并将 this 指向这个对象,然后调用构造函数。类似于以下的操作:
var obj = {}; obj.__proto__ = foo.prototype; foo.call(obj);
setter
当我们对一个设置属性时,并不是简单地增加一个新属性,比如下面代码:myObject.foo = "bar";
如果 myObject 已经有一个 foo 的属性,那么就会直接覆盖。
如果没有这个属性,则会往 myObject 的原型链上寻找:
如果在原型链上找到了 foo 这个属性,且属性值不为 writable:false,那么 foo 这个属性会添加到 myObject 上;
如果在原型链上找到了 foo 这个属性,且属性值 writable:false,那就不会在 myObject 上添加 foo 属性;
如果在原型链上找到了 foo 这个属性,且是一个 setter 函数,那么就是调用这个setter 函数。 foo 不会添加到 myObject.
注意下面代码中 anotherObject.b = 4 没有生效
var anotherObject = { a: 2 }; var myObject = Object.create( anotherObject ); Object.defineProperty(anotherObject,'b',{ value:1, writable:false, }); anotherObject.b = 4; console.log(anotherObject.b); // => 1
下面代码中 anotherObject.b = 4 也没有生效
var anotherObject = { a: 2 }; var myObject = Object.create( anotherObject ); Object.defineProperty(anotherObject,'b',{ get:function () { return 3; } }); anotherObject.b=4; console.log(anotherObject.b); // => 3
但如果想要将 foo 属性添加到 myObject 中,可以使用 Object.defineProperty() 的方法。
再看下面代码
var anotherObject = { a: 2 }; var myObject = Object.create( anotherObject ); anotherObject.a; // 2 myObject.a; // 2 myObject.a++; // oops, implicit shadowing! anotherObject.a; // 2 myObject.a; // 3
执行 myObject.a++ 的结果实际是 myObject.a = myObject.a + 1 ,也就是获取 myObject.a 是从原型链得到2,而赋值是在 myObject.a 操作的。
spread and curry
使用 apply 可以展开参数,类似与 ES6 中的展开运算符。使用 bind 传入第二个参数可以实现类似柯里化的操作
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // spreading out array as parameters foo.apply( null, [2, 3] ); // => a:2, b:3 // currying with `bind(..)` var bar = foo.bind( null, 2 ); bar( 3 ); // => a:2, b:3
隐式转换
看下面代码,是不是很奇怪var a = { b: 42 }; var b = { b: 43 }; a < b; // false a == b; // false a > b; // false a <= b; // true a >= b; // true
在使用 < >比较两个对象时,会首先调用 ToPrimitive(toString 或 valueOf)。所以 a 会变成 [object Object],b 也是 [object Object]。
在使用 == 比较两个对象时,直接比较两个对象的地址,因此也是 false。
而在 a<= b 时,实际是先比较 a > b,然后对其结果取反,所以是true。
labeled statements
仔细看下面代码 bar:{…}, 这里并不是创建一个对象字面量。而是将一个代码块打上标记 (labeled statements) 。打上标记的代码块可以使用 break 来结束执行。如下代码,在 break bar 后面的代码不会被执行。
function foo() { // `bar` labeled-block bar: { console.log( "Hello" ); break bar; console.log( "never runs" ); } console.log( "World" ); } foo(); // => Hello // => World
async console
浏览器中的 console 实际是异步执行的,因此有时候打印出来的变量跟实际的变量会有所不同。var a = { index: 1 }; // later console.log( a ); // ?? // even later a.index++;
上面的代码在 chrome 下执行会打印出:
很奇怪的结果,所以想要观察准确的变量值还是使用断点调试为好。
相关文章推荐
- 《You dont know JS》类型篇总结
- JavaScript-读 You Dont Know JS, Object到底是什么
- 《You dont know JS》强制类型转换
- 《You dont know JS》原生函数
- JavaScript-读 You Dont Know JS,this到底是什么
- 《You dont know JS》值相关总结
- JavaScript-读 You Dont Know JS,原型继承不是继承
- 《You Don't Know JS》
- 读you don't know js 有感之作用域
- Most Enjoyable DisOrDats on YouDontKnowJack.com
- So, you think you know JavaScript? (你认为你懂JS吗)
- 5ThingsToKnowBeforeYouGetStartedWithAngulerJS
- 读书笔记——You Don't Know Js Up & Going
- You don't know js
- Note On <You Don't Know JS - Scope and Closures>
- Node.js modules you should know about: request
- Note On <You Don't Know JS - this and Object Prototypes>
- You Don't Know JS: Types & Grammar 总结
- if you dont know where to go, it doesnt matter which way to go.
- https://github.com/oneuijs/You-Dont-Need-jQuery