在日常使用new時,我們很清楚它的作用。
準備工作
我們先建立一個Person
類,他接受兩個引數name
姓名和age
年齡:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function(){
console.log('我叫' + this.name + ', 今年' + this.age + '歲了');
}
複製程式碼
new 的使用
我們先用new例項化一個person,並列印出來,看看結構。
var person = new Person('小明', 20);
console.log(person); // Person {name: "小明", age: 20}
person.sayHello(); // 我叫小明, 今年20歲了
複製程式碼
最終person的結果是一個object
:
模擬過程
結合Person方法,我們容易發現,彷彿有一個object替代了this的位置,執行了賦值操作,輸出了最後的結果。
1、替代this賦值
思考替代過程:
// 1、建立了一個物件
var result = {};
// 2、物件代替了this的位置,執行了賦值
{
result.name = "小明";
result.age = 20;
}
// 3、輸出 {name: "小明", age: 20}
return result;
複製程式碼
那麼問題來了,我該如何將這個result替代this的位置呢?
這就用到了call
或者apply
:
var result = {};
Person.call(result, '小明', 20);
result; // {name: "小明", age: 20}
複製程式碼
這樣我們就完成了第一步,你可以在console控制檯中嘗試一下!
接下來就是處理原型部分了。
2、原型移植
這就很簡單了,我們有很多辦法:
//方案1
result.__proto__ = Person.prototype; //有一定副作用(可列舉)
//方案2
Object.setPrototypeOf(result, Person.prototype);
//方案3
result = Object.create(Person.prototype);
複製程式碼
3、初步結果
綜上我們容易整理出這樣的結果:
function likeNew(fn){
//我們先完成原型移植,以免建構函式中呼叫了原型方法。
var result = Object.create(fn.prototype);
var args = [].slice.call(arguments,1);
fn.apply(result, args);
return result
}
var person = likeNew(Person, '小明', 20);
console.log(person); // Person {name: "小明", age: 20}
person.sayHello(); // 我叫小明, 今年20歲了
複製程式碼
雖然我們按照思路是先建立物件->執行->處理原型。
但是實際上正確的順序是 建立包含對應原型的物件->執行。
問題
如果被例項化的方法如果本身包含返回值,new的結果會是什麼呢?
function Person(name, age){
this.name = name;
this.age = age;
return name
}
var person = new Person('張三', 20);
console.log(person); // ?
var person1 = new Person(['張三'], 20);
console.log(person); // ?
複製程式碼
通過嘗試,輸出結果分別為Person {name: '張三', age: 20}
、['張三']
。
為什麼會產生完全不同的結果呢?
猜想:方法返回值的型別決定例項化後的結果
。
1、基本型別
js中的基本型別有number、string、boolean、undefined、null、symbol(es6)
共6種。
function Test(value){
this.value = value;
return value;
}
//number
var number = new Test(123);
console.log(number); // Test {value: 123}
//string
var string = new Test('abc');
console.log(string); // Test {value: 'abc'}
//boolean
var boolean = new Test(true);
console.log(boolean); // Test {value: true}
//undefined
var Undefined = new Test();
console.log(Undefined); // Test {value: undefined}
//null
var Null = new Test(null);
console.log(Null); // Test {value: null}
//symbol
var symbol = new Test(Symbol('key'));
console.log(symbol); // Test {value: Symbol(key)}
複製程式碼
上述例子所有返回值均為例項化後的物件,由此可見,所有基本型別返回的都是正常的。
2、引用型別
js中的引用型別有 object、array、function
。我們接著上面的Test類繼續建立物件:
//object
var object = new Test({}});
console.log(object); // {}
//array
var array = new Test([]);
console.log(array); // []
//function
var functions = new Test(function(){});
console.log(functions); // function(){}
//特殊的number
var number = new Test(new Number(1));
console.log(number); // Number {1}
console.log(typeof number); // object。
複製程式碼
可見,方法的返回值若為引用型別,new操作符就“失效”了。
那他真的失效了嗎?讓我們看看方法內部執行的過程:
function Test(value){
this.value = value;
console.log(this);
return [1,2,3];
}
var result = new Test(1); // Test {value: 1}
console.log(result);// [1, 2, 3]
複製程式碼
由此可見,this在Test的例項化過程中,確實被建立了,只不過由於Test本身的返回值為引用型別,所以例項化後的結果被其替換
了。
最後的整理
根據以上的推論,再次完善了likeNew:
function likeNew(fn){
var result = Object.create(fn.prototype);
var args = [].slice.call(arguments,1);
var fnResult = fn.apply(result, args);
if(typeof fnResult === 'object' || typeof fnResult === 'function' && fnResult !== null){
return fnResult
}
return result
}
var person = likeNew(Person, '小明', 20);
console.log(person); // Person {name: "小明", age: 20}
person.sayHello(); // 我叫小明, 今年20歲了
//原始型別 number
var number = likeNew(Test, 1);
console.log(number); // Test {value: 1}
var Null = likeNew(Test, null);
console.log(Null); // Test {value: null}
//引用型別
var object = likeNew(Test, {});
console.log(object); // {}
var numberObject = likeNew(Test, new Number(1));
console.log(numberObject); // Number{1}
複製程式碼
總結:
1、new操作符在進行例項化時,首先會建立一個包含指定__proto__的物件,再帶入方法中執行,並選擇性輸出此物件。
2、被操作的方法的返回值若為引用型別,則會替換原本例項化的結果。
以上如有不當請指出。