今天公司没那么忙 闲来无事 就手动实现下js的call,apply和new的原理吧~本篇不长 废话不多 分为3步:手写call方法手写apply方法手写new方法我们知道 call可以改变this指向,同时也可以传递参数。即 call的第一个参数为改变后的this,剩余参数则是正常的函数参数。并且,调用call和apply后相当于改变this并立马执行函数。ok,上代码~let str = ‘str’;let obj = { name:‘123’};function fn(){ console.log(this);}fn.call(str); // 打印 String {“str”}fn.call(obj); // 打印 {name: “123”}手写call方法注意观看顺序1,2,3,4,5…/* Function原型上拓展call方法*/Function.prototype.myCall = function(context){ /** 1 如果没传上下文conntext 就取window为this * 此处Object() 主要考虑到如果是String类型 / context = context?Object(context):window; /* 2 改变this指向 * this就是原函数 / context.fn = this; /* 3 取参数 注意从第二个开始取 * 因为第一个参数是上下文context 也就是this / let args = []; for(let i = 1; i < arguments.length; i++){ /* 4 这里传递的上字符串 因为待会要配合eval()使用 / args.push(‘arguments[’+ i +’]’); } /* 5 把参数传递进去 eval()方法可以让字符串执行 / let r = eval(‘context.fn(’+ args +’)’); /* 6 删除原context.fn / delete context.fn; /* 7 返回r / return r;}let str = ‘str’;let obj = { name:‘123’};function fn(){ console.log(this);}fn.myCall(str); // 打印 String {“str”}fn.myCall(obj); // 打印 {name: “123”}????. 接下来是apply方法 ,其实apply方法最简单 因为apply和call方法的唯一区别就是apply第一个参数后面的参数是数组形式 仅此而已。 所以 我们在call方法上进行稍微改造就好:手写apply方法注意观看顺序1,2,3,4,5…/ Function原型上拓展apply方法*/Function.prototype.myApply = function(context,args){ /** 1 如果没传上下文conntext 就取window为this * 此处Object() 主要考虑到如果是String类型 / context = context?Object(context):window; /* 2 改变this指向 * this就是原函数 / context.fn = this; /* 3 把参数传递进去 eval()可以让字符串执行 / let r = eval(‘context.fn(’+ args +’)’); /* 6 删除原context.fn / delete context.fn; /* 7 返回r / return r;}let str = ‘str’;let obj = { name:‘123’};function fn(){ console.log(this);}fn.apply(str); // 打印 String {“str”}fn.apply(obj); // 打印 {name: “123”}fn.myApply(str); // 打印 String {“str”}fn.myApply(obj); // 打印 {name: “123”}可以看到 apply的实现方法比call简单许多 这是因为call需要处理其他参数的情况,而apply的参数本身就是一个数组 直接传递进去 执行就好~ok.call和apply我们都手动实现了,接下来就是new了~首先是原newfunction Animal(type){ this.type = type;}Animal.prototype.eat = function(){ console.log(’eat-meat’);}let tiger = new Animal(’tiger’);console.log(tiger.type); // 打印 tigerconsole.log(tiger.eat()); // 打印 eat-meat手写new方法注意观看顺序1,2,3,4,5…function myNew(){ /* 1 我们都new第一个参数为构造函数 * shift:把数组的第一个元素从其中删除,并返回第一个元素的值 * 此时Constructor就是构造函数 / let Constructor = Array.prototype.shift.call(arguments); /* 2 将要返回的实例 / let obj = {}; /* 3 实例obj和构造函数Constructor指向同一个原型对象 / obj.proto = Constructor.prototype; /* 4 拿到实例执行的结果 观察结果是不是一个引用类型 * 改变Constructor的this指向为将要返回的实例obj 并传递参数 * 这里一定要用apply 不要用call 因为apply传递的是一个参数数组 */ let r = Constructor.apply(obj,arguments); return r instanceof Constructor? r : obj;}function Animal(type){ this.type = type;}Animal.prototype.eat = function(){ console.log(’eat-meat’);}let tiger = new Animal(’tiger’);let tiger1 = new myNew(Animal,’tiger’);console.log(tiger.type); // 打印 tigerconsole.log(tiger.eat()); // 打印 eat-meatconsole.log(tiger1.type); // 打印 tigerconsole.log(tiger1.eat()); // 打印 eat-meat好.至此 call apply和new的原理我们都手写出来了. 其实还有一个bind方法,不过bind方法略麻烦,对于bind 我会单独写一篇文章.代码在git上