前端手写系列(一)

159 阅读3分钟

手写 new

用法

function Person(name){
    this.name=name;
    this.sayname=function(){
        console.log(this.name)
    }
}

var sbn = new Person ('撒贝宁');
sbn.sayname();	// 撒贝宁

在调用 new 的过程中会发生以下四件事:

  • 新生成一个对象
  • 将构造函数的作用域赋值给新对象(即绑定新对象的 this)
  • 执行构造函数中的代码(即为这个新对象添加属性和方法)
  • 返回新对象

代码

function Person(name){
    this.name=name;
    this.sayname=function(){
        console.log(this.name)
    }
}

// ...args为ES6展开符,如果使用arguments要先将其转换为数组
function _new(ctx, ...args) {
  // 创建空对象
  let obj = new Object();
  // 连接新对象原型,新对象可以访问原型中的属性
  obj._proto_ = ctx.prototype;
  // 执行构造函数,即绑定this,并且为这个对象添加属性
  let res = ctx.call(obj, ...args);
  // 如果函数返回的事null或者undefined则返回obj,否则返回res
  return res instanceof Object? res:obj;
}

var sbn = _new(Person, '撒贝宁')
sbn.sayname();	// 撒贝宁

总结

new一个对象的过程:

  • 创建一个空对象
  • 对新对象进行 prototype 绑定
  • 将新对象和函数调用的this绑定起来
  • 执行构造函数的方法
  • 如果函数没有返回值则自动返回这个对象

手写 instanceof

作用

用来检测一个对象是否是某个构造函数的实例对象,如果是则返回true,如果不是,则返回false。通过 === 严格等于来比较两个原型是否相等。

原型对象和原型链

函数即对象,每个函数都有一个prototype属性,这个属性就是函数的原型对象,在原型对象上定义的属性或方法,会被该函数的实例对象所继承,实例对象可以直接访问到原型对象上面的属性或方法。

prototype属性默认值为空对象,这个空对象正是来自Object的构造函数。

__proto__属性是在实例被创建的时候,js引擎为实例自动添加的一个属性,等于其构造函数的prototype属性,也就是说实例的__proto__就是这个实例的原型对象。

在代码中我们可以直接操作prototype属性,prototype被称为显式原型对象。代码上不推荐直接访问__proto____proto__被称为隐式原型对象。

当实例要访问某一个属性的时候,首先会在实例自身查找,如果没有找到,就继续沿着__proto__对象查找,如果还是没找到,则继续对__proto____proto__对象查找,如果最终找到__proto__对象为null时都没有找到,就返回属性未找到的错误,如果找到了就返回该属性值。这个由若干个__proto__对象串联起来的查找路径,就称为原型链

代码

// L实例对象,R构造函数
function _instanceof(L, R) {
  let l = L.__proto__;
  let r = R.prototype;
  while (true) {
    if (l == null) {
      return false;
    }
    if (l === r) {
      return true;
    }
    // 如果本轮没找到,则沿着__proto__继续向上查找
    l = l.__proto__;
  }
}
console.log(_instanceof([1,2,3], Array)) // true

总结

函数接收两个参数,第一个是被检测的对象,第二个是实例的构造函数。然后循环比较实例的__proto__和构造函数的prototype是否相等,当__proto__为null的时候停止循环并返回false,当找到了就返回true。