继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

jsliang :jsliang 求职系列 - 11 - 手写 new

慕少0118308
关注TA
已关注
手记 2
粉丝 0
获赞 1

  面试官:来手写一个 new。

  看到这道题,不要急不要慌,jsliang 逐步深入带你搞一个。

  我们先看一个案例:

  this.name = 'jsliang';

  let Foo = function() {

  this.name = 'zhazhaliang';

  }

  let foo = new Foo();

  console.log(foo.name); // 输出啥?

  console.log(window.name); // 输出啥?

  复制代码

  上面代码输出啥?

  答案是:

  zhazhaliang

  jsliang

  那么,这道题中的 new 做了啥呢?我们深入研究研究。

  三 原生 new

  返回目录

  先看一下原生 new 的一个案例,思考下 new 能做啥:

  function Person( name, age){

  this.name = name;

  this.age = age;

  // return; // 返回 this

  // return null; // 返回 this

  // return this; // 返回 this

  // return false; // 返回 this

  // return 'hello world'; // 返回 this

  // return 2; // 返回 this

  // return []; // 返回 新建的 [], person.name = undefined

  // return function(){}; // 返回 新建的 function,抛弃 this, person.name = undefined

  // return new Boolean(false); // 返回 新建的 boolean,抛弃 this, person.name = undefined

  // return new String('hello world'); // 返回 新建的 string,抛弃 this, person.name = undefined

  // return new Number(32); // 返回 新的 number,抛弃 this, person.name = undefined

  }

  var person = new Person("jsliang", 25);

  console.log(person); // Person {name: "jsliang", age: 25}

  复制代码

  四 手写 new

  返回目录

  那么我们开始理解 new 里面的内容,看看怎么手写一个 new。

  4.1 简单实现

  返回目录

  那么我们先简单地用 3 行代码写一个 new 试试:

  首先创建一个空对象 tempObj = {}。

  接着调用 Foo.apply 方法,将 tempObj 作为 apply 方法的参数,这样当 Foo 的执行上下文创建时,它的 this 就指向 tempObj 对象。

  然后执行 Foo 函数,此时的 Foo 函数执行上下文中的 this 指向了 tempObj 对象。

  最后返回 tempObj 对象。

  function myNew(func, ...args) {

  const tempObj = {};

  func.apply(tempObj, args);

  return tempObj;

  }

  this.name = 'jsliang';

  let Foo = function(name, age) {

  this.name = name;

  this.age = age;

  }

  let foo = myNew(Foo, 'zhazhaliang', 25);

  console.log(foo.name); // 输出啥?

  console.log(foo.age); // 输出啥?

  console.log(window.name); // 输出啥?

  复制代码

  如上,我们可以看到此时 this 是属于 tempObj 的,绑定到 foo 上去了,从而获取到:

  zhazhaliang

  25

  jsliang

  wow,是不是豁然开朗,简单 new 的实现只需要 3 行代码!

  4.2 完善版本

  返回目录

  OK,我们写好了简单版的,让我们看看复杂版需要的条件吧:

  第一个参数必须是个函数。const person = new Person(),但是我们搞不了原汁原味的,那就变成 const person = myNew(Person)。

  原型链继承。我们新建一个对象 obj,这个 obj 的 __proto__ 指向 func 的原型 prototype,即 obj.__proto__ === func.prototype。

  修正 this 指向。通过 apply 绑定 obj 和 func 的关系,并且将参数作为一个数组传递进去(方法体定义已经将剩余参数解构为数组)

  判断构造函数是否返回 Object 或者 Function。typeof 判断 object 需要排除 null,因为 typeof null === object。

  非函数和对象返回新创建的对象,否则返回构造函数的 return 值。

  下面贴一下最终实现:

  function myNew(func, ...args) {

  // 1. 判断方法体

  if (typeof func !== 'function') {

  throw '第一个参数必须是方法体';

  }

  // 2. 创建新对象

  const obj = {};

  // 3. 这个对象的 __proto__ 指向 func 这个类的原型对象

  // 即实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性

  obj.__proto__ = Object.create(func.prototype);

  // 为了兼容 IE 可以让步骤 2 和 步骤 3 合并

  // const obj = Object.create(func.prototype);

  // 4. 通过 apply 绑定 this 执行并且获取运行后的结果

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

  // 5. 如果构造函数返回的结果是引用数据类型,则返回运行后的结果

  // 否则返回新创建的 obj

  const isObject = typeof result === 'object' && result !== null;

  const isFunction = typeof result === 'function';

  return isObject || isFunction ? result : obj;

  }

  // 测试

  function Person(name) {

  this.name = name;

  return function() { // 用来测试第 5 点

  console.log('返回引用数据类型');

  };

  }

  // 用来测试第 2 点和第 3 点

  Person.prototype.sayName = function() {

  console.log(`My name is ${this.name}`);

  }

  const me = myNew(Person, 'jsliang'); // 用来测试第 4 点

  me.sayName(); // My name is jsliang

  console.log(me); // Person {name: 'jsliang'}

  // 用来测试第 1 点

  // const you = myNew({ name: 'jsliang' }, 'jsliang'); // 报错:第一个参数必须是方法体

  复制代码

  这样,我们就了解 new 是啥东东,碰到手写 new 的时候就不慌啦!


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP