Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

实现MyNew方法 #15

Closed
BetaSu opened this issue Mar 25, 2022 · 22 comments
Closed

实现MyNew方法 #15

BetaSu opened this issue Mar 25, 2022 · 22 comments
Labels
JS l1:JS 相关 动手 需要动手敲代码

Comments

@BetaSu
Copy link
Owner

BetaSu commented Mar 25, 2022

要实现的功能

我们可以使用new实例化一个构造函数,请根据实例化过程中构造函数内部工作流程,实现类似功能的MyNew方法。

代码示例

function MyNew(fn, ...args) {
  // 实现...
}

function Person(name, age) {
  this.name = name;
  this.age = age;
}
const kasong = MyNew(Person, 'KaSong', 18);
console.log(kasong.age); // 18

function Something(name) {
  this.name = name;
  return {name: 'something'};
}
const something = MyNew(Something, 'XiaoMing');
console.log(something.name); // something

问题补充

  1. 请阐述实例化过程中构造函数内部工作流程
  2. 请写出代码,并为代码的关键步骤增写注释
  3. 附带测试用例是加分项
  4. 不需要考虑异常发生
  5. 不过度设计,代码简洁优雅是加分项

最佳答案

Chorer的回答

理由:实现完备,且包含问题补充中的所有要素

答题同学须知

  • 答题规范:请在一次评论中完成作答,后续修改也请编辑该评论,而不是追加新的评论

  • 评选标准:最佳答案由围观同学的 👍 和卡颂共同决定

  • 评选时间:一般是当日18:00左右评选,如果问题发布当天回答数较少,问题悬赏金额可能增加,同时悬赏时间也会增加

围观同学须知

  • 对于你满意的答案,请不要吝惜你的 👍,这是评选最佳答案的依据

  • 非答题的评论会被删除,问题相关讨论请在赏金猎人群中进行

@BetaSu BetaSu added JS l1:JS 相关 动手 需要动手敲代码 悬赏10元 最佳答题者可以获得 10 元 labels Mar 25, 2022
@vfiee
Copy link

vfiee commented Mar 25, 2022

function PolyfillNew(fn, ...args) {
  // 如果不是函数报错
  if (typeof fn !== 'function') {
    throw TypeError(`Expected a function, but got a ${typeof fn}`)
  }
  // 创建新对象,并修改新对象的原型
  const o = Object.create(fn.prototype)
  // 以新对象作为 this 调用函数
  const r = fn.apply(o, args)
  const t = typeof r
  // 如果函数返回值为对象或函数则返回,否则返回新对象
  return (r !== null && t === 'object') || t === 'function' ? r : o
}

function Person(name, age) {
  this.name = name
  this.age = age
}
const kasong = PolyfillNew(Person, 'KaSong', 18)
console.log(kasong.age) // 18

function Something(name) {
  this.name = name
  return { name: 'something' }
}
const something = PolyfillNew(Something, 'XiaoMing')
console.log(something.name) // something

function Others(name) {
  this.name = name
  return function test() {
    console.log(this)
  }
}

const others = PolyfillNew(Others, 'others')
console.log(others)
// ƒ test() {
//  console.log(this)
// }

const others2 = new Others('others2')
console.log(others2)
// ƒ test() {
//  console.log(this)
// }

@vanstline
Copy link

 const MyNew = (constructor, ...args) => {
    const obj = Object.create(constructor.prototype)

    constructor.apply(obj, args)

    return obj
}

@Stan9726
Copy link

Stan9726 commented Mar 25, 2022

function myNew(constructor) {
  var _args = Array.prototype.slice.call(arguments, 1)
  var _this = {}

  _this.__proto__ = constructor.prototype

  var res = constructor.apply(_this, _args)

  return res !== null && typeof res === 'object' ? res : _this
}

@acmu
Copy link

acmu commented Mar 25, 2022

正好我最近在学习这块,就来尝试下~

new操作符究竟干了啥?

  1. 新建一个对象
  2. 把新对象的[[Prototype]]属性指向构造函数的prototype属性
  3. 用新对象的上下文执行构造函数
  4. 返回新对象
function myNew(constructorFunction, ...args) {
  const res = {}
  Object.setPrototypeOf(res, constructorFunction.prototype)
  constructorFunction.apply(res, args)
  return res
}

有人实现myNew还会看构造函数的返回值,如果是对象还有特殊操作,但我个人不知道这样做的意义,为什么要在构造函数中返回一个对象呢?构造函数就不应该有返回值的呀。所以我没有实现这块,也使得代码更简单了一些。

还有也不太懂题目中 myNew 为啥首字母大写,这也不是一个类呀 😂

@ninenan
Copy link

ninenan commented Mar 25, 2022

