为什么要用这个
要回答这个问题,我们先看看不使用这个会出现什么问题。 试想一下,如果不使用这个,下面的代码应该怎么写:
function speak(){
var name = this.name
console.log(`Hello I am ${name}`)
}
var me = {
name: 'a',
speak: speak
}
var you = {
name: 'b',
speak: speak
}
me.speak() //Hello I am a
you.speak() //Hello I am b
复制代码
这可以在同一执行环境中使用不同的上下文对象。 它实际上提供了一种更优雅的方式来隐式“传递”对象引用,因此可以使 API 设计更加简洁,易于重用。
这是谁
这既不是它本身,也不是当前功能的范围。 我们可以用代码来测试一下。
function fn(){
console.log(this.name)
}
fn.name = 'xxx'
fn() //undefined
复制代码
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo(); //undefined
复制代码
那么这是谁? 不一定,this是运行时绑定的,所以要看函数的执行上下文。
调用函数时,会创建活动记录(执行上下文)。 这条记录会包含函数调用位置(调用栈)、函数的调用方式、传入的参数等信息,这也是这里的一个属性。
如何判断这个点
判断this的重点就是判断函数的执行上下文,即“谁调用了它”。 有几种判断方法:
独立函数调用
function foo(){
console.log(this.a)
}
var a = 2
foo() // 2
复制代码
这个直接调用this的方法指向全局对象,如果在浏览器中,则指向
对象上下文(隐式绑定)
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
复制代码
尽管 foo 是在全局范围内定义的,但在调用时会被 obj 上下文引用。 可以理解为在调用foo的那一刻是属于obj对象的。 所以这指向 obj。
这里有两个问题:
在链式调用的情况下,只有最后一层会影响调用位置,例如:
obj1.obj2.obj3.fn() //这里的fn中的this指向obj3
复制代码
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "xxxxx"
bar(); // xxxxx
复制代码
这里的bar其实是指obj.foo的地址,指向一个函数,也就是说bar的调用其实是符合“函数独立调用”规则的。 所以这不是 obj。
回调函数实际上是隐式丢失的
稍微改变一下上面的代码:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "xxxxx"
setTimeout( obj.foo ,100); // xxxxx
复制代码
我们看到回调函数虽然被obj引用了,但是this已经不是obj了。 其实内置的()函数的实现类似于下面的伪代码:
function setTimeout(fn, delay){
//等待delay毫秒
fn()
}
复制代码
实际上,这段代码将这个操作隐藏为fn=obj.foo,类似于上例中的bar=obj.foo。
显式绑定
显式绑定与隐式绑定相反,隐式绑定是指通过call、apply、bind显式改变this的方向。
这三个方法的第一个参数就是this要指向的对象。
请注意,如果您将值(字符串、布尔值、数字)类型传递给第一个参数,该值将被转换为对象形式(调用 new(..)、new(..)、new(..) )。
这三个方法中的bind方法比较特殊,它可以延迟方法的执行,让我们写出更灵活的代码。 它的原理也很容易模拟:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
var obj = {
a:2
};
var bar = bind( foo, obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
复制代码
注意:如果第一个参数传入null or,这个值会被忽略,相当于符合独立函数调用的规则
新绑定
Js中的new不同于传统的面向类的语言机制,Js中的“构造函数”其实和普通的函数没什么区别。
实际上,当我们使用new来调用一个函数时,会发生以下事情:
其中,第三步绑定了this,所以“构造函数”和原型中的this总是指向新的实例。
优先事项
以上四个判断规则的权重是递增的。 判断顺序是:
严格模式的区别
上面说的都是在非严格模式下建立的,在严格模式下这一点是不一样的。
箭头函数中的this
箭头函数不是通过关键字定义的,也没有使用上面的this规则,而是“继承”了外作用域中的this指针。
其实,虽然没有箭头函数,但是我们经常做一些和箭头函数效果一样的事情,例如:
function foo() {
var self = this;
setTimeout( function(){
console.log( self );
}, 100 );
}
复制代码
有了这个
es6 中的 or 函数会将 this 绑定到设置或获取属性的对象上。
function sum() {
return this.a + this.b + this.c;
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
}
};
Object.defineProperty(o, 'sum', { get: sum, enumerable: true, configurable: true} );
console.log(o.average, o.sum); // logs 2, 6