JavaScript 模擬new的實現

mst發表於2019-03-29

JavaScript 模擬new的實現
首先一句話解釋一下new:

new 可以實現一個物件繼承建構函式的屬性以及方法

舉個例子:

function Parent(name,age){
  this.name = name;
  this.age = age;
}

Parent.prototype.sayName = function(){
  console.log(`my name is ${this.name}`)
}

let child = new Parent('js',18);

console.log(child.name);    // js
console.log(child.age);     // 18
child.sayName();            // my name is js
複製程式碼

從這個案例,可以看到例項child

1.繼承了建構函式裡面的屬性

2.可以訪問到建構函式prototype中的屬性

通過下面這個圖來直觀感受一下,這裡涉及到js原形以及原型鏈,先提前補腦一下

JavaScript 模擬new的實現
proto 指的是例項物件的原形鏈,每個物件都有這個屬性,它只想建構函式的原形;而prototype屬性是函式物件才有的屬性,那什麼是函式物件?就是凡是通過new functioin()出來的就是函式物件。

child.constructor === Parent //true

child._proto === Parent.prototype //true

Parent.prototype.constructor === Parent //true
複製程式碼

接下來按照剛剛的例子結合上面的這張圖片來實現new 內部是怎麼的實現過程:

function newFactory(){
	let obj = new Object(),
	    context = Array.prototype.shift.call(arguments); 
	    
	 obj.__proto__ = context.prototype;
	context.apply(obj,arguments);
	return obj;
}
複製程式碼
  1. 首先通過new Object()新建一個物件 obj;
  2. 取出第一個引數,就是我們要傳入的建構函式。因為 shift 會修改原陣列,所以 arguments 會被去除第一個引數.
  3. 講obj的原形指向建構函式
  4. 使用apply改變建構函式的this指向,這樣obj就可以訪問到建構函式的屬性

接下來測試一下

function Parent(name,age){
  this.name = name;
  this.age = age;
}

Parent.prototype.sayName = function(){
  console.log(`my name is ${this.name}`)
}

function newFactory(){
  let obj = new Object(),
      context = Array.prototype.shift.call(arguments); 
	    
  obj.__proto__ = context.prototype;
  context.apply(obj,arguments);
  return obj;
}
let child = newFactory(Parent,'js','18')

console.log(child.name);    // js
console.log(child.age);     // 18

child.sayName();            // my name is js
複製程式碼

到這一步我們已經完成了80%,因為還有種情況我們沒有考慮到,當建構函式有返回值的時候,例項化的物件會怎麼?

function Parent(name,age){
  this.age = age;
  this.sex = 'boy'
  return {
     name:name,
     address:'china'
  }
}

var child = new Parent('js','18')

console.log(child.name);    // js
console.log(child.age);     // undefined
console.log(child.sex);    // undefined
console.log(child.address);     // china
複製程式碼

通過上面這個案例,可以看出當建構函式返回了一個物件的化,例項child只能訪問到返回物件的屬性,那如果返回的是基本型別呢?

function Parent(name,age){
  this.age = age;
  this.sex = 'boy'
  return 'china';
}

var child = new Parent('js','18')

console.log(child.name);    // undefined
console.log(child.age);     // 18
console.log(child.sex);    // boy
console.log(child.address);     // undefined
複製程式碼

從這個案例可以看出來當建構函式返回值是基本型別的時候,跟沒有返回值一樣。

終極版 四大步驟:

1、建立一個空物件,並且 this 變數引用該物件,// let obj = new Object();

2、繼承了函式的原型。// obj.proto = func.prototype;

3、屬性和方法被加入到 this 引用的物件中。並執行了該函式func// func.call(target);

4、新建立的物件由 this 所引用,並且最後隱式的返回 this 。// 如果func.call(target)返回的res是個物件或者function 就返回它

function newFactory(){
  let obj = new Object(),
      context = Array.prototype.shift.call(arguments); 
    
   obj.__proto__ = context.prototype;
   let res = context.apply(obj,arguments);
 
   if ((typeof res === "object" || typeof res === "function") && res !== null) {
      return res;
  }
  return obj;
}

複製程式碼

相關文章