1.调用位置
this完全取决于函数的调用位置。
调用位置是函数在代码中被调用的位置。
调用栈是为了到达当前执行位置所调用的所有函数,调用位置就在当前正在执行的函数的前一个调用中。
function baz() {
// 当前调用栈: baz
// 当前调用位置: 全局作用域
bar();
}
function bar() {
// 当前调用栈: baz => bar
// 当前调用位置: baz
foo();
}
function foo() {
// 当前调用栈: baz => bar => foo
// 当前调用位置: bar
console.log('foo');
}2.绑定规则
this的绑定对象需要找到调用位置,然后判断需要应用下面四条规则中的哪一条。
- 默认绑定
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 非严格模式: 2;严格模式: TypeError: this is undefined;- 隐式绑定
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。
function foo() {
console.log(this.a);
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42隐式丢失:隐式绑定的函数会丢失绑定对象,会应用默认绑定。
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
};
var bar = obj.foo; // 函数别名
var a = "我是全局变量a";
bar(); // 我是全局变量a
// bar是obj.foo的一个引用,实际上,它引用的是foo函数本身。
// 此时的bar()其实是一个不带任何修饰的函数调用,因此应用 默认绑定参数传递也是一种隐式赋值。eg: 回调函数。
function foo() {
console.log(this.a);
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
foo: foo
};
var a = "我是全局变量a";
doFoo(obj.foo); // 我是全局变量a- 显式绑定
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(a); // 2- new
- 创建一个全新的对象
- 这个新对象会被执行[[Prototype]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2四条规则优先级:new绑定>显式绑定>隐式绑定>默认绑定
3.绑定例外
- 被忽略的this
使用场景:
function foo(a,b) {
console.log(a + "+" + b);
}
// 把数组“展开”成参数
foo.apply(null,[2,3]); // 2 + 3
// 使用bind(...)进行柯里化
var bar = foo.bind(null,2);
bar(3); // 2 + 3总是使用null来忽略this绑定可能产生一些副作用,会将this绑定到全局对象。一种更安全的做法是传入一个特殊的对象,把this绑定到这个对象不会对你的程序产生任何副作用——创建一个DMZ对象(一个空的非委托对象)。
Object.create(null); // {}相似,但不会创建Object.prototype这个委托,比{}更‘空’- 间接引用
function foo() {
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- 软绑定
if(!Function.prototype.softBind) {
Function.prototype.softBind = function (obj) {
var fn = this;
var curried = [].slice.call(arguments,1); // 捕获所有curried
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;
}
}4.箭头函数
箭头函数根据外层作用域来决定this。和之前的self = this机制一样。
function foo() {
return a => {
console.log(this.a); // this继承自foo
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call(obj1);
bar.call(obj2); // 2;5.经典题解
- this指向
var num = 1;
var myObject = {
num: 2,
add: function() {
this.num = 3; // 隐式绑定 修改 myObject.num = 3
(function() {
console.log(this.num); // 默认绑定 输出 1
this.num = 4; // 默认绑定 修改 window.num = 4
})(); // 立即执行函数
console.log(this.num); // 隐式绑定 输出 3
},
sub: function() {
console.log(this.num) // 因为丢失了隐式绑定的myObject,所以使用默认绑定 输出 4
}
}
myObject.add(); // 1 3
console.log(myObject.num); // 3
console.log(num); // 4
var sub = myObject.sub;// 丢失了隐式绑定的myObject
sub(); // 4- 深入this指向
// 1、赋值语句是右执行的,此时会先执行右侧的对象
var obj = {
// 2、say 是立即执行函数
say: function() {
function _say() {
// 5、输出 window
console.log(this);
}
// 3、编译阶段 obj 赋值为 undefined
console.log(obj);
// 4、obj是 undefined,bind 本身是 call实现,
return _say.bind(obj);
}(),
};
obj.say();