sisterAn/JavaScript-Algorithms

字节:模拟实现 new 操作符

sisterAn opened this issue · 14 comments

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this

代码实现:

function new_object() {
  // 创建一个空的对象
  let obj = new Object()
  // 获得构造函数
  let Con = [].shift.call(arguments)
  // 链接到原型 (不推荐使用)
  obj.__proto__ = Con.prototype
  // 绑定 this,执行构造函数
  let result = Con.apply(obj, arguments)
  // 确保 new 出来的是个对象
  return typeof result === 'object' ? result : obj
}

警告: 通过现代浏览器的操作属性的便利性,可以改变一个对象的 [[Prototype]] 属性, 这种行为在每一个JavaScript引擎和浏览器中都是一个非常慢且影响性能的操作,使用这种方式来改变和继承属性是对性能影响非常严重的,并且性能消耗的时间也不是简单的花费在 obj.__proto__ = ... 语句上, 它还会影响到所有继承来自该 [[Prototype]] 的对象,如果你关心性能,你就不应该在一个对象中修改它的 [[Prototype]]。相反, 创建一个新的且可以继承 [[Prototype]] 的对象,推荐使用 Object.create()

—MDN

所以进一步优化 new 实现:

// 优化后 new 实现
function create() {
  // 1、获得构造函数,同时删除 arguments 中第一个参数
  Con = [].shift.call(arguments);
  // 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
  let obj = Object.create(Con.prototype);
  // 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
  let ret = Con.apply(obj, arguments);
  // 4、优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
};

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this。
function myNew(fn, ...args) {
  const obj = {};
  obj.__proto__ = fn.prototype;
  const res = fn.call(obj, ...args);
  return typeof res === 'object' ? res : obj;
}
function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype); // 相当于 obj.__proto__ = fn.prototype
  
  let result = fn.apply(obj, args); // 执行构造方法, 绑定新 this
  
  // 如果构造方法 return 了一个对象,那么就返回该对象,否则返回创建的新对象
  return Object.prototype.toString.call(result) === '[object Object]' ? result : obj;
}
function myNew() {
  // 创建一个空对象
  let obj = {};
  //获取构造函数
  let Con = [].shift.call(arguments);
  // 设置对象的原型
  obj.__proto__ = Con.prototype;
  // 绑定this
  let result  = Con.apply(obj, arguments);
 // 判断返回值是不是一个对象,如果是,那么返回这个对象,否则返回新创建的对象
  return typeof result === 'object'  ? result  : obj;
}

function _new(Fn, ...args) {
let obj = Object.create(Fn.prototype);
let res = Func.call(obj, ...args);
if(res !== null && /^(object|function)$/.test(typeof res) return res;
return obj;
}

function myNew(fn, ...args) {
  let _this = {}
  let result = fn.apply(_this, args)
  _this.__proto__ = fn.prototype
  return typeof (result === 'object' || typeof result === 'function') &&
    result != null
    ? result
    : _this
}
function _new(){
  const Constructor = Array.prototype.shift.call(arguments)
  const obj = {}
  obj.__proto__ = Constructor.prototype
  const ret = Constructor.apply(obj,arguments)
  return typeof ret === 'object' ? ret : obj
}

步骤:创建,执行,原型链,判断。

  1. 创建:创建一个新的空对象(对象字面量{}或者借用构造函数new Object)
  2. 执行:this指向新对象,执行构造函数,故应先获取构造函数。
  3. 原型链:设置原型链,新对象的__proto__指向构造函数的prototype
  4. 判断:判断传入对象的类型,是对象,则返回新对象;不是对象,则直接返回。
/**
 * @file 模拟new
 * @author 阿吉
 * @returns {object} 新对象
 */
function lxhNew(){
    // 1. 创建
    let obj = new Object();
    // 2.执行
    let Constructor = [].shift.call(arguments);
    let result = Constructor.apply(obj, arguments);
    // 3.原型链
    result.__proto__ = Constructor.prototype;
    // 4.判断
    return result instanceof Object? result: obj;
}
function _new(fn, ...args) {
  const obj = Object.create(fn.prototype);
  const res = fn.apply(obj, args);
  const type = typeof res;
  if((type === "object" || type === "function") && res !== null) {
    return res;
  }
  return obj;
}

所以进一步优化 new 实现:

// 优化后 new 实现
function create() {
  // 1、获得构造函数,同时删除 arguments 中第一个参数
  Con = [].shift.call(arguments);
  // 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
  let obj = Object.create(Con.prototype);
  // 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
  let ret = Con.apply(obj, arguments);
  // 4、优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
};

@sisterAn 最后优化的版本 变量 Con的声明漏掉了let关键词

function create() {
  ....
  let Con = [].shift.call(arguments);
  ....
};

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});
  2. 链接该对象(即设置该对象的构造函数)到另一个对象 ;
  3. 将步骤1新创建的对象作为this的上下文 ;
  4. 如果该函数没有返回对象,则返回this

