haizlin/fe-interview

[js] 第22天 你对new操作符的理解是什么?手动实现一个new方法

Opened this issue · 31 comments

第22天 你对new操作符的理解是什么?手动实现一个new方法

new 的理解

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

new步骤

模拟new操作前,要先知道new操作是发生了什么,就拿new Object()举例:

  1. 创建一个新对象
  2. 把新对象的原型指向构造函数的prototype
  3. 把构造函数里的this指向新对象
  4. 返回这个新对象

构造函数:

先准备一个构造函数来new使用。

function constructorFunction(name, age){
  this.name = name;
  this.age = age;
}
constructorFunction.prototype.say = function(){
  return 'Hello '+ this.name
}

原生new:

var obj = new constructorFunction('willian', 18)
console.log(obj.name, obj.age);//'willian', 18
console.log(obj.say())//Hello willian

模拟new

模拟的new 暂称为newNew (囡..囡 哈哈~)
使用:newNew(constructor, arg1, arg2, ..) 第0个参数传入构造函数,1~n个参数是构造函数的形参。
使用上面的构造函数试一下:

function newNew(){
 var newObj = {}
 // 1. 创建一个新对象
 var Con = [].shift.call(arguments)
 // 得到构造函数
 newObj.__proto__ = Con.prototype;
 // 2. 把新对象的原型指向构造函数的prototype
 var res = Con.apply(newObj, arguments)
 // 3. 把构造函数里的this指向新对象
 return typeof res === 'object' ? res : newObj;
 // 4. 返回新对象
}
var obj = newNew(constructorFunction, 'willian', 18)
console.log(obj.name, obj.age);//'willian', 18
console.log(obj.say())//Hello willian

得到和new 一样的答案,说明模拟成功。
你也可以F12 打开控制台试一试。
以上参考:

  1. mqyqingfeng/Blog#13
  2. https://blog.csdn.net/liwenfei123/article/details/80580883

function _new(Fn, ...arg) {
const obj = Object.create(Fn.prototype);
const obj1 = Fn.apply(obj, arg);
return obj1 instanceof Object ? obj1 : obj;
}

之前在github看另外一个人写的

  1. 创建新对象
  2. 新对象原型[[prototype]] = 构造函数prototype
  3. this 指向新对象
  4. 执行构造函数
  5. 如果构造函数返回非空对象,就返回这个对象引用,不然返回创建的新对象
function myNew(fn){
  var obj = {};
  Object.setPrototypeOf(obj,fn.prototype)
  obj.apply(fn)
  return obj
}
  1. 创建一个空对象,作为要返回的对象实例
  2. 将这个对象的原型,指向构造函数的prototype属性
  3. 将这个空对象赋值给函数内部的this关键字
  4. 开始执行构造函数内部的代码
function newF(obj) {
	const o = {},
		args = [].slice.call(arguments);
	o.__ptoto__ = obj.prototype;
	const res = obj.apply(o, args.slice(1));
	return typeof res === 'object' ? res : o
}
function customNew(fn) {
  const obj = {};
  return function (...args) {
    const res = fn.apply(obj, args);
    obj.__proto__ = Object.getPrototypeOf(fn);
    return typeof res === "object" ? res : obj
  }
}

function Foo(name) {
  this.name = name
}

console.log(customNew(Foo)(2));
/**
 * 模拟实现 new 操作符
 * @param  {Function} ctor [构造函数]
 * @return {Object|Function|Regex|Date|Error}      [返回结果]
 */
function newOperator(ctor){
    if(typeof ctor !== 'function'){
      throw 'newOperator function the first param must be a function';
    }
    // ES6 new.target 是指向构造函数
    newOperator.target = ctor;
    // 1.创建一个全新的对象,
    // 2.并且执行[[Prototype]]链接
    // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
    var newObj = Object.create(ctor.prototype);
    // ES5 arguments转成数组 当然也可以用ES6 [...arguments], Aarry.from(arguments);
    // 除去ctor构造函数的其余参数
    var argsArr = [].slice.call(arguments, 1);
    // 3.生成的新对象会绑定到函数调用的`this`。
    // 获取到ctor函数返回结果
    var ctorReturnResult = ctor.apply(newObj, argsArr);
    // 小结4 中这些类型中合并起来只有Object和Function两种类型 typeof null 也是'object'所以要不等于null,排除null
    var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
    var isFunction = typeof ctorReturnResult === 'function';
    if(isObject || isFunction){
        return ctorReturnResult;
    }
    // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表达式中的函数调用会自动返回这个新的对象。
    return newObj;
}

