阅读 56

JavaScript-从使用new到手写new

new

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

在手写 new 之前,我们来看看 new 实现了那些功能。

Demo:

// 狗的类
function Dog (name, age) { 
	this.name = name;
  this.age = age;
  this.habit = "Ball";
}

Dog.prototype.weight = 50;
Dog.prototype.run = function() {
  console.log("I am" + this.name + ", i am run...");
};

// 一条白色的狗
var whiteDog = new Dog('xiao bai', '2');

console.log(whiteDog.name); // xiao bai
console.log(whiteDog.age); // 2
console.log(whiteDog.weight); // 50
whiteDog.run(); // I am xiao bai, i am run...
复制代码

从上面的Demo中可以看出来,实例 whiteDog 可以:

  1. 访问到 Dog 构造函数中的属性;
  2. 访问到 Dog.prototype 中的属性;

所以。我们可以简单的实现一个 new。

因为new是关键字,我们无法覆盖,所以我们用new2来表示我们的new

使用的时候呢如下:

function Dog(...){...};
// 使用new
var whiteDog = new Dog(...);
// 使用new2
var whiteDog = new2(Dog,...);
复制代码

初步实现

分析:

因为 new 的结果会返回一个对象,所以在模拟实现它的时候,我们也要建立一个新的对象,假设这个对象叫 obj。因为 obj 会具有构造函数(这里指Dog)里面的属性。我们使用 Dog.allpy(obj, arguments) 来给 obj 添加新的属性。

补充两点:

  1. __proto__constructor 属性是对象所独有的;
  2. prototype 属性是函数所独有的。

第一版代码

function new2() {
  var obj = {};

  var Constructor = [].shift.call(arguments);
  obj.__proto__ = Constructor.prototype;
  Constructor.apply(obj, arguments);
  return obj;
}
复制代码

其实上面的代码已经可以生效了。不信你可以复制下面的代码运行一下。

// 狗的类
function Dog (name, age) { 
	this.name = name;
  this.age = age;
  this.habit = "Ball";
}

Dog.prototype.weight = 50;
Dog.prototype.run = function() {
  console.log("I am" + this.name + ", i am run...");
};

// 一条白色的狗的实例
var whiteDog = new Dog('xiao bai', '2');

// console.log(whiteDog.name); // xiao bai
// console.log(whiteDog.age); // 2
// console.log(whiteDog.weight); // 50
// whiteDog.run(); // I am xiao bai, i am run...

function new2() {
  var obj = {};
  // 这里通过定制传参规则,获取第一个参数,即我们需要继承的对象。
  // 同时因为使用的数组的 shift 方法,最后的 arguments 是少了第一项的
  var Constructor = [].shift.call(arguments);
  obj.__proto__ = Constructor.prototype;
  Constructor.apply(obj, arguments);
  return obj;
}

var blackDog = new2(Dog, "xiao hei", '1');
console.log(blackDog.name); // xiao hei
复制代码

但是有一个问题。

什么问题呢?那就是如果我的构造函数不是默认的返回值而是自己添加了一个返回值呢?

Demo2

function Dog (name, age) {
  this.weight = 60;
  this.age = age;
  return {
    name: name,
    habit: 'Games'
  }
}
var dog = new Dog('Kevin', '18');
console.log(dog.name) // Kevin
console.log(dog.habit) // Games
console.log(dog.weight) // undefined
console.log(dog.age) // undefined
复制代码

在这个例子中,构造函数返回了一个对象,在实例 person 中只能访问返回的对象中的属性。

而且还要注意一点,在这里我们是返回了一个对象,假如我们只是返回一个 基本类型 的值呢?

Demo3:

function Dog (name, age) {
  this.weight = 60;
  this.age = age;
  return "Kevin" // 你可以试试其他基本类型,包括ES6中的 Symbol();
}
var dog = new Dog('Kevin', '18');
console.log(dog.name) // undefined
console.log(dog.habit) // undefined
console.log(dog.weight) // 60
console.log(dog.age) // 18
复制代码

结果完全颠倒过来,这次尽管有返回值,但是相当于没有返回值进行处理。

所以我们还需要判断返回的值是不是一个对象,如果是一个对象,我们就返回这个对象,如果没有,我们该返回什么就返回什么。

所以我们有了第二版的 new2

// 第二版的代码
function new2() {
    var obj = {},
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    var res= Constructor.apply(obj, arguments);
    return typeof res === 'object' ? res: obj;
};
复制代码