Code前端首页关于Code前端联系我们

手稿系列调用、应用、绑定

terry 2年前 (2023-09-08) 阅读数 142 #Vue

关于调用、使用或者连接的功能,主要是用来改变this的指向,在很多地方经常可以看到,特别是在一些框架源码中,面试官也喜欢让你模拟并实现这些功能。在我之前的文章【this是谁?】中,介绍this的时候,就用了这些特征来展示绑定场景。如果您有兴趣,可以看看这篇文章。

我们来看看如何模拟实现这些功能~

1。调用函数的模拟与实现

1。语法:
function.call(thisArg, arg1, arg2, ...)
 

thisArg第一个参数是可选的,表示函数函数执行时绑定this值的对象。如果在非严格模式下传递 null 或 undefined,它将指向一个窗口。以下参数是执行该函数所必需的参数,也是可选的。

2。示例:
var namee = "黎奔";
let obj = {
    namee: 'liben'
}
function getName(x, m) {
    console.log(">>>>", `${this.namee}:姓${x}名${m}`)
}
getName('黎', '奔')  // 黎奔:姓黎名奔
getName.call(obj, "li", "ben") // liben:姓li名ben
 

可以看到,使用call函数改变this指向对象obj。 细心的人可能会注意到,namee变量是用var声明的,为什么不用let或const声明,因为用let或const声明的变量不会放在窗口中,在不使用call调用函数时,this指的是窗口并导致未找到的变量被打印为未定义。

3。如何实现:

从上面的例子我们可以了解到,手动执行时必须注意以下几点:

  • 调用绑定到函数原型

  • 将立即执行

  • 参数可选

实现原理:

就是为obj对象添加一个要调用的方法,然后调用该方法(目前this指向obj),调用完后删除该方法。

需要注意的是,this绑定的优先级=>[箭头函数>new>显式>隐式>默认绑定],调用属于显示箭头函数,new的优先级是无法改变的,所以你只能用隐含的方式来改变this的指向。

4。执行代码:
Function.prototype._call = function(context, ...args) {

    // 如果context不传,this就默认绑定到window
    const ctx = context || window;

    // 这里this指调用函数
    ctx.fn = this;
    // console.log(this)

    // 立即执行方法
    const res = ctx.fn(...args);

    delete ctx.fn;  // 为何要delete? 不删则绑定的ctx对象会添加一个fn方法进来可能产生副作用

    return res;  // 这里为何要return出去结果呢? _call相当于包装器,ctx.fn函数得到的结果也要return出去

}
 
5。验证:
// 验证例子
let age = 28;
let objCall = {
    age: 23
}
function getAge(x, m) {
    console.log(">>>>call", `姓${x}名${m}:年龄${this.age}`)
    return x + m;
}
getAge._call(objCall, "aa", "bb")  // >>>>call 姓aa名bb:年龄23
getAge._call(objCall) // >>>>call 姓undefined名undefined:年龄23
getAge._call(null) // >>>>call 姓undefined名undefined:年龄undefined
console.log(getAge("77", "88")) // >>>>call 姓77名88:年龄undefined  7788

console.log(getAge._call(objCall, 1, 2))  // >>>>call 姓1名2:年龄23  3

console.log(">>>delete?", objCall) // 不过不delete 则objCall对象会添加一个fn方法
 

这里我们自己模拟实现了call()函数。有疑问的可以一步一步打印出来看看注释是什么意思~

2。使用功能的模拟与实现

1。语法:
func.apply(thisArg, [argsArray])
 

第一个this Arg参数是可选的,表示函数function执行时绑定this值的对象。如果在非严格模式下传递 null 或 undefined,则它指向一个窗口。以下参数是执行该函数所必需的参数,也是可选的。

注意与 call 的区别:call() 方法接受参数列表,而 apply() 方法接受参数数组。

2。示例:
var namee = "黎奔";
let obj = {
    namee: 'liben'
}
function getName(x, m) {
    console.log(">>>>", `${this.namee}:姓${x}名${m}`)
}
getName('黎', '奔')  // 黎奔:姓黎名奔
// getName.call(obj, "li", "ben") // liben:姓li名ben
getName.apply(obj, ["黎", "奔"]) // liben:姓黎名奔

getName.apply()   //  >>>> vn:姓undefined名undefined
 
3。如何实现:

实现原理与调用相同,不同的是第二个参数必须是字符串

4。执行代码:
Function.prototype._apply = function(context, args) {

    // 如果context不传,this就默认绑定到window
    const ctx = context || window;

    // 这里this指调用函数
    ctx.fn = this;

    // 这里参数为数组
    const res = ctx.fn(...args);

    delete ctx.fn;

    return res;

}
 

和调用执行之间的区别在于参数参数的使用。