参考 https://juejin.im/post/5bde7c926fb9a049f66b8b52#heading-5

function _new(Fn,...args){
    // 创建对象
    let obj = {};
    // 对象的原型指向其构造函数对应的原型
    obj.proto = Fn.prototype;
    // 运行函数,且修改this指向实例
    obj1= Fn.apply(obj,args)
    // 返回对象
    return obj1 instanceof Object ? obj1:obj
}
0x3c commented

new 操作会执行以下几步:

  • 创建一个对象
  • 对象[[ProtoType]] 指向构造函数 prototype
  • this 指向该对象
  • 执行函数
  • 如果函数没有返回一个引用值则返回该对象
function newFn(C, ...args) {
  const o = Object.create(C.prototype);
  const o2 = C.apply(o, args);
  return o2 instanceof Object ? o2 : o;
}
function _new(target, ...res) {
    const obj = {};
    obj.__proto__ = target.prototype;
    const ret = target.apply(obj, res);
    return typeof ret === 'object' ? ret : obj;
}

对一个实例new做了以下4件事

  1. 生成一个空对象
  2. 将此对象的原型对象(proto)指向实例的原型(prototype)
  3. 将此对象的this指向实例
  4. 返回一个此对象

实现方法,楼上大佬的

function _new(fn, ...arg){
      let obj = Object.create(fn.prototype) 
      // Object.create 1.创建一个对象 2.将参数对象指向生成对象的原型(__proto__) 3.返回该对象
      let obj1 = fn.apply(obj, arg) 
      return obj1 instanceof Object ? obj1 : obj 
      // 不是很理解为什么要对象才返回,当我传Date的时候不就跟new 不一致了吗
}
        // new 的实现原理
        // 创建一个新对象
        // 将新对象的_proto_指向构造函数的prototype对象
        // 将构造函数的作用域赋值给新对象 (也就是this指向新对象)
        // 执行构造函数中的代码(为这个新对象添加属性)
        // 返回新的对象

        function _new(fn,...arg){
            //将创建的对象的原型指向构造函数的原型 ==> 新对象.__proto__(原型) == fn.prototype
            const obj = Object.create(fn.prototype);
            // 将this指向新对象 
            const ret = fn.apply(obj,arg);
            //判断返回值 (如果构造函数本身有返回值且是对象类型,就返回本身的返回值,如果没有才返回新对象)
            return ret instanceof Object ? ret : obj;
        }

new 的理解

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

new步骤

模拟new操作前,要先知道new操作是发生了什么,就拿new Object()举例:

  1. 创建一个新对象
  2. 把新对象的原型指向构造函数的prototype
  3. 把构造函数里的this指向新对象
  4. 返回这个新对象

构造函数:

先准备一个构造函数来new使用。

function constructorFunction(name, age){
  this.name = name;
  this.age = age;
}
constructorFunction.prototype.say = function(){
  return 'Hello '+ this.name
}

原生new:

var obj = new constructorFunction('willian', 18)
console.log(obj.name, obj.age);//'willian', 18
console.log(obj.say())//Hello willian

模拟new

模拟的new 暂称为newNew (囡..囡 哈哈~)
使用:newNew(constructor, arg1, arg2, ..) 第0个参数传入构造函数,1~n个参数是构造函数的形参。
使用上面的构造函数试一下:

function newNew(){
 var newObj = {}
 // 1. 创建一个新对象
 var Con = [].shift.call(arguments)
 // 得到构造函数
 newObj.__proto__ = Con.prototype;
 // 2. 把新对象的原型指向构造函数的prototype
 var res = Con.apply(newObj, arguments)
 // 3. 把构造函数里的this指向新对象
 return typeof res === 'object' ? res : newObj;
 // 4. 返回新对象
}
var obj = newNew(constructorFunction, 'willian', 18)
console.log(obj.name, obj.age);//'willian', 18
console.log(obj.say())//Hello willian

