this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
寻找调用位置就是寻找“函数被调用的位置”,但是做起来并没有这么简单,因为某些编程模式可能会隐藏真正的调用位置。最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
3.因此,如果你想要分析 this 的绑定,使用开发者工具得到调用栈,然后找到栈中第二个元素,这就是真正的调用位置。
4.绑定规则 独立函数调用:是无法应用其他规则时的默认规则。this指向全局对象(非严格模式下);如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined。 隐式绑定:需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。对象属性引用链中只有最顶层或者说最后一层会影响调用位置。 显式绑定:call(…) 和 apply(…) 方法,它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我们称之为显式绑定。如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(…)、new Boolean(…) 或者new Number(…))。这通常被称为“装箱”。硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype.bind。 new绑定:包括内置对象函数在内的所有函数都可以用 new 来调用,这种函数调用被称为构造函数调用。使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。1. 创建(或者说构造)一个全新的对象。2. 这个新对象会被执行 [[ 原型 ]] 连接。3. 这个新对象会绑定到函数调用的 this。4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
(1). 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。 var bar = new foo() (2). 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。 var bar = foo.call(obj2) (3). 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。 var bar = obj1.foo() (4). 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到 全局对象。 var bar = foo()6.绑定例外 如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
7.另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。间接引用最容易在赋值时发生。
8.软绑定 如果可以给默认绑定指定一个全局对象和 undefined 以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。
9.箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。箭头函数的绑定无法被修改。箭头函数可以像 bind(…) 一样确保函数的 this 被绑定到指定对象,此外,其重要性还体现在它用更常见的词法作用域取代了传统的 this 机制。
10.要么只使用词法作用域并完全抛弃错误 this 风格的代码; 要么完全采用 this 风格,在必要时使用 bind(…),尽量避免使用 self = this 和箭头函数。
11.对象可以通过两种形式定义:声明(文字)形式和构造形式。
在 JavaScript 中一共有六种主要类型(术语是“语言类型”):string number boolean null undefined object13.注意,简单基本类型(string、boolean、number、null 和 undefined)本身并不是对象。null 有时会被当作一种对象类型,但是这其实只是语言本身的一个 bug,即对 null 执行typeof null 时会返回字符串 “object”。1 实际上,null 本身是基本类型。JavaScript 中有许多特殊的对象子类型,我们可以称之为复杂基本类型。
14.JavaScript 中还有一些对象子类型,通常被称为内置对象。String Number Boolean Object Function Array Date RegExp Error
15.对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的,我们称之为属性。在对象中,属性名永远都是字符串。如果你使用 string(字面量)以外的其他值作为属性名,那它首先会被转换为一个字符串。
16.对于 JSON 安全(也就是说可以被序列化为一个 JSON 字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法: var newObj = JSON.parse( JSON.stringify( someObj ) );
17.相比深复制,浅复制非常易懂并且问题要少得多,所以 ES6 定义了 Object.assign(…) 方法来实现浅复制。Object.assign(…) 方法的第一个参数是目标对象,之后还可以跟一个或多个源对象。它会遍历一个或多个源对象的所有可枚举(enumerable,参见下面的代码)的自有键(owned key,很快会介绍)并把它们复制(使用 = 操作符赋值)到目标对象,最后返回目标对象,就像这样: var newObj = Object.assign( {}, myObject ); newObj.a; // 2 newObj.b === anotherObject; // true newObj.c === anotherArray; // true newObj.d === anotherFunction; // true
18.属性描述符 Object.getOwnPropertyDescriptor( myObject, “a” ); 这个普通的对象属性对应的属性描述符(也被称为“数据描述符”,因为它只保存一个数据值)可不仅仅只是一个 2。它还包含另外三个特性:writable(可写)、enumerable(可枚举)和 configurable(可配置)。writable 决定是否可以修改属性的值。只要属性是可配置的,就可以使用 defineProperty(…) 方法来修改属性描述符。把 configurable 修改成false 是单向操作,无法撤销!enumerable(可枚举)从名字就可以看出,这个描述符控制的是属性是否会出现在对象的属性枚举中,比如说for…in 循环。如果把 enumerable 设置成 false,这个属性就不会出现在枚举中,虽然仍然可以正常访问它。相对地,设置成 true 就会让它出现在枚举中。
19.不变性 对象常量:结合 writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改、重定义或者删除)。 禁止扩展:如 果 你 想 禁 止 一 个 对 象 添 加 新 属 性 并 且 保 留 已 有 属 性, 可 以 使 用 Object.prevent Extensions(…)。 密封:Object.seal(…) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(…) 并把所有现有属性标记为 configurable:false。所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。 冻结:Object.freeze(…) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal(…) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们的值。这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改(不过就像我们之前说过的,这个对象引用的其他对象是不受影响的)。
20.对象默认的 [[Put]] 和 [[Get]] 操作分别可以控制属性值的设置和获取。
21.在数组上应用 for…in 循环有时会产生出人意料的结果,因为这种枚举不仅会包含所有数值索引,还会包含所有可枚举属性。最好只在对象上应用for…in 循环,如果要遍历数组就使用传统的 for 循环来遍历数值索引。
22.in 和 hasOwnProperty(…) 的区别在于是否查找 [[Prototype]] 链,然而,Object.keys(…)和 Object.getOwnPropertyNames(…) 都只会查找对象直接包含的属性。
23.遍历 forEach(…) 会遍历数组中的所有值并忽略回调函数的返回值。 every(…) 会一直运行直到回调函数返回 false(或者“假”值)。 some(…) 会一直运行直到回调函数返回 true(或者“真”值)。 every(…) 和 some(…) 中特殊的返回值和普通 for 循环中的 break 语句类似,它们会提前终止遍历。
24.直接遍历值而不是数组下标(或者对象属性)呢?幸好,ES6 增加了一种用来遍历数组的 for…of 循环语法(如果对象本身定义了迭代器的话也可以遍历对象)。for…of 循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next() 方法来遍历所有返回值。for…of 循环每次调用 myObject 迭代器对象的 next() 方法时,内部的指针都会向前移动并返回对象属性列表的下一个值。
25.面向类的设计模式:实例化(instantiation)、继承(inheritance)和(相对)多态(polymorphism)。
26.类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数。
27.类构造函数属于类,而且通常和类同名。此外,构造函数大多需要用 new 来调,这样语言引擎才知道你想要构造一个新的类实例。
28.传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类中。多态(在继承链的不同层次名称相同但是功能不同的函数)看起来似乎是从子类引用父类,但是本质上引用的其实是复制的结果。
29.所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。由于所有的“普通”(内置,不是特定主机的扩展)对象都“源于”(或者说把 [[Prototype]] 链的顶端设置为)这个 Object.prototype 对象,所以它包含 JavaScript 中许多通用的功能。
30.JavaScript 才是真正应该被称为“面向对象”的语言,因为它是少有的可以不通过类,直接创建对象的语言。
31.原型继承
图中由下到上的箭头表明这是委托关联,不是复制操作。
32.继承意味着复制操作,JavaScript(默认)并不会复制对象属性。相反,JavaScript 会在两个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数。委托这个术语可以更加准确地描述 JavaScript 中对象的关联机制。
33.检查一个实例(JavaScript 中的对象)的继承祖先(JavaScript 中的委托关联)通常被称为内省(或者反射)。
34.[[Prototype]] 机制就是指对象中的一个内部链接引用另一个对象。如果在第一个对象上没有找到需要的属性或者方法引用,引擎就会继续在 [[Prototype]]关联的对象上进行查找。同理,如果在后者中也没有找到需要的引用就会继续查找它的[[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。
35.委托行为意味着某些对象(XYZ)在找不到属性或者方法引用时会把这个请求委托给另一个对象(Task)。
class