实现 new、apply、call、bind 的底层逻辑
-
apply、call、bind在前端代码开发中非常重要,在很多时候,可以通过这些来节约内存。这些包括new都与this的指向密切相关。所以必须要了解这些方法
-
思考题:
-
① 用什么样的思路可以new关键词?
-
② apply、call、bind这三个方法之间有什么区别?
-
③ 怎么样实现一个apply或者call的方法?
-
一、new
-
这个的关键词主要作用是执行一个构造函数,
-
这个结果比较简单相信大家都知道,接下来用一段代码来过
function Person() {
this.name = 'Jack';
}
var p = new Person();
console.log(p.name) // Jack
(1)new在生成实例过程中分几个步骤呢?
-
① 创建一个新对象
-
② 将构造函数的作用于赋予新对象(this指向新对象)
-
③ 执行构造函数中的代码(为这个新对象添加属性)
-
④ 返回新对象
(2)如果去掉new会怎么样的?
-
去掉new就像我们普通使用的函数一看,返回值返回什么就是什么,而且this指向的是window
-
如:
function Person(){
this.name = 'Jack';
}
var p = Person();
console.log(p) // undefined
console.log(name) // Jack
console.log(p.name) // 'name' of undefined
-
可以看出不使用new的话就会把this指向window,并且在window添加属性
(3)当函数有返回值的话呢?
function Person(){
this.name = 'Jack';
return {age: 18}
}
var p = new Person();
console.log(p) // {age: 18}
console.log(p.name) // undefined
console.log(p.age) // 18
-
用一段代码感受一下
-
当return出来的是一个和this无关的对象的时候,new会直接返回这个新对象,而不是它通过new执行步骤生成的this对象
-
这里要求构造函数必须返回的是一个对象,如果不是对象,还是会按照new的实现步骤,返回新生成的对象
-
如
-
function Person(){
this.name = 'Jack';
return 'tom';
}
var p = new Person();
console.log(p) // {name: 'Jack'}
console.log(p.name) // Jack
-
即使有了return,也还是执行new的操作
(4)自己实现的new
function _new(ctor, ...args) {
if (typeof ctor !== 'function') throw 'ctor must be a function';; // 必须为一个函数
let obj = {}; // 创建一个空对象
Reflect.setPrototypeOf(obj, ctor.prototype); // 绑定原型
let res = ctor.apply(obj, args); // 这里做了两件事, 1. 获取ctor的返回值 2. 将ctor的this指向obj并执行里面的代码,包括往this添加属性
return typeof res === 'function' || (typeof res === 'object' && typeof res !== 'null') ? res : obj;
} // 判断是否为一个对象
二、apply & call & bind 原理介绍
-
先来了解一下这三个方法的基本情况,call、apply、bind是挂在Function对象上的三个方法,调用这三个方法的必须是一个函数
-
这三种方法的基本语法
func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
-
这三个方法共有的作用就是,都可以改变func的this指向,call和apply的区别在于传入的参数不同
-
apply需要的是数组
-
call传入的是参数列表
-
-
而bind的区别就是在于它不会立刻执行函数
-
结合一段代码感受一下
-
let a = {
name: 'jack',
getName: function(msg) {
return msg + this.name;
}
}
let b = {
name: 'lily'
}
console.log(a.getName('hello~')); // hello~jack
console.log(a.getName.call(b, 'hi~')); // hi~lily
console.log(a.getName.apply(b, ['hi~'])) // hi~lily
let name = a.getName.bind(b, 'hello~');
console.log(name()); // hello~lily
(1)应用场景
-
比如判断数据类型的时候,借用Object.prototyoe的toString方法
-
类数组项进行数组操作的时候借用数组的方法
-
获取最大最小值(Math的方法)
-
继承中改变this执行
(2)apply和call的实现
-
因为两个基本相像所以放在一起,主要通过eval来实现的
Function.prototype.myCall = function (content, ...args) {
var content = content || window; // 没有制定content则默认window,使用var是因为要绑定到全局中
content.fn = this; // 使要指向的对象添加一个fn方法
let result = eval('content.fn(...args)'); // 使用eval调用该方法
delete content.fn; // 完成之后删除引用
return result;
}
Function.prototype.myApply = function (content, args) {
var content = content || window;
content.fn = this;
let result = eval('content.fn(args)');
delete content.fn;
return result;
}
// 同上
(3)bind的实现
-
基本思路与apply和call一样,但是这里最后返回的结果不同
Function.prototype.myBind = function (content, ...args) { if (typeof this !== 'function') throw 'this must be a function'; // 必须是一个函数 const self = this; // 将这个函数的this指向保存在self const fbind = function () { self.apply(this instanceof self ? this : content, Array.prototype.concat.apply(args, arguments)); } // 定义的这个新函数里面执行的就是一个apply, self中的函数执行,然后在这过程中改变this指向为content if (this.prototype) Reflect.setPrototypeOf(fbind, this.prototype); // 将原型对象赋予给fbind return fbind; }
(4)总结
-
用一张图来表示