得到和new 一样的答案,说明模拟成功。
你也可以F12 打开控制台试一试。
以上参考:

  1. mqyqingfeng/Blog#13
  2. https://blog.csdn.net/liwenfei123/article/details/80580883

+1

new操作符做了四件事:初始化一个对象、将对象的原型指向构造函数的prototype、将构造函数的this指向这个对象、返回这个对象。

function objectFactory() {
    let object = new Object(), Constractor = [].shift.call(arguments);
    object.__proto__ = Constractor.prototype;
    Constractor.apply(object, arguments)
    return object;
}
m7yue commented
const myNew = (Ctor, ...args) => {
  let instance = {}
  Object.setPrototypeOf(instance, Ctor.prototype)
  const res = Ctor.apply(instance, args)

  // 如果构造方法已经return了一个对象,那么就返回该对象, 否则返回myNew创建的新对象 obj
  if (typeof res === "function" || (typeof res === "object" && res !== null)) {
    return res
  }
  return instance
}
function F(name) {
  this.name = name
}
const f = myNew(F, '7 yue')
console.log(f)
console.log(f instanceof F)
Reflect.construct(target, argumentsList[, newTarget]) // 新的思路
function create(fun, ...args) {
  const obj = Object.create(fun.prototype);
  obj.constructor = fun;
  const res = fun.call(obj, ...args);

  return isPrimitive(res) ? obj : res;
}

const isPrimitive = (val) => Object(val) !== val;

function Apple(name, count){
  this.name = name
  this.count = count
}

var a = create(Apple, 'apple', 3)

console.log(a)

// Apple { constructor: [Function: Apple], name: 'apple', count: 3 }
    function Dog(name, age) {
        this.name = name
        this.age = age
    }

    let dog1 = new Dog('旺财', 2)



    function myNew() {
        let [Fn, ...args] = arguments
        let obj = Object.create(Fn.prototype)
        Fn.call(obj, ...args)    //借用Fn这个构造函数 给obj添加属性
        return obj
    }

    let dog2 = myNew(Dog, '二哈', 5)
    console.log(dog2)

    function myNew2() {
        let obj = {};
        let [fn, ...arg] = arguments;
        obj.__proto__ = fn.prototype;
        fn.call(obj, ...arg);
        return obj;
    }

对一个实例new做了以下4件事

  1. 生成一个空对象
  2. 将此对象的原型对象(proto)指向实例的原型(prototype)
  3. 将此对象的this指向实例
  4. 返回一个此对象

实现方法,楼上大佬的

function _new(fn, ...arg){
      let obj = Object.create(fn.prototype) 
      // Object.create 1.创建一个对象 2.将参数对象指向生成对象的原型(__proto__) 3.返回该对象
      let obj1 = fn.apply(obj, arg) 
      return obj1 instanceof Object ? obj1 : obj 
      // 不是很理解为什么要对象才返回,当我传Date的时候不就跟new 不一致了吗
}

我的理解是这样的:

obj 和 obj1 原则上应该是同一个对象的引用。
obj1 是构造函数返回的值,但是通常我们不会在构造函数中写 return 语句。
所以当构造函数有返回值,且这个返回值是 Object 的实例,就优先返回这个值,否则就默认返回 obj。

实例化一个对象。

实例化对象

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例

实现

function _new(constructor, ...arg) {
    // 1.创建一个空对象
    let obj = {};
    // 2.为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象prototype
    obj.__proto__ = constructor.prototype;
    // 3.将步骤1新创建的对象作为this的上下文
    let res = constructor.apply(obj, arg);
    // 4.如果该函数没有返回对象,则返回this
    // return typeof res === "object" ? res : obj;
    return Object.prototype.toString.call(res) === "[object Object]"
        ? res
    : obj;
}

const Fun = function (name) {
    this.name = name;
};

