new, call,apply,bind 原理剖析及手写实现
分类:javascript
new, call,apply,bind的手写实现
我们都知道,js中call,apply,bind的作用都是改变作用域,来改变this的指向。这些javascript中概念在我们前端领域有着非常重要的概念,经常被使用到。那么它们的原理到底是什么,怎样用代码模拟实现呢?那么作为经典面试题,我们背答案是肯定行不通的,所以就需要从根本上去理解其实现原理,才能自己手写实现。接下来就让我们彻底掌握它们。
1. new
首先我们看new这个关键词,它在Javascript中的主要作用就是执行一个构造函数、然后返回一个实例对像。那么在new 生成实例的这个过程中,发生了什么呢?
大致分为四个步骤:
- 创建一个对象;
- 将构造函数的作用域赋给这个新对象(相当于将this 指向新对象);
- 执行构造函数中的代码(为这个新对象添加属性和方法);
- 返回这个新对象;
- 那我们就可以用代码来实现了,其实只需要完成这几个功能。
- 让实例可以访问到私有属性;
- 让实例可以访问构造函数原型所在的原型链上的属性;
- 构造函数返回的最后结果是引用数据类型;
function _new(fn, ...args) {
if(typeof fn !== 'function') {
throw 'fn must be a function';
}
let obj = new Object();
obj.__proto__ = Object.create(fn.prototype);
let res = fn.apply(obj, [...args]);
let isObject = typeof res === 'object' && res !== null;
let isFunction = typeof res === 'function';
return isObject || isFunction ? res : obj;
};
2. apply & call & bind 原理解析
我们可以先看看这三个函数的基本用法,call,apply,bind 都是需要挂在Function对象上的三个方法,并且调用这三个方法必须得是函数。
func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2, ...])
func.bind(thisArg, param1,param2, ...)
-
其中func 是要调用的函数,thisArg 一般是this所指的对象,后面的为参数。
-
这三者的公共点就是,都是改变函数func的this指向。其中 call和apply 的区别在于传参的写法不同:
call 是一个一个参数传,而apply是以数组的形式传入。- 然后bind 与这两者的区别在于:bind函数不会马上执行,而(call 与 apply)是在改变了函数的this指向后立马执行。
-
晓得了这三个的原理,让我们动手来实现一下吧。
- call 和 apply的基本原理是差不多的,这里放在一块写。
Function.prototype.call = function (context, ...args) {
var context = context || window;
context.fn = this;
var res = eval('context.fn(...args)');
delete context.fn
return res;
}
Function.prototype.apply = function (context, args) {
let context = context || window;
context.fn = this;
let res = eval('context.fn(...args)');
delete context.fn
return res;
}
- bind的实现
- 这里需要注意bind的核心是需要返回一个函数。这样调用bind方法接收到函数的对象,再通过执行接收的函数,就可以得到想要的结果。
- 一个 绑定函数 也能使用 new 操作符创建对象,这种行为就像把原函数当成构造器,thisArg 参数无效。也就是 new 操作符修改 this 指向的优先级更高。
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw TypeError('error');
}
// 缓存this
const self = this;
const args = [...arguments].slice(1);
//返回一个函数
return function fn() {
// 判断调用方式
if (this instanceof fn) {
return new self(...args, ...arguments);
}
return self.apply(context, args.concat(...arguments));
};
};
总结
那么到这里我们就用代码模拟完成了日常写代码经常用到的new,apply,call,bind这几种方法,可以看出来,这几个方法是有一些区别和联系的。
下面有一个表格整理了call,apply,bind的异同点。
标题 | call | apply | bind |
---|---|---|---|
参数 | 一个一个列出 | 一个数组列出 | 单个单个列出 |
功能/用处 | 函数调用改变this | 函数调用改变this | 函数调用改变this |
返回结果 | 直接执行 | 直接执行 | 返回一个待执行函数 |
从底层逻辑了解这些,可以让我们在一些场景更加熟练的使用它们。
例如:继承--各种各样的继承方式,还有判断数据类型的Object.prototype.toString.call()方法等等都会运用相关的知识点。
本人前端准实习生,在各方面有何不足欢迎指正,让我们共同进步。