This与对象原型
第一章:this是什么?
-任何足够先进的技术都跟魔法没有区别。---ArthurC.Clarke
-JavaScript的this机制实际上没有那么先进
-this代词/关键词标识符
为什么要用this
-函数对多个环境对象进行复用,而不是针对每个对象定义函数的分离版本
-this机制提供了更优雅的方式来隐含地“传递”一个对象引用
-导致更加干净的API设计和更容易的复用
困惑
-它自己:this指向函数自己
1.函数内部引用自己(递归、第一次被调用时会解除自己绑定的事件处理器)
2.将函数作为一个对象,在方法调用之间储存状态(属性中的值)
3.对this的含义和其工作方式上的理解(以上理解不对)
-它的作用域
1.this不会以任何方式指向函数的词法作用域
2.JavaScript作用域是引擎的内部实现
3.不能使用this引用在词法作用域中查找东西
-this既不是函数自身的引用,也不是函数词法作用域的引用
什么是this?
-this不是编写时绑定而是运行时绑定,依赖于函数调用的上下文条件
-this绑定与函数声明位置无关,与函数被调用方式紧密相连
-一个函数被调用时,会建立一个称为执行环境的活动记录,这个记录包含函数是从何处(调用栈---call-stack)被调用的,函数是如何被调用的,被传递了什么参数等信息
-这个记录的属性之一,就是在函数执行期间将被使用的this引用
-寻找函数的调用点(call-site)来判定它的执行如何绑定this
第二章:this豁然开朗!
-this是一个完全根据调用点(函数是如何被调用的)而为每次函数调用建立的绑定
调用点(Call-Site)
-调用点:函数在代码中被调用的位置
-某些特定的编码模式会使真正的调用点变得不那么明确
-调用栈(call-stack):使我们到达当前执行位置而被调用的所有方法的堆栈
-调用点就位于当前执行中的函数之前的调用
-在分析代码来寻找(从调用栈中)真正的调用点要小心,因为它是影响this绑定的唯一因素
仅仅是规则
-调用点如何决定函数执行期间this指向哪里
-考察调用点并判定4中规则中哪一种适用
默认绑定(Default Binding)
-独立函数调用:没有其他规则适用时的默认规则
functionfoo() {
console.log( this.a );
}
var a =2;
foo(); // 2
-foo()是被一个直白的毫无修饰的函数引用调用的,没有其他规则
-this默认绑定,指向了全局对象a
functionfoo() {
"use strict";
console.log( this.a );
}
var a =2;
foo(); // TypeError: `this` is `undefined`
functionfoo() {
console.log( this.a );
}
var a =2;
(function(){
"use strict";//与调用点的strict mode无关
foo(); // 2
})();
-foo()内容在strict mode下执行,对于默认绑定全局对象不合法
隐含绑定(Implicit Binding)
-调用点是否有一个环境对象(contextobject),也称为拥有者(owning)或容器(containing)
functionfoo() {
console.log( this.a );
}
var obj = {
a:2,
foo: foo
};
obj.foo(); // 2
-foo()被声明后作为引用属性添加到obj,并不被obj所真正“拥有”或“包含”
-调用点使用obj环境来引用函数,可以说在被调用时间点上拥有这个函数引用
-foo()在调用点指向obj对象引用(方法引用存在环境对象)
-隐含绑定规则:这个函数调用的this绑定这个对象this.a—obj.a
-只有对象属性引用链最后一层影响调用点
functionfoo() {
console.log( this.a );
}
var obj2 = {
a:42,
foo: foo
};
var obj1 = {
a:2,
obj2: obj2
};
obj1.obj2.foo(); // 42
-隐含丢失(ImplicitlyLost)
functionfoo() {
console.log( this.a );
}
var obj = {
a:2,
foo: foo
};
var bar = obj.foo; // 函数引用!
var a ="oops, global"; // `a` 也是一个全局对象的属性
bar(); // "oops, global"
-bar似乎是obj.foo的引用,实际上只是另一个foo本身的引用,起作用调用点是bar(),适用默认绑定
functionfoo() {
console.log( this.a );
}
functiondoFoo(fn) {
// `fn` 只不过 `foo` 的另一个引用
fn(); // <-- 调用点!
}
var obj = {
a:2,
foo: foo
};
var a ="oops, global"; // `a` 也是一个全局对象的属性
doFoo( obj.foo ); // "oops, global"
-回调函数也是如此,我们在传递一个函数,它是一个隐含的引用赋值
functionfoo() {
console.log( this.a );
}
var obj = {
a:2,
foo: foo
};
var a ="oops, global"; // `a` 也是一个全局对象的属性
setTimeout( obj.foo, 100 ); // "oops, global"
-语言内建也是如此,假想为JavaScript环境内建的实现
functionsetTimeout(fn,delay) {
// (通过某种方法)等待 `delay` 毫秒
fn(); // <-- 调用点!
}
-回调函数丢掉this绑定十分常见(通过固定this解决问题)
明确绑定(Explicit Binding)
-强制一个函数调用使用某个特定对象作为this绑定
-函数工具call()、apply()通过他们的[Prototype]
1.接收的第一个参数都是一个用于this的对象
2.直接指明想让this是什么
functionfoo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); // 2,强制函数的this指向obj
-如果是简单基本类型值作为this绑定,会包装在对应对象类型中(封箱boxing)
-从this绑定的角度讲call()和apply()完全一样
-硬绑定(Hard Binding):为所有传入的参数和传出的返回值创建一个通道
-硬绑定已作为ES5的内建工具提供:Function.prototype.bind
functionfoo(something) {
console.log( this.a, something );
returnthis.a + something;
}
var obj = {
a:2
};
var bar = foo.bind( obj );
var b =bar( 3 ); // 2 3
console.log( b ); // 5
-bind()返回一个硬编码的新函数,使用你指定的this环境来调用原本的函数
-ES6中bind()生成的硬绑定函数有一个.name的属性,源自原始的目标函数,显示在调用栈轨迹的函数调用名称中
-API调用的“环境”
-内建函数提供的可选参数也可以起到bind()的作用
-内部也是通过call()或apply()
New绑定(new Binding)
-实际上JavaScript机制和new在JS中的用法暗示面向类的功能没有任何联系
-在JS中,构造器仅仅是一个函数,在被使用new来调用时改变了行为
-引用ES5.1的语言规范
15.7.2 Number 构造器
当 Number 作为 new 表达式的一部分被调用时,它是一个构造器:它初始化这个新创建的对象。
-实际上不存在“构造器函数”,只有函数的构造器调用(constructor call)
-函数前加new完成构造器调用时,会自动完成:
1.一个全新的对象会凭空创建(被构建)
2.这个新构建的对象会被接入原型链([[Prototype]]-linked)
3.这个新构建的对象被设置为函数调用的this绑定
4.除非函数返回一个它自己的其他对象,否则这个被new调用的函数将自动返回这个新构建的对象
一切皆有顺序
-new绑定>明确绑定>隐含绑定>默认绑定
-bind()的一个能力:任何在第一个this绑定参数之后被传入的参数。默认地作为当前函数的标准参数(技术上这称为”局部应用(partialapplication)”,是一种”柯里化(currying)”)
-判定this
1.函数是通过new被调用的嘛
2.函数是通过call或apply被调用或隐藏在bind硬绑定之中嘛
3.函数是通过环境对象(拥有着或容器对象)被调用的嘛
4.否则使用默认地this(strict mode为undefined否则是global对象)
绑定的特例
-被忽略的this
1.硬绑定传递null或undefined作为绑定参数
2.值会被忽略,适用默认绑定规则适用
-更安全的this
1.为this传递一个特殊创建好的对象
2.创建完全为空没有委托的对象
3.建立一个”DMZ (非军事区)”对象:符号可以自己创建比如ø
functionfoo(a,b) {
console.log( "a:"+ a +", b:"+ b );
}
// 我们的 DMZ 空对象
var ø =Object.create( null );
// 将数组散开作为参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 用 `bind(..)` 进行 currying
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
-间接
-通过赋值产生的简介引用
functionfoo() {
console.log( this.a );
}
var a =2;
var o = { a:3, foo: foo };
var p = { a:4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
-赋值表达式p.foo=o.foo结果值是一个刚好指向底层函数对象的引用,调用点为foo()
软化绑定(Softening Binding)
-硬绑定极大地降低了函数的灵活性,阻止我们手动使用隐含绑定或后续的明确绑定附带this
-构建一个所谓软绑定工具模拟我们期望的行为
if (!Function.prototype.softBind) {
Function.prototype.softBind=function(obj) {
var fn =this,
curried = [].slice.call( arguments, 1 ),
bound=function bound() {
return fn.apply(
(!this||
(typeof window!=="undefined"&&
this===window) ||
(typeof global!=="undefined"&&
this===global)
)? obj :this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype=Object.create( fn.prototype );
return bound;
};
}
-softBind()函数在调用时检查this,是global或undefined使用预先指定的默认值(obj),否则保持this不变
functionfoo() {
console.log("name:"+this.name);
}
var obj = { name:"obj" },
obj2 = { name:"obj2" },
obj3 = { name:"obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 ); // name:obj <---- 退回到软绑定
词法this
-ES6引入了一种不适用这些规则特殊的函数:箭头函数(arrow function)
functionfoo() {
// 返回一个箭头函数
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()被this绑定到obj1就不会被覆盖
-常用于回调,如事件处理器或计时器
functionfoo() {
setTimeout(() => {
// 这里的 `this` 是词法上从 `foo()` 采用
console.log( this.a );
},100);
}
var obj = {
a:2
};
foo.call( obj ); // 2
选择
-仅使用词法作用域并忘掉虚伪的this风格代码
-完全接受this风格机制,包括在必要的时候使用bind(),并尝试避开self=this和箭头函数的“词法this”技巧
-词法和this不要混合使用
复习
为执行中的函数判定 this 绑定需要找到这个函数的直接调用点。找到之后,四种规则将会以这种优先顺序施用于调用点:
1.通过 new 调用?使用新构建的对象。
2.通过 call 或 apply(或 bind)调用?使用指定的对象。
3.通过持有调用的环境对象调用?使用那个环境对象。
4.默认:strict mode 下是 undefined,否则就是全局对象。
小心偶然或不经意的 默认绑定 规则调用。如果你想“安全”地忽略 this 绑定,一个像 ø = Object.create(null) 这样的“DMZ”对象是一个很好的占位值,以保护 global 对象不受意外的副作用影响。
与这四种绑定规则不同,ES6 的箭头方法使用词法作用域来决定 this 绑定,这意味着它们采用封闭他们的函数调用作为 this 绑定(无论它是什么)。它们实质上是 ES6 之前的 self = this 代码的语法替代品。