代码实现:

function new_object() {
  // 创建一个空的对象
  let obj = new Object()
  // 获得构造函数
  let Con = [].shift.call(arguments)
  // 链接到原型 (不推荐使用)
  obj.__proto__ = Con.prototype
  // 绑定 this,执行构造函数
  let result = Con.apply(obj, arguments)
  // 确保 new 出来的是个对象
  return typeof result === 'object' ? result : obj
}

警告: 通过现代浏览器的操作属性的便利性,可以改变一个对象的 [[Prototype]] 属性, 这种行为在每一个JavaScript引擎和浏览器中都是一个非常慢且影响性能的操作,使用这种方式来改变和继承属性是对性能影响非常严重的,并且性能消耗的时间也不是简单的花费在 obj.__proto__ = ... 语句上, 它还会影响到所有继承来自该 [[Prototype]] 的对象,如果你关心性能,你就不应该在一个对象中修改它的 [[Prototype]]。相反, 创建一个新的且可以继承 [[Prototype]] 的对象,推荐使用 Object.create()
—MDN

所以进一步优化 new 实现:

// 优化后 new 实现
function create() {
  // 1、获得构造函数,同时删除 arguments 中第一个参数
  Con = [].shift.call(arguments);
  // 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
  let obj = Object.create(Con.prototype);
  // 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
  let ret = Con.apply(obj, arguments);
  // 4、优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
};

Con.apply(obj, arguments); 这一步是什么意思呀?

/**
 * new 使用Js原生实现
 */
function Parent(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log(this.name);
    }
}
const _new = function (Parent, ...rest) {
    //1.以构造器Parent的prototype为原型创建新对象
    const child = Object.create(Parent.prototype);
    //2. 将this和调用参数传给构造器执行
    const result = Parent.apply(child, rest);
    return typeof result === 'object' ? result : child;
}
const p1 = _new(Parent,'www','23');
console.log(p1);
p1.sayName(); 
/**
 * new 使用Js原生实现
 */
function Parent(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log(this.name);
    }
}
const _new = function (Parent, ...rest) {
    //1.以构造器Parent的prototype为原型创建新对象
    const child = Object.create(Parent.prototype);
    //2. 将this和调用参数传给构造器执行
    const result = Parent.apply(child, rest);
    return typeof result === 'object' ? result : child;
}
const p1 = _new(Parent,'www','23');
console.log(p1);
p1.sayName(); 

因为 Parent 没有返回值,所以const result = Parent.apply(child, rest); 的 result 永远是 undefined

function newFn(f) {
  let obj, ret, proto;
  proto = Object(f.prototype) === f.prototype ? f.prototype : Object.prototype;
  obj = Object.create(proto);
  ret = f.apply(obj, Array.prototype.slice.call(arguments, 1));
  return Object(ret) === ret ? ret : obj
}

不相信有人不查阅资料一次能写出来,目前来看最后一步这个写法最科学。

  if((type === "object" || type === "function") && res !== null) {
    return res;
  }

有同学使用Object.prototype.toString.call,但是忽略了,它的返回值不一定是[object Object],比如Array,比如更改了Symbol.toStringTag的对象。总之还是太卷了