new、call、apply、bind内部进行了什么操作?

new

思路

  1. 创建一个对象__proto__指向这个类的原型对象(可以使用prototype上的方法了)
  2. 调用类执行并且修改this指向为新创建的obj,并且传入剩余参数
  3. 判断如果构造函数返回的是引用类型返回这个值,否则返回obj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Animal(type) {
this.type=type;
// 如果当前构造函数返回的是一个引用类型 需要把这个返回
//return {name:'a'}
}
Animal.prototype.say = function () {
console.log('say')
}
function mockNew(){
// Constructor => Animal
let Constructor = [].shift.call(arguments);
let obj = {}; // 返回的结果
obj.__proto__ = Constructor.prototype; // 原型上的方法
let r = Constructor.apply(obj,arguments);
return r instanceof Object?r:obj;

}

let animal = mockNew(Animal,'爬行类');

console.log(animal.type); // 爬行类
animal.say() // say

call

思路

  1. 先找到call方法 => 只有函数才能找到call方法 => 找到call
  2. 并且把调用call这个函数中的this修改成call的第一个参数 => 改this
  3. 调用call方法的那个函数执行 => 这个函数中的this已经被修改过了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function fn1(){
console.log(1);
console.log(this);
}

function fn2(){
console.log(2);
console.log(this);
}

Function.prototype.call = function(context){
context = Object(context) || window
context.fn = this // fn1.this = fn2(这样是不可行的,js中不能直接改变this指向) ==> {}.fn1() fn1 中的this 就指向fn2了 也就相当于this();

let args = [];
for(let i = 1;i<arguments.length;i++){
args.push('arguments['+i+']');
}

eval('context.fn('+args+')')
}

// fn1 的this指向fn2 并且让fn1执行
fn1.call(fn2);

// 无论写多少个都会找到最终的一个call方法,并且把这个call中的this 变成 fn2,并且让这个call执行,call执行内部就会让fn2执行
fn1.call.call.call(fn2); //--> (fn1.call.call).call(fn2) --> call.call(fn2)

// 改变当前函数的this指向 并且让当前函数执行

apply

思路:和call一样,只不过第二个参数是数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.apply = function (context,args){
context = Object(context) || window;
context.fn = this;

if(!args){
return context.fn()
}

// 利用数组的toString的特性
let r = eval('context.fn('+ args +')');

delete context.fn;

return r;

};

fn1.apply(fn2,[1,2]);

bind

思路

  1. bind方法可以绑定this指向 绑定参数
  2. bind方法返回一个绑定后的函数(高阶函数)
  3. 如果绑定的函数被new了 当前函数的this就是当前的实例
  4. new 出来的结果可以找到原有类的原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let obj = {
name: 'aa'
}

function fn(name, age) {
console.log(this.name + '养了一只' + name + age + '岁')
}

Function.prototype.bind = function (context) {
let that = this; // 保存原来的方法
let bindArgs = Array.prototype.slice.call(arguments, 1);
function Fn() {}
function fBound() { //this
let args = Array.prototype.slice.call(arguments);
return that.apply(this instanceof fBound ? this : context, bindArgs.concat(args));
}
// fBound.prototype = this.prototype //这样两个类的原型指向同一个地址,不太好
Fn.prototype = this.prototype; // 中间件函数实现关联,
fBound.prototype = new Fn();
return fBound
}

fn.prototype.flag = '哺乳类';
let bindFn = fn.bind(obj, '猫');
//bindFn(9);

let instance = new bindFn(9);
console.log(instance.flag);