Open libin1991 opened 6 years ago
回答这个问题我们就先看看如果不使用this会出现什么问题。试想下面代码如果不使用this应该怎么写:
this
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
this可以在同一个执行环境中使用不同的上下文对象。它其实提供了一种更加优雅的方式来隐式“传递”一个对象引用,因此可以使API设计的更加简洁且易于复用。
this既不是自身也不是当前函数的作用域。我们可以通过代码来测试。
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(); // ReferenceError: a is not defined
那么this到底是谁呢?其实不一定,this是运行时绑定的,所以取决于函数的执行上下文。
当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息,this也是这里的一个属性。
确定this指向就是确定函数的执行上下文,也就是“谁调用的它”,有以下几种判断方式:
function foo(){ console.log(this.a) } var a = 2 foo() // 2
这种直接调用的方式this指向全局对象,如果是在浏览器就指向window
window
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
foo虽然被定义在全局作用域,但是调用的时候是通过obj上下文引用的,可以理解为在foo调用的那一刻它被obj对象拥有。所以this指向obj。 这里有两个问题:
foo
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的调用其实符合“独立函数调用”规则。所以它的this不是obj。
bar
obj.foo
回调函数其实就是隐式丢失
稍微改一下上面的代码:
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "xxxxx" setTimeout( obj.foo ,100); // xxxxx
我们看到,回调函数虽然是通过obj引用的,但是this也不是obj了。其实内置的setTimeout()函数实现和下面的伪代码类似:
function setTimeout(fn, delay){ //等待delay毫秒 fn() }
其实这段代码隐藏这一个操作就是fn=obj.foo,这和上面例子中的bar=obj.foo异曲同工。
fn=obj.foo
bar=obj.foo
显式绑定的说法是和隐式绑定相对的,指的是通过call、apply、bind显式地更改this指向。 这三个方法第一个参数是this要指向的对象。 注意,如果你给第一个参数传递一个值(字符串、布尔、数字)类型的话,这个值会被转换成对象形式(调用new String(..)、new Boolean(..)、new Number(..))。 这三个方法中的bind方法比较特殊,它可以延迟方法的执行,这可以让我们写出更加灵活的代码。它的原理也很容易模拟:
call
apply
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或者undefined,这个值会被忽略,相当于符合独立函数调用规则
Js中new与传统的面向类的语言机制不同,Js中的“构造函数”其实和普通函数没有任何区别。 其实当我们使用new来调用函数的时候,发生了下列事情:
new
其中,第三步绑定了this,所以“构造函数”和原型中的this永远指向new出来的实例。
以上四条判断规则的权重是递增的。判断的顺序为:
以上所说的都是在非严格模式下成立,严格模式下的this指向是有差异的。
undefined
箭头函数不是通过function关键字定义的,也不使用上面的this规则,而是“继承”外层作用域中的this指向。 其实一起虽然没有箭头函数,我们也经常做和箭头函数一样效果的事情,比如说:
function
function foo() { var self = this; setTimeout( function(){ console.log( self ); }, 100 ); }
es6中的getter或setter函数都会把this绑定到设置或获取属性的对象上。
getter
setter
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
MDN - this 《你不知道的javascript 上卷》
为什么要用this
回答这个问题我们就先看看如果不使用
this
会出现什么问题。试想下面代码如果不使用this
应该怎么写: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
this
可以在同一个执行环境中使用不同的上下文对象。它其实提供了一种更加优雅的方式来隐式“传递”一个对象引用,因此可以使API设计的更加简洁且易于复用。this到底是谁
this
既不是自身也不是当前函数的作用域。我们可以通过代码来测试。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(); // ReferenceError: a is not defined
那么
this
到底是谁呢?其实不一定,this
是运行时绑定的,所以取决于函数的执行上下文。如何判断this指向
确定
this
指向就是确定函数的执行上下文,也就是“谁调用的它”,有以下几种判断方式:独立函数调用
function foo(){ console.log(this.a) } var a = 2 foo() // 2
这种直接调用的方式
this
指向全局对象,如果是在浏览器就指向window
对象上下文(隐式绑定)
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
foo
虽然被定义在全局作用域,但是调用的时候是通过obj
上下文引用的,可以理解为在foo
调用的那一刻它被obj
对象拥有。所以this
指向obj
。这里有两个问题:
链式调用的情况下只有最后一层才会影响调用位置,例如:
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
的调用其实符合“独立函数调用”规则。所以它的this
不是obj
。稍微改一下上面的代码:
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "xxxxx" setTimeout( obj.foo ,100); // xxxxx
我们看到,回调函数虽然是通过
obj
引用的,但是this
也不是obj
了。其实内置的setTimeout()函数实现和下面的伪代码类似:function setTimeout(fn, delay){ //等待delay毫秒 fn() }
其实这段代码隐藏这一个操作就是
fn=obj.foo
,这和上面例子中的bar=obj.foo
异曲同工。显式绑定
显式绑定的说法是和隐式绑定相对的,指的是通过
call
、apply
、bind
显式地更改this
指向。这三个方法第一个参数是
this
要指向的对象。注意,如果你给第一个参数传递一个值(字符串、布尔、数字)类型的话,这个值会被转换成对象形式(调用new String(..)、new Boolean(..)、new Number(..))。
这三个方法中的
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
new绑定
Js中
new
与传统的面向类的语言机制不同,Js中的“构造函数”其实和普通函数没有任何区别。其实当我们使用
new
来调用函数的时候,发生了下列事情:其中,第三步绑定了
this
,所以“构造函数”和原型中的this
永远指向new
出来的实例。优先级
以上四条判断规则的权重是递增的。判断的顺序为:
new
出来的,this
指向实例this
指向绑定的第一个参数this
指向上下文对象this
指向全局对象严格模式下的差异
以上所说的都是在非严格模式下成立,严格模式下的
this
指向是有差异的。this
指向undefined
this
永远指向该对象箭头函数中的this
箭头函数不是通过
function
关键字定义的,也不使用上面的this
规则,而是“继承”外层作用域中的this
指向。其实一起虽然没有箭头函数,我们也经常做和箭头函数一样效果的事情,比如说:
function foo() { var self = this; setTimeout( function(){ console.log( self ); }, 100 ); }
getter与setter中的this
es6中的
getter
或setter
函数都会把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
参考资料
MDN - this
《你不知道的javascript 上卷》