function MyNew (cto, ...rest) {
    let isFun = (cto) => typeof cto === 'function'
    let isObj = (param) => typeof param === 'object' && param !== null
    if (!isFun(cto)) return 'the first param must be a function'
    MyNew.target = cto 
    let obj = Object.create(cto.prototype)
    let res = cto.apply(obj, rest)

    if (isFun(res) || isObj(res)) return res
    return obj
}

@lingdeqingyuan
Copy link

function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype);

  let result = fn.apply(obj, args);

  return result instanceof Object ? result : obj;
}
`

@Picsong
Copy link

Picsong commented Mar 25, 2022

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

  1. 新生成了一个对象
  2. 链接到原型
  3. 改变this执行
  4. 返回新对象
function MyNew(ctor) {
    if(typeof ctor !== 'function') {
      throw 'ctor must be a function';
    }
    let obj =  Object.create(ctor.prototype); //1-2
    let res = ctor.apply(obj, arguments);//3
    return res instanceof Object ? res : obj;//4
};

@emotionl
Copy link

emotionl commented Mar 25, 2022

首先,我们先来看看 new 操作起了什么作用

// 构造函数 People (无显式返回值)
function People(name, age) {
  this.name = name
  this.age = age
}

People.prototype.printInfo = function () {
  return `My name is ${this.name} and ${this.age}`
}

// 构造函数 Student (有显式返回值,且返回值为对象类型)
function Student(name) {
  this.name = name
  return { ps: 'balabala' }
}

const p1 = new People('Jack', 17)
const s1 = new Student('David')
console.log(p1.name)        // Jack
console.log(p1.age)         // 17
console.log(p1.printInfo()) // My name is Jack and 17
console.log(s1)             // {ps: "balabala"}

可以看出 new 关键字大体上有一下几个作用

  • 构造函数无显式返回值时(例如 Person),通过 new 操作得到的 p1 可以访问到构造函数 Person 里的属性和 Person.prototype 里的属性
  • 构造函数有显式返回值并且返回值为对象类型时(例如 Student),通过 new 操作得到的 s1 是构造函数的显示返回值 {ps: "balabala"}

基于以上的内容,我们来实现一个 new 操作

  1. 首先创建一个空对象,这个对象将会作为执行 new 构造函数() 之后,返回的对象实例
  2. 将上面创建的空对象的原型(__proto__),指向构造函数的 prototype 属性
  3. 将这个空对象赋值给构造函数内部的 this,并执行构造函数逻辑
  4. 根据构造函数执行逻辑,返回第一步创建的对象或者构造函数的显式返回值
const MyNew = (Con, ...args) => {
  const obj = Object.create(Con.prototype)
  const result = Con.apply(obj, args)
  return typeof result === 'object' ? result : obj
}

@tangdingga1
Copy link

tangdingga1 commented Mar 25, 2022

/**
 * @param {function} fn target function
 * @param {any} fnArgs function rest arguments
 * @returns {Object}
 */
function MyNew(fn, ...fnArgs) {
  const PROTOTYPE_OBJECT = Object.create(null);
  if (typeof fn !== 'function') {
    return PROTOTYPE_OBJECT;
  }
  try {
    const RESULT = fn.apply(PROTOTYPE_OBJECT, fnArgs);
    return RESULT instanceof Object ? RESULT : PROTOTYPE_OBJECT;
  } catch (error) {
    return PROTOTYPE_OBJECT;
  }
}

@andyyxw
Copy link

andyyxw commented Mar 25, 2022

实例化过程中构造函数内部工作流程:

  1. 创建新对象,使用构造函数的原型作为新对象的原型对象
  2. 将新的对象作为函数的上下文对象(this)
  3. 执行函数中的代码
  4. 将新建的对象返回

实现:

function MyNew(fn, ...args) {
  const result = Object.create(fn.prototype) // 创建新对象,使用构造函数的原型作为新对象的原型对象
  const obj = fn.apply(result, args)
  // 引用类型直接返回
  return (typeof obj === 'object' && obj !== null || typeof obj === 'function') ? obj : result
}

单测:

describe('MyNew', () => {
  it('should be defined', () => {
    expect(MyNew).toBeDefined()
  })
  it('should work on common constructor', () => {
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    const result = MyNew(Person, 'KaSong', 18)
    expect(result.age).toBe(18)
  })
  it('should work on constructor return object', () => {
    function Something(name) {
      this.name = name
      return { name: 'something' }
    }
    const result = MyNew(Something, 'XiaoMing')
    expect(result.name).toBe('something')
  })
})

@fxwing
Copy link

fxwing commented Mar 25, 2022

function myNew(fn, ...args) {
  if (typeof fn !== 'function') throw new Error('fn nust be a function');
  const obj = Object.create(fn.prototype);
  const result = fn.apply(obj, args);
  return (typeof result === 'object' && result !== null) || typeof result === 'function' ? result : obj;
}

@ZHENGGEGE
Copy link

ZHENGGEGE commented Mar 25, 2022

function myNew(fn, ...args) {
    const obj = Object.create(fn.prototype)
    let result = fn.apply(obj, args)
    return result instanceof Object ? result : obj
}

测试:
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function () {
    console.log(this.name)
}

let p = mynew(Person, "su", 123)
console.log(p) // Person {name: "su", age: 123}
p.say()  // "su"

@NoBey
Copy link

NoBey commented Mar 25, 2022

来个函数式的

const mynew = (fn, ...arg) => 
  (((ctx) => 
    (result => typeof result === 'object' ? result : ctx)
    (fn.apply(ctx, arg)))
  ({ __proto__: fn.prototype }))


function mynew(fn, ...arg){
  const ctx = { __proto__: fn.prototype }
  const result = fn.apply(ctx, arg)
  return typeof result === 'object' ? result : ctx
}

@wuwenbang
Copy link

function myNew(fn, ...args) {
  // 创建临时对象,同时绑定原型
  const obj = Object.create(fn.prototype)
  // 执行构造函数
  const result = fn.apply(obj, args);
  // 返回构造结果或临时对象
  return typeof result === 'object' ? result : obj;
}

@hanpei
Copy link

hanpei commented Mar 25, 2022

/** MDN
 * new 运算符
 *   new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
 *
 * new 关键字会进行如下的操作:
 *   1. 创建一个空的简单JavaScript对象(即{});
 *   2. 为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
 *   3. 将步骤1新创建的对象作为this的上下文 ;
 *   4. 如果该函数没有返回对象,则返回this。
 */

function MyNew(fn, ...args) {
  //1
  const obj = Object.create(null);
  //2
  Object.setPrototypeOf(obj, fn.prototype)
  //3
  const ret = fn.apply(obj, args);
  //4
  return ret instanceof Object ? ret : obj;
}

@RJM1996
Copy link

RJM1996 commented Mar 25, 2022

new 关键字

js 中定义一个类

// 定义构造函数
function Person(name) {
  console.log('constructor')
  // 将构造函数的this指向新对象
  this.name = name
}

// 定义类的属性
Person.prototype.say = function () {
  console.log('My name is', this.name)
}

// 创建新对象
const p1 = new Person('tom')
p1.say()

在调用 new 时, 主要做了 4 件事:

  1. 创建一个新的空对象

  2. 将空对象的 __proto__ 指向构造函数的 prototype

  3. 执行构造函数, 并将新创建的空对象绑定为构造函数的 this 对象

  4. 如果构造函数有返回一个对象,则返回这个对象,否则返回新创建的那个对象

根据以上规则,我们可以模拟实现一个 new 函数

function myNew(constructorFn, ...args) {
  // 1. 创建一个空对象
  const obj = {}
  // 2. 将空对象的__proto__指向constructor的prototype
  obj.__proto__ = constructorFn.prototype
  // 3. 执行 constructor, 并将新对象绑定为constructor的this对象
  const res = constructorFn.apply(obj, args)
  // 4. 如果构造函数有返回值则返回res,否则返回新对象
  return typeof res === 'object' ? res : obj
}

const p1 = myNew(Person, 'jack')
p1.say()

@Chorer
Copy link

Chorer commented Mar 25, 2022

内部工作流程:

  1. 检测 new 的目标是不是非函数,如果是非函数,抛出错误
  2. 修改 new 的 target 属性,使之指向构造函数
  3. 新建一个空的实例对象。注意不能使用 Object.create 创建,否则当构造函数原型为 null 的时候,实例对象隐式原型也为 null,但根据 new 的规范,这里不是这样的。具体见 4。
  4. 检测构造函数原型是否为 null,如果不是,则将其作为实例对象的隐式原型,否则将 Object 的原型作为实例对象的隐式原型
  5. 执行构造函数,将其 this 指向实例对象,同时传入参数
  6. 获得构造函数返回值,判断是不是对象,如果是对象,则作为 new 的返回值,否则将实例对象作为 new 的返回值

代码实现:

function myNew(Fn,...args){
   // 检测异常
    if(typeof Fn != 'function'){
        throw new TypeError(Fn + 'is not a constructor')
    }
  // 修改 target 属性
    myNew.target = Fn
  // 创建空的实例对象
    const instance = {}
    // 检测构造函数原型是不是对象
    instance.__proto__ = Fn.prototype instanceof Object ? Fn.prototype : Object.prototype 
   // 执行构造函数
    const returnValue = Fn.call(instance,...args)
  // 决定 new 的返回值
    return returnValue instanceof Object ? returnValue : instance
}

测试:

// 构造函数原型不为空的情况
function Student(name,age){
    this.name = name
    this.age = age
}
const student1 = myNew(Student,'Jack',20)
const student2 = new Student('Jack',20)
console.log(student1)                     // {name:'Jack',age:20}
console.log(student2)                     // {name:'Jack',age:20}

// 构造函数原型为空的情况
function Fn(){}
Fn.prototype = null
const fn1 = myNew(Fn)
const fn2 = new Fn()
Object.getPrototypeOf(fn1) === Object.prototype           // true
Object.getPrototypeOf(fn2) === Object.prototype           // true

@w615
Copy link

w615 commented Mar 25, 2022

function myNew(argument) {
let content = {} //生成一个对象
let args = Array.prototype.slice.call(argument,0) //参数
let constructor = args.shift()//获得构造函数
content.proto = constructor.prototype //空对象原型指向这个构造函数 content.proto
constructor.apply(content,args)//构造函数指向这个对象,值为args
return content //返回新对象
}

@xietingyang
Copy link

实现new 创建实例的步骤:
1、创建一个新对象
2、将这个新对象的原型关联到该构造函数的prototype上
3、判断该构造函数的返回值是不是引用类型,如果是则直接返回该值,反之,返回空对象

function MyNew(targetFn, ...arg) {
const obj = {};
obj.proto = targetFn.protoType;
const res = targetFn.apply(obj, [...arg]);
return typeof res === 'object' ? res : obj;
}
//测试用例
function Foo(name) {
this.name = name;
};
Foo.prototype.changeName = (name) => { this.name = name };
const instance = MyNew(Foo, '张三');
console.log(instance)

@fanerge
Copy link

fanerge commented Mar 25, 2022

思路(根据 new 的执行原理来mock)

  1. 调用方式 new Person('name', 'age'); MyNew 则接收多个参数,第一个为构造函数,剩余为构造函数所需的参数。
  2. 创建一个对象,并将对象的原型链指向构造函数的原型(标注1)使该对象具有构造函数原型上的方法。
  3. 构造函数执行需执行this为新建的对象(标注2)。
  4. 返回值默认是该构造函数实例化的对象,但如果该构造函数强制返回了非引用类型则需要返回前面新建的对象 (标注3)。

代码实现 (测试用例均通过)

function MyNew(fn, ...args) {
 // 标注1
  let obj = Object.create(fn.prototype);
 // 标注2
  let res = fn.apply(obj, args);
  // 标注3
  if ((typeof res === "object" && res !== null) || typeof res === "function") {
    return res;
  }
  return obj;
}

@BetaSu BetaSu added 悬赏15元 最佳答题者可以获得 15 元 and removed 悬赏10元 最佳答题者可以获得 10 元 labels Mar 25, 2022
@Travelguest
Copy link

原理:

  • new 首先会在内存中创建一个空对象,并将这个空对象的__proto__指向构造函数的prototype,从而继承构造函数原型上的方法()
  • 将this指向刚刚创建的新对象,并执行构造函数中的代码,以获取私有属性和方法
  • 如果构造函数返回了一个对象res,就将该返回值对象res返回,如果返回的不是对象,则将创建的新对象返回
function MyNew(Fn, ...args) {  //将不定数量的参数表示为一个数组
  //1.创建空对象obj,并将obj._proto_指向构造函数Fn.prototype
  const obj = Object.create(Fn.prototype);
  //2.将构造函数Fn的this指向obj,并执行Fn代码,获得返回值
  const res = Fn.apply(obj, args); //args是数组['dy','男']
  //3.根据返回值类型,决定到底返回谁
  return res instanceof Object ? res : obj;
}

//构造函数
function Person(name, sex) {
  this.name = name;
  this.sex = sex;
}
Person.prototype.getName = function () {
  return this.name;
};
Person.getSex = 'bukeyisese'

const ymy = new Person("ymy", "女");
const dy = MyNew(Person, "dy", "男");
console.log(ymy);
console.log(dy);
console.log(dy.getName());
//共享原型上的方法
console.log(ymy.getName === dy.getName); //true
  • 剩余参数:如果函数的最后一个命名参数以...为前缀,则它将成为一个由剩余参数组成的真数组。
  • Object.create()方法创建一个新对象,并将新创建的对象的__proto__指向传入的对象的原型。

@BetaSu BetaSu removed the 悬赏15元 最佳答题者可以获得 15 元 label Mar 25, 2022
@BetaSu BetaSu closed this as completed Mar 25, 2022
@toFrankie
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS l1:JS 相关 动手 需要动手敲代码
Projects
None yet
Development

No branches or pull requests