JS 函数中的this、手写call、 apply、bind

函数中的this指向

this可以理解为函数的执行上下文,在 普通函数 和 箭头函数 中this的指向是不同的

this是个关键字,JS中不允许给this赋值

  • 普通函数
    • this指向谁取决于函数最终被谁调用
    • 普通函数中的this只有函数被调用时才能确定指向谁
  • 箭头函数
    • 箭头函数中的this是静态的,不可被被改变
    • this总是继承自身定义环境所在的this值,即 箭头函数没有this, this会作为变量一直向上级词法作用域查找,直到找到为止

普通函数(function) this绑定规则:

  1. 作为函数调用, 即fn()
    1. 非严格模式下,指向全局对象; 严格模式下,为undefined
  2. 作为对象的方法调用, 即foo.fn() bar.fn() foo.bar.fn() fn0
    1. 指向调用该方法的对象
  3. 作为构造函数使用, 即 let obj = new Obj()
    1. 指向 new 出来的新对象
  4. 间接调用 foo.call() foo.apply() foo.bind()
    1. 指向传入的第一个参数

原生JS实现call apply bind

首先我们要明确的是:

call apply bind都是函数的方法。

call

call方法接收参数的方式为:

  • ​ 第一个参数:要在其上调用这个函数的对象,即调用这个函数的上下文
  • ​ 这个函数的参数,跟在第一个参数后面, 挨个传入

例: fn.call(obj, 参数1, 参数2, ...)

下面我们来实现一下call函数:

一、将函数的this指向改变为传入的对象
function fn (){
    console.log(this.name)
}

let obj = {
    name: 'Luffy'
}

Function.prototype.newCall = function (obj) {
  	//如果没传obj就将其指向window
  	obj = obj || window
    //此时this指向调用newCall的函数, 我们这里是fn
    // 我们将fn作为 传入的obj的方法, 此时fn中的this访问到的就是obj
    obj.p = this
		// 此时obj变为
    //  {
    //      name: 'Luffy',
    //      p () {
    //          console.log(this.name)
    //      }
    //  }
    obj.p()
  	//调用完之后,删掉p属性
    delete obj.p
}
fn.newCall(obj)

我们的第一步就实现了

二、处理函数的参数
function fn (a, b , c){
    console.log(this.name)
    console.log(a, b, c)
}

let obj = {
    name: 'Luffy'
}

Function.prototype.newCall = function (obj) {
    obj.p = this
    let newArgs = []
    //将传入的参数收集到一个newArgs中
    for(let i = 1; i < arguments.length; i++){
        newArgs.push(arguments[i])
    }
  	//调用时使用ES6 ...操作符将数组展开,一个个的传入fn中
    obj.p(...newArgs)
    delete obj.p
}
fn.newCall(obj, 1,2,3)

现在看起来就很完整了,但是我们忽略了一个问题, 函数有两个功能, 执行某些动作返回结果

我们并没有处理函数有返回值的情况,接下来处理一下

三、函数有返回值时返回内容

只要将代码稍微修改一下:

function fn (a, b , c){
    console.log(this.name)
    console.log(a, b, c)
}

let obj = {
    name: 'Luffy'
}

Function.prototype.newCall = function (obj) {
    obj.p = this
    let newArgs = []
    for(let i = 1; i < arguments.length; i++){
        newArgs.push(arguments[i])
    }
  	//将函数执行结果保存在一个变量中
    const result = obj.p(...newArgs)
    delete obj.p
  	//函数最后面将result返回
  	return result
}
fn.newCall(obj, 1,2,3)

到这里,我们的call就实现了

apply

apply方法在功能上和call是一模一样的,只是传参方式不同

apply方法参数:

  • ​ 第一个参数: 要在其上调用这个函数的对象,即调用这个函数的上下文(与call完全相同)
  • ​ 第二个参数: 一个数组, 将所有函数需要的参数 放入该数组中

实现思路与call大致相同,只是处理参数方式不同

function fn (a,b,c){
    console.log(this.name)
    return {
        name: this.name,
        age: a,
        age1: b,
        age2: c
    }
}

let obj = {
    name: 'Luffy'
}

Function.prototype.newApply = function (obj, arr){
    obj = obj || window
    obj.p = this
    let result
    if(!arr){
        result = obj.p()
    }else{
        if(!Array.isArray(arr)) throw new Error('params must be array');
        result = obj.p(...arr)
    }
    delete obj.p
    return result
}
let res = fn.newApply(fn, [1, 2, 3])
console.log(res)

bind

与call、apply 不同的是, bind会返回一个新的函数, 传给这个新函数的所有参数都会传给原始函数

bind存在柯里化特性, 即在第一个参数之后, 传给bind()的参数也会随着this值一起被绑定

function fn (a,b, c, d) {
    console.log(this.name)
    console.log(a, b, c, d)
}

let obj = {
    name:'Luffy'
}

Function.prototype.newBind = function (obj) {
    //保存一下this, 这里this指向调用newBind的函数
    //arr保存的是传入的参数, 是一个数组
    let self = this,
        arr = [...arguments].slice(1)
    return function () {
      	//为支持柯里化特性,arr2是后接收到的参数
        let arr2 = [...arguments],
            arrSum = arr.concat(arr2)
        self.apply(obj, arrSum)
    }
    
}
fn.newBind(obj, 1,2,3)(4)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值