console.log(_new(Fun, "Tom"));
  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

创建一个新对象
将构造函数中的this指向该对象
执行构造函数代码(给新对象添加属性和方法)
返回者着新对象
据此,我们初步实现模拟new的objectFactory方法:

function objectFactory(){
var obj=new Object();//创建一个新对象
var Constructor=[].shift.call(arguments);//取得该方法的第一个参数(并删除第一个参数),该参数是构造函数
obj.proto=Constructor.prototype;//将新对象的内部属性__proto__指向构造函数的原型,这样新对象就可以访问原型中的属性和方法
Constructor.apply(obj,arguments);//使用apply,将构造函数中的this指向新对象,这样新对象就可以访问构造函数中的属性和方法
return obj;//返回一个对象
}

ilss commented

可以加入一些内存方面的理解

new的背后操作:

  1. 创建一个空对象_this
  2. 将类A的实例属性作为_this的属性
  3. 将类A的原型prototype作为_this的隐式原型
  4. 将_this返回
function A () {
  this.a = 1;
}
A.b = 2;
A.prototype.c = 3;

let a = new A();

// 模仿 new A
function newA () {
  let _this = Object.create(A.prototype);
  A.call(_this);
  return _this;
}
console.log(newA() instanceof A);

// 1 创建一个空对象
// 2 将新对象的原型指向构造函数的prototype
// 3 将this指向该对象
// 4 返回这个对象

// 模拟new方法
function newFn(fn, ...args) {
  // 1 , 2
  var o = Object.create(fn.prototype)
  // 1
  // var o = {}
  // // 2
  // o.__proto__ = fn.prototype
  // 3
  obj = fn.apply(o, args)
  //4 
  return obj instanceof Object ? obj : o
}

function constructorFunction(name, age){
  this.name = name;
  this.age = age;
}
constructorFunction.prototype.say = function(){
  return 'Hello '+ this.name
}
var news1 = newFn(constructorFunction, 'xq', '28')
console.log(news1)
console.log(news1.say())
  1. 创建一个新的对象
  2. 指定对象的原型链
  3. 使得this指向空对象
  4. 如果函数没有返回对象, 表达式会返回对象
wyy-g commented

1、对new操作符的理解
创建一个obj空对象,
使obj的__proto__指向构造函数的prototype
修改构造函数的this指向,使构造函数的this指向obj并执行构造函数
返回新创建的obj对象
2、手动实现
function myNew(){
let obj = {},
fn = [].shift.call(arguments);
obj.proto = fn.prototype;
let res = fn.apply(obj, arguments);
return typeof res === 'object' ? res : obj;
}

 function myNew1(fn, ...arg){
      var obj = Object.create(fn.prototype),
            res = fn.apply(obj, arg);
      return typeof obj === 'object' ? res : obj;
 }

在JavaScript中, new 操作符用于创建一个对象实例。当你使用 new 关键字调用一个函数时,它会执行以下操作:

  1. 创建一个新的空对象。
  2. 将这个新对象的原型指向构造函数的原型。
  3. 将构造函数的作用域赋给新对象(因此 this 指向新对象)。
  4. 执行构造函数内部的代码,可以在这个过程中给新对象添加属性和方法。
  5. 如果构造函数没有显式地返回一个对象,则返回这个新对象。

下面是一个手动实现 new 方法的JavaScript示例:

function myNew(constructor, ...args) {
  // 创建一个新的空对象,并将其原型指向构造函数的原型
  const obj = Object.create(constructor.prototype);
  
  // 将构造函数的作用域赋给新对象
  const result = constructor.apply(obj, args);
  
  // 如果构造函数有显式地返回一个对象,则返回该对象;否则返回新对象
  return (typeof result === 'object' && result !== null) ? result : obj;
}

// 示例构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 使用自定义的myNew方法创建对象实例
const person = myNew(Person, 'John', 25);
console.log(person.name); // 输出: John
console.log(person.age); // 输出: 25
这个自定义的 myNew 方法模拟了 new 操作符的行为,它接受构造函数和参数,并返回一个新的对象实例。