js函数this理解手写applycallbind就够了

37次阅读

共计 2987 个字符,预计需要花费 8 分钟才能阅读完成。

一、this 是什么?

函数的内部属性,this 引用的是函数据以执行的 环境对象 。也就是说函数的 this 会指向 调用函数的执行环境

function a(){return  this}
console.log(a() === window) //true

函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

function a(){
    'use strict'
    return  this
}
console.log(a() === undefined) //true

二、this 指向哪里

this 做为函数的关键字,指向函数的调用对象。

大致的指向总结归纳为可以分为以下几种:

1. 函数做为全局环境调用时

var name = 'window';
function a(){return  this.name}
a() ==='window' //true

2. 函数做为对象方法调用时

var obj = {
    name:'obj',
    sayName:function(){return this.name}
}
console.log(obj.sayName() === 'obj') //true

// 稍微改动一下; 添加下面代码。var sayName = obj.sayName;
console.log(sayName() === 'obj') //false

// 此时,sayName 函数里的 this 指向了 window。这里需要明白一点,函数名仅仅是一个包含指针的变量,函数是复杂数据类型,所以函数名就只是一个指针,指向堆中的内存地址!所以 sayName 此时只是复制了指针地址,所以,上面代码改写成下面就很清晰了。var sayName = function(){return this.name}
var obj = {
    name:'obj',
    sayName:sayName
}
console.log(obj.sayName() === 'obj') //true
console.log(sayName() === 'obj') //false

3. 函数做为 dom 节点事件调用时

var container3 = document.getElementById('container3')
container3.onclick = function(){ // 指向节点本身
    console.log(this) //<div id="container3">container3</div>
}

4. 做为构造函数实力化方法时

function A(name){
    this.name = name;
    this.sayName = function(){console.log(this.name)// 指向实例对象
    }
}
var a = new A('aa');
a.sayName(); //aa

5. 箭头函数里的 this

var name = 'window'
var obj = {
    name:'obj',
    fn:function(){(function (){console.log(this.name)
       })()}    

}
obj.fn() //window

普通函数,由于闭包函数是 window 执行的,所以 this 指向 window;箭头函数的 this 指向函数创建时的作用域。var name = 'window'
var obj = {
    name:'obj',
    fn:function(){(()=>{ // 改成箭头函数
        console.log(this.name)
       })()}    

}
obj.fn()

改成箭头函数,后可以看出创建时的作用域是 obj.fn 函数执行是的作用域,也就是 obj

三、this 指向怎么改

js 提供了一些可以改变函数 执行作用域 的方法。因为普通函数如果通过上面的写法来改变 this 执行时上下文,写法就太过于麻烦。

apply、call、bind 用法

apply:
fn.apply(thisObj, 数组参数)
定义:应用某一个对象的一个方法,用另一个对象替换当前对象
说明:如果参数不是数组类型的,则会报一个 TypeError 错误。

call:
fn.call(thisObj, arg1, arg2, argN)
apply 与 call 的唯一区别就是接收参数的格式不同。

bind:
fn.bind(thisObj, arg1, arg2, argN)
bind()方法创建一个新的函数,在 bind()被调用时,这个新函数的 this 被 bind 的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

apply、call、bind 实现

apply 的实现:Function.prototype.myApply= function(context){
    context.fn = this;//1. 将函数挂载到传入的对象
    var arg = [...arguments].splice(1)[0];//2. 取参数
    if(!Array.isArray(arg)) {throw new Error('apply 的第二个参数必须是数组') //3. 限制参数类型为数组
    }    
    context.fn(arg) //4. 执行对象的方法
    delete context.fn; //5. 移除对象的方法
}

var obj = {name:'obj'}
function sayName(arr){console.log(this.name,arr)
}
sayName.myApply(obj,[1,2,3]) //obj [1, 2, 3]
call 实现:与 apply 的唯一区别就是参数格式不同

Function.prototype.myCall= function(context){
    context.fn = this;//1. 将函数挂载到传入的对象
    var arg = [...arguments].splice(1);//2. 取参数
    context.fn(...arg) //3. 执行对象的方法
    delete context.fn; //4. 移除对象的方法
}
var obj = {name:'obj1'}
function sayName(){console.log(this.name,...arguments)
}
sayName.myCall(obj,1,2,3,5) //obj1 1,2,3,5


bind 在 mdn 上的实现:Function.prototype.myBind = function(oThis){if(typeof this !== 'function'){throw new TypeError('被绑定的对象需要是函数')
    }
    var self = this
    var args = [].slice.call(arguments, 1)
    fBound = function(){ //this instanceof fBound === true 时, 说明返回的 fBound 被当做 new 的构造函数调用
        return self.apply(this instanceof fBound ? this : oThis, args.concat([].slice.call(arguments)))
    }
    var func = function(){}
    // 维护原型关系
    if(this.prototype){func.prototype = this.prototype}
    // 使 fBound.prototype 是 func 的实例,返回的 fBound 若作为 new 的构造函数,新对象的__proto__就是 func 的实例
    fBound.prototype = new func()
    return fBound
}

正文完
 0