你应该了解的this
- this机制:this在运行时进行绑定,并不是在编写是进行绑定,它的上下文取决于函数调用时的各种条件。this的绑定和和函数的声明位置没有任何关系,只取决于函数的调用方式
- 当一个函数被调用的时候,会创建一个活动记录(执行上下文),这个记录会包含函数在哪里被调用、函数的调用方式、传入的参数等信息,this就是这个记录的一个属性,会在函数的执行中被找到。
四种绑定规则
默认绑定
我们先来看一段代码:
var name = 'global'
function person(){
var name = 'inner'
console.log(this.name)
}
person() // global
这里我们执行person()、输出结果是在全局上的name的值,那么为什么呢,因为这里的this指向全局对象,执行的是默认绑定。
那么我们怎么知道何时使用默认绑定呢,这里函数 person()在调用的时候没有应用任何的修饰,直接使用的是 person(),因此就只能使用默认绑定,那么使用修饰调用是什么样的、之后会进行详细说明。
但是也不是默认绑定的所以情况都是指向全局对象,当你使用严格模式编写代码时,
this指向undefined。
'use strict'
var name = 'global'
function person(){
var name = 'inner'
console.log(this.name)
}
person() //Cannot read property 'name' of undefined
隐式绑定
先上代码:
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj = {
name:'objName',
person: person
}
obj.person() // objName
这里我们遇见第一种带有修饰的调用obj.person()
而非默认绑定中的直接调用person()
就像我们最开始说的那种,this不是在声明时进行绑定的,而是在调用时进行绑定的。当函数的引用有上下文对象时,隐式绑定会把函数调用中的this绑定到这个上下文对象,这个例子中this绑定到obj对象上,this.name就相当于obj.name,我们最终就会得到objName
。
下面这个例子中,说明只有在引用链的最后一层起作用。
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name:'obj1Name',
person: person
}
var obj2 = {
name:'obj2Name',
obj1: obj1
}
obj2.obj1.person() // obj1Name
常见面试题:隐式丢失问题
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name:'obj1Name',
person: person
}
var obj2 = obj1.person
obj2()// global
这里声明一个变量 obj2 = obj1.person
之后调用 obj(2)
虽然obj2
是对obj1.person
的引用,但是在下文的调用时使用的是没有任何修饰的调用,即obj2
,这将应用我们上面提到的默认绑定,即非严格模式下this绑定到全局对象,
严格模式下绑定到undefined,输出golbal也符合我们的预期。
再看下面的代码:
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
function student(fn) {
// fn=obj1.person
fn()
}
var obj1 = {
name: 'obj1Name',
person: person
}
student(obj1.person) //global
这里执行student()是相当于 fn=obj1.person fn是对obj1.person的引用,和上个例子中同样,发生隐式绑定丢失,对this使用默认绑定。
setTimeout:
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name: 'obj1Name',
person: person
}
setTimeout(obj1.person, 1000); // node 环境下 undefined
我们来看下 mdn对this指向错误的解释:
由setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致,这些代码中包含的 this 关键字在非严格模式会指向 window (或全局)对象,严格模式下为 undefined,这和所期望的this的值是不一样的
mdn上polyfill实现setTimeout的部分代码
window.setTimeout = function(vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */ ) {
var aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeST__(vCallback instanceof Function ? function() {
vCallback.apply(null, aArgs);
} : vCallback, nDelay);
}
我们简化一下:去掉args
window.setTimeout = function(vCallback, nDelay ) {
return __nativeST__(vCallback instanceof Function ? vCallback() : vCallback, nDelay);
}
我们可以看出和上面一样,vCallback = obj1.person 同样是对obj1.person的引用。
显示绑定
call、apply
一般来说基本上所有的函数都是由Function创建,Function的原型链上有两个方法call,apply
mdn上关于call和apply的方法接收的参数:
call: fun.call(thisArg, arg1, arg2, ...)
apply:fun.apply(thisArg, [argsArray])
可以看出第一个参数是一样的都是this,不同点在于call接收的是参数列表,apply接收一个数组。
这种显示的更改this的指向方法,我们称之为显示绑定。
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name: 'obj1Name',
person: person
}
person.call(obj1) //obj1Name
这样我们虽然解决了this绑定的值,但是并没有解决之前提到的绑定丢失问题。
bind:
变种:我们封装一个函数,当每次调用函数时将this绑定到obj1上。
var name = 'global'
function person() {
var name = 'inner'
console.log(this.name)
}
var obj1 = {
name: 'obj1Name',
person: person
}
function bind(fn,obj,...args){
return function(){
fn.apply(obj,args)
}
}
var res = bind(person,obj1)
res() //obj1Name
同样,javascript 也为我们提供了这个方法:Function.protoype.bind()
mdn 上关于bind()的polyfill:
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 维护原型关系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
可以看出都是和我们写的bind()核心是一样的内部调用call、apply。
new 绑定
当new 一个函数的时候会执行一下的几步:
- 创建一个空对象;
- 将空对象的原型指向函数的property
- 调用apply、call 方法空对象的this指向函数
- 如果函数返回了一个“对象”,那么这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那么new出来的结果为步骤1创建的对象
之前写了一篇模拟实现 new的文章
new的模拟实现
示例:
function Person(name) {
this.name = name
}
var student = new Person('lili')
console.log(student.name) // lili
ES6 箭头函数
箭头函数本身没有this,而是根据外层(函数或者全局)作用域来决定this.
var name = 'outer'
function person() {
setTimeout(() => {
console.log(this.name)
}, 100)
}
var obj = {
name: 'lili',
person: person
}
obj.person() // lili