手写call
原生call具备的三个功能
- 改变this指向。
- 调用call就是调用函数。(能够返回结果)
实现代码
function person(a,b,c) { return { name: this.name, a: a, b: b, c: c } } const egg = {name: 'hello'}; Function.prototype.myCall = function(obj) { obj = obj || window obj.p = this; const newArg = []; for (let i = 1; i < arguments.length; i++) { newArg.push(arguments[i]); } const result = obj.p(...newArg); delete obj.p; return result; } person.myCall(egg,'点赞','收藏','转发') person.call(egg,'点赞','收藏','转发') 复制代码
2. 手写apply
apply和call很类似,区别在于apply只接受两个参数,一个是this的指向,另一个是是一个数组。
原生apply具备的几个功能
- 改变this指向。
- 调用函数并接受返回值。
实现代码
function person(a,b,c) { return { name: this.name, a: a, b: b, c: c } } const egg = {name: 'hello'}; Function.prototype.myApply = function(obj,arr) { obj.p = this; let result; if (!arr) { result = obj.p(); } else { result = obj.p(...arr); } delete obj.p; return result; } person.myApply(egg,['点赞','收藏','转发']) person.apply(egg,['点赞','收藏','转发']) 复制代码
3. 手写bind
原生bind具备的功能
- 会返回一个函数。(这个返回的函数应该是改变过this的,并且接受参数就能执行的)
- 会修改this指向。
- 原生bind方法返回的函数如果通过new创建实例的时候,会使得this失效。不调用new,this不会失效。
注意事项
- arguments其实是对象,而不是数组。
- 使用Array.prototype.slice.call(arguments,1):可以获取arguments包括第二个元素之后的所有元素,并将其装换为数组。
- 下面的题解涉及到了原型链的知识,值得我们多次反复揣摩。
实现代码
function person(a,b,c) { console.log(this.name); console.log(a,b,c); } const egg = {name: 'hello'}; Function.prototype.myBind = function(obj) { let that = this; let arr = Array.prototype.slice.call(arguments,1); // 定义一个空函数作为桥梁 let empty = function() {} let newf = function () { let arr2 = [].slice.call(arguments); let arrSum = arr.concat(arr2); // 判断是否调用了new if (this instanceof empty) { that.apply(this,arrSum); } else { that.apply(obj,arrSum); } } empty.prototype = that.prototype; newf.prototype = new empty; return newf; } const bibi = person.myBind(egg,'点赞','投币') // const b = new bibi('充电') bibi('充电') // person.apply(egg,['点赞','收藏','转发']) 复制代码
参考链接
原生JavaScript实现call、apply和bind - Web前端工程师面试题讲解
启示
- 首先call、apply、bind看似简单,实则需要很强的基本功。
- 本文需要多次反复阅读,揣摩,加油,希望多看几遍!