5。验证:
// 验证例子
var addr = "深圳";
let objApply = {
    addr: "岳阳"
}
function getAddress(a, b) {
    console.log(">>>>apply", `现居:${this.addr} - 国籍:${a} - 省:${b}`)
    // return a + b;
}
getAddress("china", "广东")  >>>>apply 现居:深圳- 国籍:china - 省:广东
getAddress._apply(objApply, ["china", "广东"]) // >>>>apply 现居:岳阳 - 国籍:china - 省:广东
getAddress._apply(null, ["china", "广东"])  // >>>>apply 现居:undefined - 国籍:china - 省:广东
 

3。仿真实现联动功能

1。语法:
function.bind(thisArg[, arg1[, arg2[, ...]]])
 

第一个 this Arg 参数是可选的,表示执行 function 函数时将绑定 this 值的对象。如果绑定函数参数列表为空或者this Arg为null或未定义,this会将YaoMingArg视为执行作用域中的新函数。以下参数是执行该函数所必需的参数,也是可选的。

如果一个函数需要传递name和age两个参数,其实可以在绑定时只传递一个name,然后在执行返回函数时传递另一个参数age

2。示例:
var a = "a";
let bindObj = {
    a: "bat"
}
function getA(x, y) {
    console.log(">>>>", `${this.a} - ${x} - ${y}`)
    return x + y
}
getA("你", "好")
getA.bind(bindObj, "你", "好")()  // >>>> bat - 你 - 好
getA.bind(bindObj)("你", "好")  // >>>> bat - 你 - 好  同上
getA.bind(bindObj, "可以", "挺狠")("你", "好") // >>>> bat - 可以 - 挺狠
getA.bind(bindObj, "nb")("你", "好") // >>>> bat - nb - 你 会忽略多余的参数
console.log(getA(1, 2))   // 3

// bind返回作为构造函数
let GetAFun = getA.bind(bindObj, 7, 8)
let getAFunIns = new GetAFun()  // >>>> undefined - 7 - 8 按理应该this.a应该返回bat 但这里返回undefined说明绑定失败
 
3。表演方法:
  • 该函数在函数原型中定义

  • 不会立即执行,它返回被调用的函数

  • 参数可选

4。执行代码:
Function.prototype._bind = function(context, ...args) {

    // 如果context不传,this就默认绑定到window
    const ctx = context || window;

    ctx.fn = this;
    // console.log(this)

    return function(...subArgs) {

        // return ctx.fn(...args.concat(subArgs))  // 可以先在bind()传入一部分,再执行返回时传入另外参数

        const res = ctx.fn(...args.concat(subArgs))

        delete ctx.fn;

        return res;

    }
}
 

实现原理是返回一个与this绑定的带参数的函数,这里ctx.fn(...args.concat(subArgs))可以通过调用或者断言来实现,不需要delete函数,但我们不会 或者这是一个模拟?如果用call的话可能会很困惑哈哈~

5。验证:
var b = "bbbb";
var _bindObj = {
    b: "nb"
}
function getB(x, y) {
    console.log("_bind>>>>", this.b, x, y)
    return x + y;
}
getB(1, 2)
getB._bind(_bindObj, 3, 4)() // _bind>>>> nb 3 4
getB._bind(_bindObj)(5, 6) // _bind>>>> nb 5 6
console.log(getB._bind(_bindObj, 3, 4)())  // 7
console.log(_bindObj)  // {b: "nb", fn: ƒ}

let GetBFun = getB._bind(_bindObj, "liben")
let getBFunIns = new GetBFun('菜鸡啊')  // _bind>>>> nb liben 菜鸡啊 这里this.b应该指向undefined才对
 

这里,当我们使用bind return作为构造函数时,我们发现它与官方的实现有所不同。this指出了问题所在。接下来我们看看最终的优化版本是如何写的。

6,装订最终版本:

参考【MDN Polyfill 最佳优化版本】

Function.prototype.newbind = function(context, ...args) {

    // 如果不是函数调用bind
    if (typeof this !== "function") {
        throw new Error("调用bind的不是函数~")
    }

    // 如果context不传,this就默认绑定到window
    const ctx = context || window;

    // 调用函数
    const self = this;

    const fNOP = function() {};
    const fBound = function(...subArgs) {
        return self.call(this instanceof fNOP ? this : ctx, ...args.concat(subArgs))
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}
 

验证示例

var c = "cccc";
var newbindObj = {
    c: "今晚我来C"
}
function getC(x, y) {
    console.log("newbind>>>>", this.c, x, y)
    return x + y;
}
let GetCFun = getC.newbind(newbindObj, "liben")
let getCFunIns = new GetCFun('菜鸡啊')  // newbind>>>> undefined liben 菜鸡啊
 

本文为作者总结整理。如有偏差请留言指正。如果觉得这篇文章对你有用的话还可以点赞哦~

作者简介:
GitHub
简书
掘金

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

上一篇:Dom 的理解 下一篇:预编译 JavaScript

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门