思考题
请输出下列代码
1.非严格模式-指向window
function foo() {
console.log(this.a);
}
var a = 2;
foo();
展开答案
输出:2分析: 在非严格模式下, 函数内的 this 是 指向window, var a 声明了一个全局变量
2.严格模式-指向undefined
'use strict';
function foo() {
console.log(this.a);
}
var a = 2;
foo();
展开答案
输出:Uncaught TypeError: Cannot read property 'a' of undefined3. 隐式绑定-指向对象
function foo() {
var a = 1;
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo();
展开答案
输出:24. 隐式绑定-指向对象
function foo() {
console.log(this.a);
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
b:foo,
obj2: obj2
};
obj1.obj2.foo();
obj1.b();
foo();
展开答案
依次输出:42依次输出:2
依次输出:undefined
5. 非严格模式-隐式绑定-隐式丢失
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo1: foo
};
obj.foo1()
var bar = obj.foo1; // 函数别名!
var a = "哈哈,你猜我在哪里呢?"; // a 是全局对象的属性
bar();
展开答案
依次输出:2依次输出:哈哈,你猜我在哪里呢?
6. 隐式绑定-隐式丢失
function foo() {
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
function setTimeout(fn,delay){
fn();
}
var a = "哈哈,猜猜我在哪里呢?";
setTimeout(obj.foo,100);
展开答案
输出:哈哈,你猜我在哪里呢?7. 误解-this指向函数的作用域
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo();
展开答案
输出:ReferenceError: a is not defined请看下面改写的代码:
var a; // 把变量 a 放在全局作用域下声明
function foo() {
a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); // 输出2
为什么要用this
- 因为
this提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计 得更加简洁并且易于复用。
this到底是什么
- 当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程中用到。
误解
this 指向函数的作用域
- 在某种情况下它 是正确的,但是在其他情况下它却是错误的。
this指向自身
- this 指向自身的观点是错误的
- 在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用
- 接受this,不要刻意回避它的写法
function foo(num) {
console.log( "foo: " + num );
// 记录 foo 被调用的次数
// 注意,在当前的调用方式下(参见下方代码),this 确实指向 foo
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
// 使用 call(..) 可以确保 this 指向函数对象 foo 本身
foo.call( foo, i );
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count ); // 4
调用位置
- this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用
- 调试技巧, 浏览器 => f12 => 在函数的代码处设置断点 => 运行代码 => 调试器在断点处暂停,显示调用栈列表(call stack)
绑定规则
默认绑定
- 非严格模式,使用默认绑定时, this 指向全局对象。
- 严格模式(strict mode),全局对象对象无法使用默认绑定, this 会绑定到
undefined。
隐式绑定
代码如下:
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
显式绑定
- call
- apply
new绑定
- new绑定发生了什么
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[ 原型 ]] 连接。
- 这个新对象会绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
优先级
- 显式绑定优先级更高
绑定例外
被忽略的this
间接引用
softBind
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有 curried 参数
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;
};
}
function foo() {
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 <---- 应用了softBind
this词法
- this 既不指向函数自身也不指向函数的词法作用域
注意事项
- 只使用词法作用域并完全抛弃错误 this 风格的代码;
- 完全采用 this 风格,在必要时使用 bind(..),尽量避免使用 self = this 和箭头函数
- 尽量不要在代码中混合使用 strict mode 和 non-strict mode。整个程序要么严格要么非严格。如果使用第三方库,其严格程度和你的代码有所不同,要注意兼容性细节。
总结
- 由 new 调用?绑定到新创建的对象
- 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到 undefined,否则绑定到全局
- ES6 中的箭头函数 并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样
参考资料
- 书籍-《你不知道的JavaScript(上卷)》