关于调用、使用或者连接的功能,主要是用来改变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
简书
掘金
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。