typeof
ECMAScript 有 5 種原始類型(primitive type),即 Undefined、Null、Boolean、Number 和 String。我們都知道可以使用typeof運算符求得一個變量的類型,但是對引用類型變量卻只會返回object,也就是說typeof只能正確識別基本類型值變量。
var a = "abc";
console.log(typeof a); // "string"
var b = 123;
console.log(typeof b); // "number"
var c = true;
console.log(typeof c); // "boolean"
var d = null;
console.log(typeof d); // "object"
var f = undefined;
console.log(typeof f); // "undefined"
var g;
console.log(typeof g); // "undefined"
console.log(typeof x); // "undefined"
您也許會問,為什么 typeof 運算符對于 null 值會返回 "object"。這實際上是 JavaScript 最初實現(xiàn)中的一個錯誤,然后被 ECMAScript 沿用了。現(xiàn)在,null 被認為是對象的占位符,從而解釋了這一矛盾,但從技術上來說,它仍然是原始值。
最后一個比較奇怪,typeof一個不存在的變量x居然返回了"object"而不是"undefined"。
我們在來如下代碼:
var a = function() { };
console.log(typeof a); // "function"
var b = [1,2,3];
console.log(typeof b); // "object"
var c = { };
console.log(typeof c); // "object"
對于數組和對象都返回"object",因此我們日常開發(fā)中一個常見需求就是如何判斷變量是數組還是對象。
類型判斷
類型判斷,一般就是判斷是否是數組,是否是空對象。這是針對這個需求,我日常用過或見過的判斷方法
判斷是否是數組
有數組:var a = [1,2,3,4,5];
方法一:
toString.call(a); // "[object Array]"
方法二:
a instanceof Array; //true
方法三:
a.constructor == Array; //true
第一種方法比較通用,也就是Object.prototype.toString.call(a)的簡寫。
instanceof和constructor判斷的變量,必須在當前頁面聲明的,比如,一個頁面(父頁面)有一個框架,框架中引用了一個頁面(子頁面),在子頁面中聲明了一個a,并將其賦值給父頁面的一個變量,這時判斷該變量,Array == object.constructor會返回false;
var a = [1,2,3,4,5];
console.log(toString.call(a)); // "[object Array]"
console.log(a instanceof Array); //true
console.log(a.constructor == Array); //true
判斷是否是空對象
有變量:var obj = {};
方法一:
JSON.stringify(obj); // "{}"
通過轉換成JSON對象來判斷是否是空大括號
方法二:
if(obj.id){ //如果屬性id存在....}
這個方法比較土,大多數人都能想到,前提是得知道對象中有某個屬性。
方法三:
function isEmptyObject(e) { var t; for (t in e) return !1; return !0 } //trueisEmptyObject(obj); //falseisEmptyObject({ "a":1, "b":2});
這個方法是jQuery的isEmptyObject()方法的實現(xiàn)方式。
instanceof
instanceof 運算符用于檢測構造函數的 prototype 屬性是否出現(xiàn)在某個實例對象的原型鏈上。
JavaScript Demo: Expressions - instanceof
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);
// expected output: true
console.log(auto instanceof Object);
// expected output: true
語法
object instanceof constructor
參數
object
某個實例對象
constructor
某個構造函數
描述
instanceof 運算符用來檢測 constructor.prototype 是否存在于參數 object 的原型鏈上。
// 定義構造函數
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,因為 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,因為 D.prototype 不在 o 的原型鏈上
o instanceof Object; // true,因為 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上
C.prototype = {};
var o2 = new C();
o2 instanceof C; // true
o instanceof C; // false,C.prototype 指向了一個空對象,這個空對象不在 o 的原型鏈上.
D.prototype = new C(); // 繼承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 因為 C.prototype 現(xiàn)在在 o3 的原型鏈上
需要注意的是,如果表達式 obj instanceof Foo 返回 true,則并不意味著該表達式會永遠返回 true,因為 Foo.prototype 屬性的值有可能會改變,改變之后的值很有可能不存在于 obj 的原型鏈上,這時原表達式的值就會成為 false。另外一種情況下,原表達式的值也會改變,就是改變對象 obj 的原型鏈的情況,雖然在目前的ES規(guī)范中,我們只能讀取對象的原型而不能改變它,但借助于非標準的 proto 偽屬性,是可以實現(xiàn)的。比如執(zhí)行 obj.proto = {} 之后,obj instanceof Foo 就會返回 false 了。
instanceof 和多全局對象(例如:多個 frame 或多個 window 之間的交互)
在瀏覽器中,我們的腳本可能需要在多個窗口之間進行交互。多個窗口意味著多個全局環(huán)境,不同的全局環(huán)境擁有不同的全局對象,從而擁有不同的內置類型構造函數。這可能會引發(fā)一些問題。比如,表達式 [] instanceof window.frames[0].Array 會返回 false,因為 Array.prototype !== window.frames[0].Array.prototype,并且數組從前者繼承。
起初,你會認為這樣并沒有意義,但是當你在你的腳本中開始處理多個 frame 或多個 window 以及通過函數將對象從一個窗口傳到另一個窗口時,這就是一個有效而強大的話題。比如,實際上你可以通過使用Array.isArray(myObj) 或者Object.prototype.toString.call(myObj) === "[object Array]" 來安全的檢測傳過來的對象是否是一個數組。
比如檢測一個 Nodes 在另一個窗口中是不是 SVGElement,你可以使用myNode instanceof myNode.ownerDocument.defaultView.SVGElement
示例
演示 String 對象和 Date 對象都屬于 Object 類型和一些特殊情況
下面的代碼使用了 instanceof 來證明:String 和 Date 對象同時也屬于Object 類型(他們是由 Object 類派生出來的)。
但是,使用對象文字符號創(chuàng)建的對象在這里是一個例外:雖然原型未定義,但 instanceof Object 返回 true。
var simpleStr = "This is a simple string";
var myString = new String();
var newStr = new String("String created with constructor");
var myDate = new Date();
var myObj = {};
var myNonObj = Object.create(null);
simpleStr instanceof String; // 返回 false, 檢查原型鏈會找到 undefined
myString instanceof String; // 返回 true
newStr instanceof String; // 返回 true
myString instanceof Object; // 返回 true
myObj instanceof Object; // 返回 true, 盡管原型沒有定義
({}) instanceof Object; // 返回 true, 同上
myNonObj instanceof Object; // 返回 false, 一種創(chuàng)建非 Object 實例的對象的方法
myString instanceof Date; //返回 false
myDate instanceof Date; // 返回 true
myDate instanceof Object; // 返回 true
myDate instanceof String; // 返回 false
演示 mycar 屬于 Car 類型的同時又屬于 Object 類型
下面的代碼創(chuàng)建了一個類型 Car,以及該類型的對象實例 mycar. instanceof 運算符表明了這個 mycar 對象既屬于 Car 類型,又屬于 Object 類型。
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var mycar = new Car("Honda", "Accord", 1998);
var a = mycar instanceof Car; // 返回 true
var b = mycar instanceof Object; // 返回 true
不是XXX的實例
要檢測對象不是某個構造函數的實例時,你可以這樣做
if (!(mycar instanceof Car)) {
// Do something, like mycar = new Car(mycar)
}
這和以下代碼完全不同
if (!mycar instanceof Car)
這段代碼永遠會得到 false(!mycar 將在 instanceof 之前被處理,所以你總是在驗證一個布爾值是否是 Car 的一個實例)。
Object.is()
Object.is() 方法判斷兩個值是否是相同的值。
語法
Object.is(value1, value2);
參數
value1
第一個需要比較的值。
value2
第二個需要比較的值。
返回值
表示兩個參數是否相同的布爾值
。
描述
Object.is()
判斷兩個值是否相同。如果下列任何一項成立,則兩個值相同:
這種相等性判斷邏輯和傳統(tǒng)的 ==
運算不同,==
運算符會對它兩邊的操作數做隱式類型轉換(如果它們類型不同),然后才進行相等性比較,(所以才會有類似 "" == false
等于 true
的現(xiàn)象),但 Object.is
不會做這種類型轉換。
這與 ===
運算符的判定方式也不一樣。===
運算符(和==
運算符)將數字值 -0
和 +0
視為相等,并認為 Number.NaN
不等于 NaN
。
例子
Object.is('foo', 'foo'); // true
Object.is(window, window); // true
Object.is('foo', 'bar'); // false
Object.is([], []); // false
var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo); // true
Object.is(foo, bar); // false
Object.is(null, null); // true
// 特例
Object.is(0, -0); // false
Object.is(0, +0); // true
Object.is(-0, -0); // true
Object.is(NaN, 0/0); // true
JavaScript 中的相等性判斷
ES2015中有四種相等算法:
- 非嚴格相等比較 (
==
) - 嚴格相等比較 (
===
): 用于Array.prototype.indexOf
,Array.prototype.lastIndexOf
, 和case
-matching - 同值零: 用于
%TypedArray%
和ArrayBuffer
構造函數、以及Map
和Set
操作, 并將用于 ES2016/ES7 中的String.prototype.includes
- 同值: 用于所有其他地方
JavaScript提供三種不同的值比較操作:
- 嚴格相等 ("triple equals" 或 "identity"),使用 === ,
- 寬松相等 ("double equals") ,使用 ==
- 以及
Object.is
(ECMAScript 2015/ ES6 新特性)
選擇使用哪個操作取決于你需要什么樣的比較。
簡而言之,在比較兩件事情時,雙等號將執(zhí)行類型轉換; 三等號將進行相同的比較,而不進行類型轉換 (如果類型不同, 只是總會返回 false ); 而Object.is的行為方式與三等號相同,但是對于NaN和-0和+0進行特殊處理,所以最后兩個不相同,而Object.is(NaN,NaN)將為 true。(通常使用雙等號或三等號將NaN與NaN進行比較,結果為false,因為IEEE 754如是說.) 請注意,所有這些之間的區(qū)別都與其處理原語有關; 這三個運算符的原語中,沒有一個會比較兩個變量是否結構上概念類似。對于任意兩個不同的非原始對象,即便他們有相同的結構, 以上三個運算符都會計算得到 false 。
嚴格相等 ===
全等操作符比較兩個值是否相等,兩個被比較的值在比較前都不進行隱式轉換。如果兩個被比較的值具有不同的類型,這兩個值是不全等的。否則,如果兩個被比較的值類型相同,值也相同,并且都不是 number 類型時,兩個值全等。最后,如果兩個值都是 number 類型,當兩個都不是 NaN,并且數值相同,或是兩個值分別為 +0 和 -0 時,兩個值被認為是全等的。
var num = 0;
var obj = new String("0");
var str = "0";
var b = false;
console.log(num === num); // true
console.log(obj === obj); // true
console.log(str === str); // true
console.log(num === obj); // false
console.log(num === str); // false
console.log(obj === str); // false
console.log(null === undefined); // false
console.log(obj === null); // false
console.log(obj === undefined); // false
在日常中使用全等操作符幾乎總是正確的選擇。對于除了數值之外的值,全等操作符使用明確的語義進行比較:一個值只與自身全等。對于數值,全等操作符使用略加修改的語義來處理兩個特殊情況:第一個情況是,浮點數 0 是不分正負的。區(qū)分 +0 和 -0 在解決一些特定的數學問題時是必要的,但是大部分情況下我們并不用關心。全等操作符認為這兩個值是全等的。第二個情況是,浮點數包含了 NaN 值,用來表示某些定義不明確的數學問題的解,例如:正無窮加負無窮。全等操作符認為 NaN 與其他任何值都不全等,包括它自己。(等式 (x !== x) 成立的唯一情況是 x 的值為 NaN)
非嚴格相等 ==
相等操作符比較兩個值是否相等,在比較前將兩個被比較的值轉換為相同類型。在轉換后(等式的一邊或兩邊都可能被轉換),最終的比較方式等同于全等操作符 === 的比較方式。 相等操作符滿足交換律。
相等操作符對于不同類型的值,進行的比較如下圖所示:
在上面的表格中,ToNumber(A) 嘗試在比較前將參數 A 轉換為數字,這與 +A(單目運算符+)的效果相同。ToPrimitive(A)通過嘗試調用 A 的A.toString() 和 A.valueOf() 方法,將參數 A 轉換為原始值(Primitive)。
一般而言,根據 ECMAScript 規(guī)范,所有的對象都與 undefined 和 null 不相等。但是大部分瀏覽器允許非常窄的一類對象(即,所有頁面中的 document.all 對象),在某些情況下,充當效仿 undefined 的角色。相等操作符就是在這樣的一個背景下。因此,IsFalsy(A) 方法的值為 true ,當且僅當 A 效仿 undefined。在其他所有情況下,一個對象都不會等于 undefined 或 null。
var num = 0;
var obj = new String("0");
var str = "0";
var b = false;
console.log(num == num); // true
console.log(obj == obj); // true
console.log(str == str); // true
console.log(num == obj); // true
console.log(num == str); // true
console.log(obj == str); // true
console.log(null == undefined); // true
// both false, except in rare cases
console.log(obj == null);
console.log(obj == undefined);
同值相等
同值相等解決了最后一個用例:確定兩個值是否在任何情況下功能上是相同的。(這個用例演示了里氏替換原則的實例。)當試圖對不可變(immutable)屬性修改時發(fā)生出現(xiàn)的情況:
// 向 Nmuber 構造函數添加一個不可變的屬性 NEGATIVE_ZERO
Object.defineProperty(Number, "NEGATIVE_ZERO",
{ value: -0, writable: false, configurable: false, enumerable: false });
function attemptMutation(v)
{
Object.defineProperty(Number, "NEGATIVE_ZERO", { value: v });
}
Object.defineProperty
在試圖修改不可變屬性時,如果這個屬性確實被修改了則會拋出異常,反之什么都不會發(fā)生。例如如果 v 是 -0 ,那么沒有發(fā)生任何變化,所以也不會拋出任何異常。但如果 v 是 +0 ,則會拋出異常。不可變屬性和新設定的值使用 same-value 相等比較。
同值相等由 Object.is方法提供。
零值相等
與同值相等類似,不過會認為 +0 與 -0 相等。
理解相等比較的模型
在 ES2015 以前,你可能會說雙等和三等是“擴展”的關系。比如有人會說雙等是三等的擴展版,因為他處理三等所做的,還做了類型轉換。例如 6 == "6" 。反之另一些人可能會說三等是雙等的擴展,因為他還要求兩個參數的類型相同,所以增加了更多的限制。怎樣理解取決于你怎樣看待這個問題。
但是這種比較的方式沒辦法把 ES2015 的 Object.is 排列到其中。因為 Object.is 并不比雙等更寬松,也并不比三等更嚴格,當然也不是在他們中間。從下表中可以看出,這是由于Object.is 處理 NaN 的不同。注意假如 Object.is(NaN, NaN)
被計算成 false
,我們就可以說他比三等更為嚴格,因為他可以區(qū)分 -0
和 +0
。但是對 NaN 的處理表明,這是不對的。Object.is 應該被認為是有其特殊的用途,而不應說他和其他的相等更寬松或嚴格。
什么時候使用 Object.is 或是三等
總的來說,除了對待NaN 的方式,Object.is 唯一讓人感興趣的,是當你需要一些元編程方案時,它對待0的特殊方式,特別是關于屬性描述器,即你的工作需要去鏡像Object.defineProperty 的一些特性時。如果你的工作不需要這些,那你應該避免使用Object.is,使用===來代替。即使你需要比較兩個NaN 使其結果為true
,總的來說編寫使用NaN 檢查的特例函數(用舊版本ECMAScript的isNaN方法
)也會比想出一些計算方法讓Object.is不影響不同符號的0的比較更容易些。
Array.is()
Array.isArray()用于確定傳遞的值是否是一個 Array
。
Array.isArray([1, 2, 3]);
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false
語法
Array.isArray(obj)
參數
obj
需要檢測的值。
返回值
如果值是 Array,則為true; 否則為false。
示例
// 下面的函數調用都返回 true
Array.isArray([]);
Array.isArray([1]);
Array.isArray(new Array());
Array.isArray(new Array('a', 'b', 'c', 'd'))
// 鮮為人知的事實:其實 Array.prototype 也是一個數組。
Array.isArray(Array.prototype);
// 下面的函數調用都返回 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray('Array');
Array.isArray(true);
Array.isArray(false);
Array.isArray(new Uint8Array(32))
Array.isArray({ __proto__: Array.prototype });
instanceof 和 isArray
當檢測Array實例時, Array.isArray 優(yōu)于 instanceof,因為Array.isArray能檢測iframes.
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]
// Correctly checking for Array
Array.isArray(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false
Polyfill
假如不存在 Array.isArray(),則在其他代碼之前運行下面的代碼將創(chuàng)建該方法。
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
Object.prototype.toString()
function Dog(name) {
this.name = name;
}
const dog1 = new Dog('Gabby');
Dog.prototype.toString = function dogToString() {
return '' + this.name;
}
console.log(dog1.toString());
// expected output: "Gabby"
語法
obj.toString()
返回值
一個表示該對象的字符串。
描述
每個對象都有一個 toString() 方法,當該對象被表示為一個文本值時,或者一個對象以預期的字符串方式引用時自動調用。默認情況下,toString() 方法被每個 Object 對象繼承。如果此方法在自定義對象中未被覆蓋,toString() 返回 "[object type]",其中 type 是對象的類型。以下代碼說明了這一點:
var o = new Object();
o.toString(); // returns [object Object]
示例
可以自定義一個方法,來取代默認的 toString() 方法。該 toString() 方法不能傳入參數,并且必須返回一個字符串。自定義的 toString() 方法可以是任何我們需要的值,但如果它附帶有關對象的信息,它將變得非常有用。
以下代碼定義了 Dog 對象類型,并創(chuàng)建了一個 Dog 類型的 theDog 對象:
function Dog(name,breed,color,sex) {
this.name = name;
this.breed = breed;
this.color = color;
this.sex = sex;
}
var theDog = new Dog("Gabby", "Lab", "chocolate", "female");
如果當前的對象調用了 toString()
方法,它將會返回從 Object
繼承而來的 toString()
方法的返回默認值:
theDog.toString(); // 返回 [object Object]
下面的代碼中定義了一個叫做 dogToString() 的方法來覆蓋默認的 toString() 方法。這個方法生成一個 "property = value;" 形式的字符串,該字符串包含了當前對象的 name、breed、color 和 sex 的值。
Dog.prototype.toString = function dogToString() {
var ret = "Dog " + this.name + " is a " + this.sex + " " + this.color + " " + this.breed;
return ret;
}
也可以這樣寫:
Dog.prototype.toString = function dogToString() {
return `Dog ${this.name} is a ${this.sex} ${this.color} ${this.breed}`;
}
使用上述代碼,任何時候在字符串上下文中使用 theDog.toString() 時,JavaScript 都會自動調用 dogToString() 方法(dogToString() 可以是一個匿名函數),并且返回以下字符串:
"Dog Gabby is a female chocolate Lab"
使用 toString() 檢測對象類型
可以通過 toString() 來獲取每個對象的類型。為了每個對象都能通過 Object.prototype.toString() 來檢測,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式來調用,傳遞要檢查的對象作為第一個參數,稱為 thisArg。
var toString = Object.prototype.toString;
toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
//Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]
同樣是檢測對象obj調用toString方法(關于toString()方法的用法的可以參考toString的詳解),obj.toString()的結果和Object.prototype.toString.call(obj)的結果不一樣,這是為什么?
這是因為toString為Object的原型方法,而Array 、Function等類型作為Object的實例,都重寫了toString方法。不同的對象類型調用toString方法時,根據原型鏈的知識,調用的是對應的重寫之后的toString方法(Function類型返回內容為函數體的字符串,Array類型返回元素組成的字符串.....),而不會去調用Object上原型toString方法(返回對象的具體類型),所以采用obj.toString()不能得到其對象類型,只能將obj轉換為字符串類型;因此,在想要得到對象的具體類型時,應該調用Object上原型toString方法。
我們可以驗證一下,將數組的toString方法刪除,看看會是什么結果:
var arr=[1,2,3];
console.log(Array.prototype.hasOwnProperty("toString"));//true
console.log(arr.toString());//1,2,3
delete Array.prototype.toString;//delete操作符可以刪除實例屬性
console.log(Array.prototype.hasOwnProperty("toString"));//false
console.log(arr.toString());//"[object Array]"
刪除了Array的toString方法后,同樣再采用arr.toString()方法調用時,不再有屏蔽Object原型方法的實例方法,因此沿著原型鏈,arr最后調用了Object的toString方法,返回了和Object.prototype.toString.call(arr)相同的結果。
Javascript構造函數及原型對象
使用Object或對象字面量創(chuàng)建對象
/*
* @ 使用Object或對象字面量創(chuàng)建對象
* @ 最基本的創(chuàng)建對象的方法
*/
// 創(chuàng)建一個student對象, new一個Object
var student = new Object();
student.name = "easy";
student.age = "20";
// 如果嫌這種方法麻煩, 有種封裝不良的感覺,試試字面量創(chuàng)建
對象字面量方式來創(chuàng)建對象
/*
* @ 對象字面量方式來創(chuàng)建對象
* @ 缺點,無法重復
*/
var student = {
name: "easy",
age: "20"
}
// 當我們要創(chuàng)建同類的student1, student2,...,studentN時, 不得不將以上的代碼重復N次
var student2 = {
name: "easy2",
age: "20"
}
// ...
var studentn = {
name: "easyn",
age: "20"
}
// 這樣麻煩而且重復太多, 能不能像工廠間那樣, 有一個車床不斷生產出對象.
工廠模式創(chuàng)建對象
/*
* @ 工廠模式創(chuàng)建對象
* @ JS中沒有類的概念, 那么我們不妨就使用一種函數將以上對象創(chuàng)建過程封裝起來以便于重復調用
* @ 同時可以給出特定接口來初始化對象
*/
function createStudent(name, age){
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}
var student1 = createStudent("easy1", 20);
var student2 = createStudent("easy2", 20);
// ...
var studentn = createStudent("easyn", 20);
// 這樣就可以通過createStudent函數源源不斷地"生產"對象了, 看起來很完美了,但我們不僅希望"產品"的生產可以像工廠間一樣源源不斷,還想知道生產的產品究竟是哪一種類型.
// 這樣就可以通過createStudent函數源源不斷地"生產"對象了, 看起來很完美了,但我們不僅希望"產品"的生產可以像工廠間一樣源源不斷,還想知道生產的產品究竟是哪一種類型
// 我們希望v1是student類型, 而v2是fruit類型, 那我們可以用構造函數來創(chuàng)建對象
/*
* @ "構造函數" 和 "普通函數" 有什么區(qū)別
* @ 一.實際上并不存在創(chuàng)建構造函數的特殊語法, 構造函數就是一個普通函數, 與普通函數唯一的區(qū)別在于調用方法
* @ 二.對于任意函數,使用new操作符調用, 那么它就是構造函數; 不使用new操作符調用, 那么它就是普通函數
* @ 三.按照慣例,我們約定構造函數名以大寫字母開頭
* @ 四.使用new操作符調用構造函數時,會經歷4個階段
* @ 1.創(chuàng)建一個新對象
* @ 2.將構造函數作用賦給新對象(使用tihs指向該新對象)
* @ 3.執(zhí)行構造函數代碼
* @ 4.返回新對象
*/
構造函數模式創(chuàng)建對象
// 將工廠模式函數重寫, 并添加一個方法屬性
function Student(name, age){
this.name = name;
this.age = age;
this.alertName = function(){
alert(this.name);
}
}
function Fruit(name, color){
this.name = name;
this.color = color;
this.alertName = function(){
alert(this.name);
}
}
// 分別new出Student和Fruit的對象
var v1 = new Student("easy", 20);
var v2 = new Fruit("apple", "green");
// 這樣就可以用instanceof操作符來檢測以上對象類型區(qū)分出不同的對象
console.log(v1 instanceof Student); // true
console.log(v2 instanceof Student); // false
console.log(v1 instanceof Fruit); // false
console.log(v2 instanceof Fruit); // true
console.log(v1 instanceof Object); // true
console.log(v2 instanceof Object); // true
// 這樣解決了工廠模式無法區(qū)分對象類型, 但這樣就完美了嗎, 在JS中,函數是對象, 當我們實例化不只一個Strudent對象的時候.
var v1 = new Student("easy1", 20);
var v2 = new Student("easy2", 20);
//...
var vn = new Student("easyn", 20);
// 其中共同的alertName()函數也被實例化了n次, 我們可以用以下方法來檢測不同的Strudent對象并不共用alertName()函數
console.log(v1.alertName == v2.alertName); // false
// 這無疑是一種內存的浪費,我們知道this對象是在運行時基于函數的執(zhí)行環(huán)境進行綁定. 在全局函數中,this對象等同于window, 在對象方法中,this指向該對象.
// 試將對象方法移到構造函數外部
function Student(name, age){
this.name = name;
this.age = age;
this.alertName = alertName;
}
function alertName(){
alert(this.name);
}
var stu1 = new Student("easy1", 20);
var stu2 = new Student("easy2", 20);
// 在調用"stu1.alert()時", this對象才被綁定到stu1上, 通過alertName()函數定義為全局函數, 這樣對象中的alertName屬性則被設置為指向該全局的指針, 由此stu1和stu2共享了該全局函數, 解決了內在浪費的問題
// 但是,通過全局函數的方式解決對象內部共享的問題, 終究不像一個好的解決方法, 更好的方案是通過原型對象模式來解決
原型模式創(chuàng)建對象
一、基本概念:
1,對象:屬性和方法的集合,即變量和函數的封裝,每個對象都有一個proto屬性,指向這個對象的構造函數的原型對象;
2,構造器函數:用于創(chuàng)建對象的函數,通過new關鍵字生成對象。函數名一般首字母大寫;
3,原型對象:每個函數都有一個prototype屬性,它是一個指向原型對象的指針(原型對象在定義函數時同時被創(chuàng)建)
二、創(chuàng)建對象的方法
1,使用構造函數和原型對象共同創(chuàng)建:
如上圖,構造器函數Person(),通過new關鍵字創(chuàng)建了兩個實例化對象p1、p2,這兩個新對象都繼承了,構造器Person()函數prototype屬性所指向的原型對象。通過構造函數創(chuàng)建實例對象p1和p2的時候,其中name、age、job這些是通過構造函數生成的(本地部分),sayName方法是通過繼承原型對象來實現(xiàn)共享的(遠程部分),這樣多個實例對象都是由本地(私有)和遠程(共享)兩部分組成。如果還是不清楚,沒關系我們上代碼。
function Person(name, age, job){//構造器函數
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {//設置構造器函數prototype指定的對象,即重置原型對象
constructor : Person,
sayName : function(){alert(this.name);}
}
var p1 = new Person("Tom", 29, "Teacher");//實例化對象p1
//{name:"Tom",age:29,job:"Teacher",__proto__:object},object即原型對象:Person.prototype指向的對象
var p2 = new Person("Jack", 27, "Doctor");//實例化對象p2
//{name:"Jack",age:27,job:"Doctor",__proto__:object}
2、僅使用原型對象創(chuàng)建
如上圖,使用Object.create方法從原型對象直接生成新的實例對象,新對象p1繼承原型對象的屬性和方法,但是這里沒有用到構造函數
var person={ classname:'human'}//將這個對象當做原型
var p1=Object.create(person)//生成實例對象
console.log(p1.classname)//human,相當于p1.proto.classname
構造函數是媽,原型對象是爸,實例對象是孩子。媽讓每個孩子擁有私有能力,爸讓它們擁有共有能力(這個共有能力其實都是爸代勞的/(ㄒoㄒ)/~);沒有構造函數的情況下,可以直接理解為克隆哦怎么樣,這樣應該能理解三者之間的關系了吧。
使用原型模型創(chuàng)建對象
直接在原型對象中添加屬性和方法
function Student() {
}
Student.prototype.name = "easy";
Student.prototype.age = 20;
Student.prototype.alertName = function(){
alert(this.name);
};
var stu1 = new Student();
var stu2 = new Student();
stu1.alertName(); //easy
stu2.alertName(); //easy
alert(stu1.alertName == stu2.alertName); //true 二者共享同一函數
以上代碼,我們在Student的protptype對象中添加了name、age屬性以及alertName()方法。但創(chuàng)建的stu1和stu2中并不包含name、age屬性以及alertName()方法,而只包含一個[[prototype]]指針屬性。當我們調用stu1.name或stu1.alertName()時,是如何找到對應的屬性和方法的呢?
當我們需要讀取對象的某個屬性時,都會執(zhí)行一次搜索。首先在該對象中查找該屬性,若找到,返回該屬性值;否則,到[[prototype]]指向的原型對象中繼續(xù)查找。
由此我們也可以看出另外一層意思:如果對象實例中包含和原型對象中同名的屬性或方法,則對象實例中的該同名屬性或方法會屏蔽原型對象中的同名屬性或方法。原因就是“首先在該對象中查找該屬性,若找到,返回該屬性值;”
擁有同名實例屬性或方法的示意圖:
在訪問stu1.name是會得到”EasySir”:
alert(stu1.name); //EasySir
通過對象字面量重寫原型對象
很多時候,我們?yōu)榱藭鴮懙姆奖阋约爸庇^上的”封裝性”,我們往往采用對象字面量直接重寫整個原型對象:
function Student() {
}
Student.prototype = {
constructor : Student,
name : "easy",
age : 20,
alertName : function() {
alert(this.name);
}
};
要特別注意,我們這里相當于用對象字面量重新創(chuàng)建了一個Object對象,然后使Student的prototype指針指向該對象。該對象在創(chuàng)建的過程中,自動獲得了新的constructor屬性,該屬性指向Object的構造函數。因此,我們在以上代碼中,增加了constructor : Student使其重新指回Student構造函數。
原型模型創(chuàng)建對象的局限性
原型模型在對象實例共享數據方面給我們帶來了很大的便利,但通常情況下不同的實例會希望擁有屬于自己單獨的屬性。我們將構造函數模型和原型模型結合使用即可兼得數據共享和”不共享”。
構造與原型混合模式創(chuàng)建對象
我們結合原型模式在共享方法屬性以及構造函數模式在實例方法屬性方面的優(yōu)勢,使用以下的方法創(chuàng)建對象:
//我們希望每個stu擁有屬于自己的name和age屬性
function Student(name, age) {
this.name = name;
this.age = age;
}
//所有的stu應該共享一個alertName()方法
Student.prototype = {
constructor : Student,
alertName : function() {
alert(this.name);
}
}
var stu1 = new Student("Jim", 20);
var stu2 = new Student("Tom", 21);
stu1.alertName(); //Jim 實例屬性
stu2.alertName(); //Tom 實例屬性
alert(stu1.alertName == stu2.alertName); //true 共享函數
以上,在構造函數中定義實例屬性,在原型中定義共享屬性的模式,是目前使用最廣泛的方式。通常情況下,我們都會默認使用這種方式來定義引用類型變量。
通過對象字面量重寫原型對象
function Student(){}
Student.prototype = {
constructor: Student,
name: "easy",
age: 20,
alertName: function(){
alert(this.name);
}
}
// 要特別注意,我們這里相當于用對象字面量重新創(chuàng)建了一個Object對象,然后使Student的prototype指針指向該對象。該對象在創(chuàng)建的過程中,自動獲得了新的constructor屬性,該屬性指向Object的構造函數。因此,我們在以上代碼中,增加了constructor : Student使其重新指回Student構造函數。
// 原型模型創(chuàng)建對象的局限性
// 原型模型在對象實例共享數據方面給我們帶來了很大的便利, 但通常情況下不同的實例會希望擁有屬性自己的單獨的屬性, 我們將構造函數的模型和原型模型結合使用即可兼得數據共享和"不共享"
構造與原型混合模式創(chuàng)建對象
/*
* 構造與原型混合模式創(chuàng)建對象
* 我們結合原型模式在共享方法屬性以及構造函數模式在實例方法屬性方面的優(yōu)勢,使用以下的方法創(chuàng)建對象
* 在構造函數中定義實例屬性,在原型中定義共享屬性的模式,是目前使用最廣泛的方式。通常情況下,我們都會默認使用這種方式來定義引用類型變量。
*/
// 我們希望每個stu擁有屬于自己的name和age屬性
function Student(name, age){
this.name = name;
this.age = age;
}
// 所有的stu應該共享一個alertName()方法
Student.prototype = {
constructor: Student,
alertName: function(){
alert(this.name);
}
}
var stu1 = new Student("Jim", 20);
var stu2 = new Student("Tom", 21);
stu1.alertName();
stu2.alertName();
alert(stu1.alertName == stu2.alertName); //true 共享函數