JavaScript中的this与它的三个函数

吐槽君 分类:javascript

this

this 机制是 JavaScript 特有的,它是为了解决在对象内部的方法中使用对象内部的属性的需求。

如果不使用 this 来实现对对象内部的属性 JavaScript 可以使用对象.属性访问到:

let people = {
	name: 'litangmm',
    logName: function(){
        console.log(people.name)
    }
}
 

这种方法,方法与对象强耦合。对象变量变化,方法必须接着改变。如果有一个匿名对象数组,那么对象位置改变,方法就得改。

let peoples = [
    {name:'litangmm',logName: function(){console.log(peoples[0].name)}},
    {name:'litangmm1',logName: function(){console.log(peoples[1].name)}},
    {name:'litangmm2',logName: function(){console.log(peoples[2].name)}},
]
 

于是,this 出现了。

this 是和执行上下文绑定的。this,是运行时确定的。

  1. 在全局环境下,this 指向全局的 window。
  2. 作为函数执行时,如果是作为对象的函数执行,则指向该对象本身,否则指向全局的window。
  3. 1 2都是非严格模式下的,全局模式下,this 拿不到全局 window 而是 undefined

还有 bind apply call new,它们和this紧密相关。

bind apply call 都是属于函数的原型方法,可以改变函数执行的 this 的指向。它们的原理都是:

作为函数执行时,如果是作为对象的函数执行,则指向该对象本身。

只不过在一些细节上实现不同。下面,通过手写它们的实现来理解它们的异同。

call 和 apply

执行效果

  1. 改变 thisarg
  2. 接收函数入参
  3. 执行函数

说明

  1. 对于 thisarg ,在非严格模式下,如果 thisarg 为 null 或 undefined ,thisarg 会为 全局 window。
  2. 对于函数入参,call 接收 的是多个参数值,apply接收的是一个参数列表。
// mycall.js
Function.prototype.mycall = function(thisarg,...args){
    let context = thisarg || window
    context.fn = this 
    const res = context.fn(...args)
    delete context.fn
    return res
}
function logNameAndAge(age, sex){
    console.log(this.name,age,sex)
}
logNameAndAge.mycall({name:"litangmm"},21,'man') // litangmm 21 man
 

context.fn = this 这里做了一件事,就是在 context 上定义了一个属性fn,赋值为 this 。**this是调用mycall的函数A。**这样,在下一行,函数A就作为 context 对象的对象函数被调用,函数A执行的this自然指向context了。

当然,这代码还是有优化空间的:

  1. 判断 this 是否为 函数。
  2. 如果传入的 thisarg 上有 fn 属性,那么该属性会被覆盖。

我们考虑这些情况,实现apply

// myapply.js
Function.prototype.myapply = function(thisarg,args){
    if(typeof this !== 'function'){
        throw new TypeError('error')
    }
    let context = thisarg || window
    const fn = Symbol()
    context.fn = this 
    const res = context.fn(...args)
    delete context.fn
    return res
}
function logNameAndAge(age, sex){
    console.log(this.name,age,sex)
}
logNameAndAge.myapply({name:"litangmm"},[21,'man'])  // litangmm 21 man
 

说明:

  1. 通过 typeof 判断调用的是否是一个函数。
  2. 通过 Symbol 来解决属性冲突。

bind

执行效果

  1. 改变 thisarg。
  2. 接收函数入参。
  3. 返回函数。

对于返回函数

  1. 可以接收入参。
  2. 可以作为构造函数使用。
  3. 作为构造函数使用时,bind 绑定的 this 会被忽略。

bind 实现的难点在于

  1. bind 执行时接收可以接收参数(类似call接收多个参数),返回的函数还可以接收参数。
  2. 判断返回函数是作为构造函数被使用。
// mybind.js
Function.prototype.mybind = function(thisarg,...args1){
    if(typeof this !== 'function'){
        throw new TypeError('error');
    }
	const that = this;
    let fBound = function(...args2){
        return that.apply(this instanceof fBound ? this: thisarg,args1.concat(args2));
    }
    let fNop = function(){};
    fNop.prototype = that.prototype;
    fBound.prototype = new fNop();  
    return fBound;
}
 

bind 本质是一个函数装饰器:

  1. 参数拼接。
  2. 如果是普通调用,使用bind的this。
  3. 当做返回函数作为构造函数,则使用返回函数的this。

说明

之所以可以通过this instanceof F 来判断是被当作构造函数被使用的,可以参考new 的原理:

  1. 创建一个空对象。

  2. 指定空对象的__proto__为构造函数的prototype

  3. 绑定该空对象到构造函数。

  4. 执行绑定后的构造函数。

  5. 如果构造函数有返回值,则返回返回值,否则返回对象。

!注意2步,因为返回的构造函数 是 fBound,而,我们希望得到的that上的原型,所以,有代码

fBound.prototype = that.prototype;	
 

这代码是存在问题的,因为fBound.prototype=== that.prototype === 原型对象地址,所以操作fBound.prototype 修改会改变 that.prototype。所以,我们可以使用原型链来实现fBoundthat 的连接。

let fNop = function(){};
fNop.prototype = that.prototype;
fBound.prototype = new fNop();   
 

这样,fBound.prototype 就是一个对象,对象的__proto__指向that.prototype。可以参考这篇文章。

!注意3、4步,构造函数执行的时候,空对象的__proto__已经指向了构造函数的prototype。所以,绑定后的构造函数的this 是可以在原型链上找到构造函数的。

一线大厂高级前端编写,前端初中阶面试题,帮助初学者应聘,需要联系微信:javadudu

回复

我来回复
  • 暂无回复内容