module1-04-实现 new、apply、call、bind 的底层逻辑

实现 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)总结

  • 用一张图来表示

上一篇:spring成神之路第二十四篇:父子容器详解


下一篇:用jamon来监控你的sql执行效率