javascript的call apply和new原理剖析 [手写]

33次阅读

共计 2697 个字符,预计需要花费 7 分钟才能阅读完成。

今天公司没那么忙 闲来无事 就手动实现下 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 了~
首先是原 new

function Animal(type){
this.type = type;
}

Animal.prototype.eat = function(){
console.log(‘eat-meat’);
}

let tiger = new Animal(‘tiger’);

console.log(tiger.type); // 打印 tiger
console.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); // 打印 tiger
console.log(tiger.eat()); // 打印 eat-meat

console.log(tiger1.type); // 打印 tiger
console.log(tiger1.eat()); // 打印 eat-meat
好. 至此 call apply 和 new 的原理我们都手写出来了. 其实还有一个 bind 方法,不过 bind 方法略麻烦,对于 bind 我会单独写一篇文章.
代码在 git 上

正文完
 0