《你不知道的JavaScript上卷》读书笔记

[toc]

昨天看到前端早读课的情封大大票圈发团购《你不知道的JavaScript》中卷的活动,毫不犹豫的买了一本。刚好在准备校招的面试,于是把上卷翻出怀念一下(segmentfault送的第一本书),顺便复习整理一下。

this

当一个函数被调用时,会创建一个活动记录(有时候也成为执行上下文)。这个记录会包含函数在哪里被调用(调用栈),函数的调用方式,传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

如何判断

  1. 函数是否在new中国调用(new绑定)?如果是的话this绑定的是创建的对象。

    1
    var bar = new foo();
  2. 函数是否通过call, apply(显示绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

    1
    var bar = foo.call(obj2);
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

    1
    var bar = obj1.foo();
  4. 如果都不是的话,使用默认绑定,如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

    1
    var bar = foo();

例外
null或者undefined作为this的绑定对象传入call, apply或者bind,这些值在调用的时候会被忽略,实际应用的是默认绑定规则。
解决方法:创建一个空的非委托的对象

1
2
var dmzObj = Object.create(null);
foo.apply(dmzObj, [2, 3]);


es6箭头中的箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo() {
// 返回一个箭头函数
return (a) => {
// this继承来自 foo()
console.log(this.a)
};
}
var obj1 = {
a: 2;
};
var obj2 = {
a: 3;
}
var bar = foo.call(obj1);
bar.call(obj2); // 2,不是3

foo() 内部创建的箭头函数会捕获调用时 foo()this 。由于 foo()this 绑定到 obj1bar (引用的箭头函数)的 this 也会绑定到 obj1 ,箭头函数的绑定无法被修改( new 也不行)。这其实和ES6之前代码中的 self=this 机制一样。

对象

  • JavaScript六种主要类型:
    简单基本类型(string,number,boolean,null,undefined),object
  • 内置对象:
    String,Number, Boolean, Object, Function, Array, Date, RegExp, Error

检测:Object.prototype.toString.call() //[Object xxx]

typeof(null)返回是”object”原理:不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判断为object类型,null的二进制表示全是0,自然前三位也是0,所以执行typeof时会返回”object”

  • 对象就是键 / 值对的集合。可以通过.propName或者[“PropName”]语法来获取属性值。访问属性时候,引擎会调用内部的默认[[Get]]操作(在设置属性值时是[[Put]]),[[Get]]操作会检查对象是否包含这个属性,如果没找到的话还会查找[[Prototype]]链。

  • 属性的特性可以通过属性描述符来控制。比如writableconfigurable。此外,可以使用Object.preventExtensions(..), Object.seal(..)Object.freeze(..)来设置对象(以其属性)的不可变级别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myObject = {a: 2}
Object.getOwnPropertyDescriptor(myObject, "a");
// {
// value: 2,
// writable: true, // 可写
// enumerable: true, // 可枚举
// configurable: true // 可配置
// }

/* 可通过defineProperty来做修改 */
var myObject = {};
Object.defineProperty(myObject, "a", {
value: 2,
writable: true, // 可写
enumerable: true, // 可枚举
configurable: false // 单向操作无法撤消
})
  • 关于 getter 和 setter
    getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏函数,会在设置属性值的时候调用。当你给一个属性定于 getter 和 setter 或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript 会忽略它们的 value 和 writable 属性,取而代之的是关心 set 和 get(还有 configurable 和 enumerable)特性。通常来说getter和setter是成对出现的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var myObject = {
    get a() {
    return this._a_;
    },
    set a(val) {
    this._a_ = val * 2;
    }
    }
    myObject.a = 2;
    console.log(myObject.a); // 4
  • 关于遍历

    • 元素的属性可以是可枚举的也可以是不可枚举的,这决定了他们是否会出现在for...in循环中 。
    • 使用ES6的for...of语法来遍历数据结构(数组,对象等等)中的值,for...of会寻找内置或者自定义的@@iterator对象并调用它的next()方法来遍历数据值。

原型

继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
}

function Bar(name, label) {
Foo.call(this, name);
this.label = label;
}

// 创建一个新的 Bar.prototype 对象并关联到 Foo.prototype
Bar.prototype = Object.create(Foo.prototype);

// 注意:现在没有Bar.prototype.constructor了
// 如果需要使用这个属性可能需要手动修复一下

Bar.prototype.myLabel = function() {
return this.label;
}

var a = new Bar("a", "obj a");

a.myName(); // "a"
a.myLabel(); // "obj a"

调用 Object.create(..)会凭空创造一个“新”对象并把新对象内部的[[prototype]]关联到你指定的对象(本例中是 Foo.prototype)
换句话说,这条语句的意思就是“创建一个新的 Bar.prototype 对象并把它关联到 Foo.prototype”。

把 Bar.prototype 关联到 Foo.prototype 的方法:

1
2
3
4
5
// ES6 之前需要抛弃默认的 Bar.prototype
Bar.prototype = Object.create(Foo.prototype);

//ES6 开始可以直接修改现有的 Bar.prototype
Object.setPrototypeOf(Bar.prototype, Foo.prototype);

  • 如果要访问对象中并不存在的一个属性,[[Get]]操作就会查找对象内部[[prototype]]关联的对象。这个关联对象实际上定义了一条“原型链”,在查找属性的时候会对它进行遍历
  • 所有普通对象都有内置的 Object.prototype,指向原型链的顶端(比如全局作用域),如果原型链中找不到指定的属性就会停止。toStringvalueOf和其他一些通用功能都存在于Object.prototype对象上,因此语言中所有的对象都可以使用他们。