《你不知道的JS(上)》读书笔记2

第1章 关于this

  • 为什么要使用this
    • 显示传递上下文对象会让代码变得越来越混乱,this提供了一种更优雅的方式隐式地“传递”一个对象引用。
  • this并不指向函数本身
  • this在任何情况下都不指向函数的词法作用域
  • this是在函数被调用时发生的绑定,是函数运行时绑定,不是编写时绑定。

第2章 this全面解析

  • 调用栈:为了达到当前执行位置所调用的所有函数
  • 调用位置:函数在代码中被调用的位置(而不是声明的位置)

绑定规则

默认绑定

  • 不带任何修饰的函数引用进行调用的函数,只能使用默认绑定,无法应用其他规则
  • 只有运行在非严格模式下,this的默认绑定才能绑定到全局对象

隐式绑定

  • 函数引用拥有上下文对象时,通常是被某个对象拥有或者包含,隐式绑定规则会把函数调用中的this绑定到这个上下文对象
  • 对象属性引用链中只有最顶层或者说最后一层会影响调用位置。
  • 函数别名、参数传递、调用回调函数的函数都可能会丢失this的隐式绑定

显式绑定

  • call(...)和apply(...)方法指定this的绑定对象
  • 硬绑定:创建一个函数a,在其内部将另一个函数b的this绑定,之后无论如何调用a,都会显式强制绑定。
  • 硬绑定的典型应用场景是创建一个包裹函数,传入所有的参数并返回接收到的所有值。

new绑定

  • 构造函数只是被new操作符调用的普通函数而已
  • 实际上并不存在所谓的构造函数,只是对于函数的构造调用
  • new调用函数时,执行的操作:
    • 创建(或者说构造)一个全新的对象
    • 这个新对象会被执行[[原型]]链接
    • 这个新对象会绑定到函数调用的this
    • 如果函数没有返回其他对象,那么new的函数调用会自动返回这个新对象

优先级

  • new 绑定>显示绑定>隐式绑定>默认绑定
  • 之所以要在new中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new进行初始化的时候就可以值传入其余的参数。
  • 部分应用(柯里化的一种):bind(..)的功能之一是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下层的函数。

绑定例外

  • DMZ对象——它是一个空的非委托的对象
  • 创建一个空对象最简单的方法是Object.create(null)。{}会创建Object.prototype,但是Object.create不会创建,所以比{}更空。
  • 间接引用:创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。

软绑定

  • 硬绑定会大大降低函数的灵活性,使用应绑定之后就无法使用隐式绑定或者显式绑定来修改this。

  • 软绑定,会对指定的函数进行封装,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    if(!Function.prototype.softBind){
    Function.prototype.softBind=function(obj){
    var fn=this;
    var curried=[].slice.call(arguments,1);
    var bound=function(){
    return fn.apply(
    (!this||this===(window||global))?obj:this.curried.concat.apply(curried,arguments)
    );
    }
    bound.prototype=Object.create(fn.prototype);
    return bound;
    }
    }

This词法

  • 箭头函数不适用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this,且绑定后无法被修改。

第3章 对象

  • 在必要时语言会自动把字符串字面量转换成一个String对象
  • object.a:属性访问
  • object[“a”]:键访问
  • 在对象中,属性名永远都是字符串
  • 数组也可以添加命名属性,数组的length值并不会发生变化
  • 访问对象属性的时候,返回值为undefined。这个值可能原属性存储的undefined,也可能是因为属性不存在所以返回undefined。解决方法:
    • in操作符检查属性是否在对象及其[[prototype]]原型链中。in操作符检查容器内某个属性名是否存在。
    • hasOwnProperty(..)只会检查属性是否在对象中,不会检查[[prototype]]原型链中。
  • Object.keys(…)会返回一个数组,包含所有可枚举属性
  • Object.getOwnPropertyNames(…)会返回一个数组,包含所有属性,无论它们是否可枚举。

遍历

  • for…in循环可以用来遍历对象的可枚举属性列表(包括[[Prototype]]链)
  • for…of ES6新增遍历数组的语法,直接遍历值而不是数组下标。
  • Symbol.iterator获取对象的@@iterator内部属性。
  • @@iterator本身不是一个迭代器对象,而是一个返回迭代器对象的函数。
  • 数组有内置的@@iterator,但是普通对象没有。可以通过下面的代码给想遍历的对象定义@@iterator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Object.defineProperty(myObject,Symbol.iterator,{
enumerable:false,
writable:false,
configurable:true,
value:function(){
var o=this;
var idx=0;
var ks=Object.keys(o);
return {
next:function(){
return{
value:o[ks[idx++]],
done:(idx>ks.length)
}
}
}
}
})