关于调用、使用或者连接的功能,主要是用来改变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前端网

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