共计 2635 个字符,预计需要花费 7 分钟才能阅读完成。
1. 什么是 call 和 apply
传送门:了解与应用 js 中的 apply()和 call()
至于为什么要手写代码,不仅仅是为了面试,也是为了帮忙咱们更好的了解代码逻辑,深刻其中原理最好的办法。
2. 手写 call
写个 call 办法,跟写一个我的项目是一样的。都要先剖析其需要的性能点,需要剖析要到位,再去一一击破。
首先,咱们须要晓得 call 是函数原型上的一个办法,它的作用是绑定对象和参数,并执行函数。应用办法如下:
function.call(thisObj, arg1, arg2, ...)
开始实现:
- 咱们就给咱们手写的 call 办法叫 myCall,承受的第一个参数 thisObj 是指标函数执行时的指标对象的值,从第二个可选参数 arg1 开始的其余参数,将作为指标函数执行时的实参。
-
须要剖析 call 函数都有哪些‘需要’
- 须要判断是否严格模式(this 作用域的指向问题,非严格模式须要对 thisObj 非凡解决)
- 如何判断严格模式
- 如果 thisObj 不是对象类型怎么解决,因为咱们是劫持绑定对象
代码如下:
// 首先 apply 是原型链 Function.prototype 上的一个办法 | |
Function.prototype.myCall = function() { | |
// 通过 arugments 拿到所有参数 | |
// 第一个参数是绑定的 this 对象 | |
var thisObj = arguments[0]; | |
// 判断是否严格模式 | |
var isStrictMode = (function(){return this === undefined}()) | |
if (!isStrictMode) { | |
// 如果在非严格模式下,thisObj 的值是 null 或 undefined,须要将 thisObj 置为全局对象 | |
if (thisObj === null || thisObj === undefined) { | |
// 获取全局对象时兼顾浏览器环境和 Node 环境 | |
thisObj = (function(){return this}()) | |
} else { | |
// 非对象类型,须要转换类型 | |
var tthisObjType = typeof thisObj | |
if (thisObjType === 'number') {thisObj = new Number(thisObj) | |
} else if (thisObjType === 'string') {thisObj = new String(thisObj) | |
} else if (thisObjType === 'boolean') {thisObj = new Boolean(thisObj) | |
} | |
} | |
} | |
// 从索引 1 开始的残余参数 | |
var invokeParams = [...arguments].slice(1); | |
// 接下来要调用指标函数,那么如何获取到指标函数呢?// 实际上 this 就是指标函数,因为 myCall 是作为一个办法被调用的,this 当然指向调用对象,而这个对象就是指标函数 | |
// 这里做这么一个赋值过程,是为了让语义更清晰一点 | |
var invokeFunc = this; | |
// 此时如果 thisObj 对象依然是 null 或 undefined,那么阐明是在严格模式下,并且没有指定第一个参数或者第一个参数的值自身就是 null 或 undefined,此时将指标函数当成一般函数执行并返回其后果即可 | |
if (thisObj === null || thisObj === undefined) {return invokeFunc(...invokeParams) | |
} | |
// 否则,让指标函数成为 thisObj 对象的成员办法,而后调用它 | |
// 直观上来看,能够间接把指标函数赋值给对象属性,为了 key 值惟一,避免笼罩掉 thisObj 对象的原有属性,能够创立一个惟一的属性名,应用 Symbol 解决 | |
var symbolPropName = Symbol(thisObj) | |
thisObj[symbolPropName] = invokeFunc | |
// 返回指标函数执行的后果 | |
return thisObj[symbolPropName](...invokeParams) | |
} |
上代码测试一下:
Math.max.myCall(null, 1,2,3,4,5) | |
// 5 | |
function mycallTest(a, b) {var args = [].slice.myCall(arguments) | |
console.log(arguments, args) | |
} | |
mycallTest(1, 2) | |
var obj = {name: 'jack'}; | |
var name = 'ross'; | |
function getName() {return this.name;} | |
getName(); | |
getName.myCall(obj); |
能够看到,成果是有的。然而毕竟是手写的,跟源码还是有区别的,思考状况可能会有不到位的中央,会有 bug 场景。
3. 手写 apply
当咱们写了 call 之后,apply 就不便许多了,很多逻辑基本一致。咱们只须要留神两个办法的差别点:第二个参数为数组,上面就不写正文了,间接上代码:
Function.prototype.myApply = function(thisObj, params) {var isStrict = (function(){return this === undefined}()) | |
if (!isStrict) { | |
var thisObjType = typeof thisObj | |
if (thisObjType === 'number') {thisObj = new Number(thisObj) | |
} else if (thisObjType === 'string') {thisObj = new String(thisObj) | |
} else if (thisObjType === 'boolean') {thisObj = new Boolean(thisObj) | |
} | |
} | |
var invokeFunc = this; | |
// 解决第二个参数 | |
var invokeParams = Array.isArray(params) ? params : []; | |
if (thisObj === null || thisObj === undefined) {return invokeFunc(...invokeParams) | |
} | |
var symbolPropName = Symbol(thisObj) | |
thisObj[symbolPropName] = invokeFunc | |
return thisObj[symbolPropName](...invokeParams) | |
} |
简略测试一下:
Math.max.myApply(null, [1, 2, 4, 8]) | |
// 8 |
OK,功败垂成!
正文完
发表至: javascript
2020-09-11