很簡單的一個分享~
首先看了一下new操作符的使用:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.job = 'engineer';
Person.prototype.sayAge = function() {
return this.age;
}
const person1 = new Person('wang', 24);
console.log(typeof person1); // "object"
console.log(person1.name); // wang
console.log(person1.sayAge()); // 24
console.log(person1.job); // engineer
複製程式碼
Person
是一個建構函式,例項person1
能訪問到建構函式的屬性,以及Person
的原型物件Person.prototype
的屬性和方法。
其實我做的更像是已知new
的過程,來求證的。new
呼叫函式的過程在 ES5官方文件
在 函式定義
一節中做了定義:
- 建立一個ECMAScript的原生物件obj.
- 給obj設定原生物件的內部屬性;(和原型不同,內部屬性表示為
[[PropertyName]]
,兩個方括號包裹屬性名,並且屬性名大寫,比如[[Prototype]]
和[[Constructor]]
) - 設定obj的
[[Class]]
為Object
. - 設定obj的
[[Extensible]]
為true.([[Extensible]]
決定是否可以向物件新增屬性) - 將
proto
的值設定為F
的prototype
屬性值. - 如果
proto
是物件型別,則obj的內部屬性[[Prototype]]
的值為proto
;(進行原型鏈關聯,實現繼承的關鍵) - 如果
proto
不是物件型別,則設定obj的內部屬性[[Prototype]]
的值為內建建構函式Object
的[[prototype]]
的值.(函式的prototype
屬性可以被改寫,如果改成非物件型別,obj的[[prototype]]
就指向Object
的原型物件) - 呼叫函式
F
,將其返回值賦給result
;其中,F
執行時的實參傳遞給[[Construct]]
(即F
的本身)的引數,F
內部this
指向obj. - 如果
result
是Object
型別,返回result
. - 如果
F
返回的不是物件型別(第9步不成立),則返回建立的物件obj
.
很多地方都更簡潔的歸納為4個步驟:
- 建立一個新的空物件{}.
- 將建構函式的作用域賦值給這個新的對.(將建構函式的_prototype_賦值給物件的_proto_屬性)
- 執行建構函式的程式碼.
- 返回物件.
開始我的簡單的測試
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.job = 'engineer';
Person.prototype.sayAge = function() {
return this.age;
}
function newOperator(fn) {
const obj = {};
// 或者 const obj = new Object();
obj._proto_ = fn.prototype;
fn.apply(obj, arguments);
return obj;
}
const person1 = newOperator(Person, 'wang', 24);
console.log(person1.name);
console.log(person1.age);
複製程式碼
列印結果:
查查查原因:發現arguments
導致的。
arguments
是一個類陣列,slice
和shift
結合Function.call
可以處理。
重新改一下newOperator
函式
// example 1
function newOperator(fn, ...res) {
const obj = new Object();
obj._proto_ = fn.prototype;
fn.apply(obj, res);
return obj;
}
// example 2
function newOperator(fn) {
const obj = new Object();
obj._proto_ = fn.prototype;
fn.apply(obj, Array.slice.call(arguments, 1));
return obj;
}
// example 3
function newOperator() {
const obj = new Object();
const Construct = [].shift.call(arguments)
Construct.apply(obj, arguments);
return obj;
}
// example 4
function newOperator(fn) {
newOperator.target = fn; // ES6 new.target 是指向建構函式
const obj = Object.create(fn.prototype);
fn.apply(obj, [].slice.call(arguments, 1));
return obj;
}
複製程式碼
修正後的列印結果 -- 正確了:
如果我們在建構函式有處理返回值的情況:
function Person(name, age) {
this.name = name;
this.age = age;
return { name: 'yun' }
}
// newOperator 函式直接返回新的物件
const person1 = newOperator(Person, 'wang', 24);
console.log(person1.name); // wang
複製程式碼
上面所說的newOperator
是最簡單的情況,沒有考慮到之前說的建構函式顯示返回的情況,所以在稍微考慮一下返回結果的情況:
如果函式沒有返回物件型別Object
(包含Functoin
, Array
, Date
, RegExg
, Error
),那麼new
表示式中的函式呼叫會自動返回這個新的物件。
function newOperator() {
const obj = new Object();
const Construct = [].shift.call(arguments)
const result = Construct.apply(obj, arguments);
const isObject = typeof result === 'object' && result !== null;
const isFunction = typeof result === 'function';
if (isObject || isFunction) {
return result;
}
return obj;
}
複製程式碼
簡單測試一下:
function Person(name, age) {
this.name = name;
this.age = age;
return { name: 'yun' }
}
// newOperator 有判斷建構函式的返回值的情況
const person1 = newOperator(Person, 'wang', 24);
console.log(person1.name); // yun
複製程式碼
一點點說明
關於Object
型別的檢測,可以用typeOf
,也可以用instanceof
。
function newOperator() {
const obj = new Object();
const Construct = [].shift.call(arguments)
const result = Construct.apply(obj, arguments);
return (result instanceof Object) ? result : obj;
}
複製程式碼
但是instanceof
的工作原理是:在表示式x instanceof Foo
中,如果 Foo
的原型(即 Foo.prototype
)出現在x
的原型鏈中,則返回 true
,不然,返回false
。
例項建立之後重寫建構函式原型,例項指向的原型已經不是建構函式的新的原型了。
const Foo = function() {};
const o = new Foo();
o instanceof Foo; // true
// 重寫 Foo 原型
Foo.prototype = {};
o instanceof Foo; // false
複製程式碼
關於建立的新的物件:
//方式多種
const obj = {};
const obj = new Object();
const obj = Object.create();
複製程式碼
但是我還沒有理解他們是有什麼區別的嗎,在最後建立出來例項當中的引用。