Undefined
對未初始化的變量執(zhí)行typeof操作符會返回undefined
值,而對未聲明的變量執(zhí)行typeof操作符同樣也會返回undefined
var message;
console.log(typeof message); // => undefined
console.log(typeof gaga); // => undefined
Boolean
各種類型轉(zhuǎn)換成Boolean的規(guī)則
數(shù)據(jù)類型 | 轉(zhuǎn)成true的值 | 轉(zhuǎn)成false的值 |
---|---|---|
Boolean | true | false |
String | 任何非空字符串 | ""空字符串 |
Number | 任何非零數(shù)字值(包括無窮大) | 0和NaN |
Object | 任何對象 | null |
Undefined | n/a | undefined |
Number
Number類型應(yīng)該是ECMAScript中最令人關(guān)注的數(shù)據(jù)類型了任内。
除了以十進制表示外赎瞎,整數(shù)還可以通過八進制或十六進制表示牌里,其中,八進制字面值的第一位必須是0务甥,然后是八進制數(shù)字序列(0 ~ 7)牡辽。如果字面值中的數(shù)值超出了范圍,那么前導0將被忽略敞临,后面的數(shù)值將被當作十進制數(shù)值解析
var n = 070; // => 56
var n = 079; // => 79(無效的八進制數(shù)值)
var n = 08; // => 8(無效的八進制數(shù)值)
八進制字面量在嚴格模式下是無效的态辛,會導致支持的JavaScript引擎拋出錯位。
十六進制字面值的前兩位必須是0x哟绊,后邊跟著任何十六進制數(shù)字(0 ~ 9 及 A ~ F)因妙。其中,字母A ~ F 可以大寫票髓,也可以小寫攀涵。
var n = 0xA; // 10
var n = 0x1f; // 31
計算的時候勺馆,八進制和十六進制都將轉(zhuǎn)成十進制后再計算煞赢。
由于保存浮點數(shù)值需要的內(nèi)存空間是保存整數(shù)值的兩倍,因此ECMAScript會不失時機的將浮點數(shù)值轉(zhuǎn)換為整數(shù)值护锤。
永遠不要測試某個特定的浮點數(shù)值:
if (a + b == 0.3) {
alert("You got 0.3");
}
上邊的例子中裆操,我們測試的是兩個數(shù)的和是不是等于0.3怒详。如果這兩個數(shù)是0.05和0.25,或者是0.15和0.15都不會有問題踪区。如果這兩個數(shù)是0.1和0.2昆烁,那么測試就無法通過。
由于內(nèi)存的限制缎岗,ECMAScript并不能保存世界上所有的數(shù)值静尼。如果某次計算的結(jié)果得到了一個超出JavaScript數(shù)值范圍的值,那么這個數(shù)值將被自動轉(zhuǎn)換成特殊的Infinity值传泊,如果這個數(shù)值是負數(shù)鼠渺,則會轉(zhuǎn)成-Infinity。出現(xiàn)正或負的Infinity值就不能繼續(xù)計算了眷细±鬼铮可以使用isFinite()函數(shù)判斷一個數(shù)值是不是有窮的。
NaN是Not a Number的縮寫溪椎,它有兩個非同尋常的特點:
- 任何涉及NaN的操作都會返回NaN
- NaN與任何值都不相等普舆,包括NaN本身
isNan()函數(shù)的原理是:在接受一個值后恬口,會嘗試將這個值轉(zhuǎn)換成數(shù)值,成功就返回false沼侣,失敗則返回true楷兽。
有3個函數(shù)可以把非數(shù)值轉(zhuǎn)換成數(shù)值:Number(),parseInt()和parseFloat()。Number函數(shù)可以用于任何數(shù)據(jù)類型华临,另外兩個則專門用于把字符串轉(zhuǎn)換成數(shù)值芯杀。
Number()函數(shù)的轉(zhuǎn)換規(guī)則如下:
如果是Boolean值,true和false將分別被轉(zhuǎn)換為1和0
如果是數(shù)字值雅潭,只是簡單的傳入和返回
如果是null值揭厚,返回0
如果是undefined,返回NaN
-
如果是字符串扶供,遵循下列規(guī)則:
- 如果字符串中只包含數(shù)字(包括前面帶正好或負號的情況)筛圆,則將其轉(zhuǎn)換為十進制數(shù)值,即“1”變成1椿浓,“123”會變成123太援,而“011”會變成11(注意:前導的0被忽略了)
- 如果字符串中包含有效的浮點格式,如“1.1”扳碍,則將其轉(zhuǎn)換為對應(yīng)的浮點數(shù)值(同樣會忽略前導0)
- 如果字符串中包含有效的十六進制格式提岔,例如“0xf”,則將其轉(zhuǎn)換為相同大小的十進制整數(shù)值
- 如果字符串是空的(不包含任何字符)笋敞,則將其轉(zhuǎn)換為0
- 如果字符串中包含除上述格式之外的字符碱蒙,則將其轉(zhuǎn)換為NaN
如果是對象,則調(diào)用對象的valueOf()方法夯巷,然后依照前面的規(guī)則轉(zhuǎn)換返回的值赛惩,如果轉(zhuǎn)換的結(jié)果是NaN,則調(diào)用對象的toString()方法趁餐,然后再一次按照前面的規(guī)則轉(zhuǎn)換返回的字符串值喷兼。
var n = Number("Hello world"); // NaN
var n = Number(""); // 0
var n = Number("000011"); // 11
var n = Number("true"); // 1
parseInt()和parseFloat()在使用的時候需要特別注意進制的問題,parseFloat()只解析十進制后雷。
String
String()方法內(nèi)部轉(zhuǎn)換規(guī)則:
- 如果值有toString()方法季惯,則調(diào)用該方法并返回相應(yīng)的結(jié)果,toString()方法不能處理null和undefined的情況
- 如果值是null喷面,則返回“null”
- 如果值是undefined星瘾,則返回“undefined”
var n1 = 10;
var n2 = true;
var n3 = null;
var n4;
console.log(String(n1)); // => "10"
console.log(String(n2)); // => "true"
console.log(String(n3)); // => "null"
console.log(String(n4)); // => "undefined"
邏輯與
邏輯與(&&)可以應(yīng)用于任何類型的操作數(shù)走孽,而不僅僅是布爾值惧辈。在有一個操作數(shù)不是布爾值的情況下,邏輯與操作就不一定返回布爾值磕瓷,它遵循下列規(guī)則:
- 如果第一個操作數(shù)是對象盒齿,則返回第二個操作數(shù)
- 如果第二個操作數(shù)是對象念逞,則只有在第一個操作數(shù)的求值結(jié)果為true的情況下才會返回該對象
- 如果兩個擦作數(shù)都是對象,則返回第二個操作數(shù)
- 如果有一個操作數(shù)是null边翁,則返回null
- 如果有一個操作數(shù)是NaN翎承,則返回NaN
- 如果有一個操作數(shù)是undefined,則返回undefined
邏輯與操作屬于短路操作符匾,即如果第一個操作數(shù)能夠決定結(jié)果叨咖,那么就不會再對第二個操作數(shù)求值,這個跟有些語言不一樣啊胶,因此在條件語句中使用邏輯與的時候要特別注意甸各。
var n = true && NaN;
console.log(String(n)); // => NaN
var n2 = Boolean(n);
console.log(n2); // => false
if (!n) {
console.log("ok"); // => ok
}
打印出了ok,說明在條件語句中可以使用&&焰坪,但是需要明白返回值的問題趣倾。
相等操作符
相等(==)操作符在進行比較之前會對操作數(shù)進行轉(zhuǎn)換,我們要了解這個轉(zhuǎn)換規(guī)則:
- 如果有一個操作數(shù)是布爾值某饰,則在比較相等性之前先將其轉(zhuǎn)換為數(shù)值儒恋,false轉(zhuǎn)換為0,而true轉(zhuǎn)換為1
- 如果一個操作數(shù)是字符串黔漂,另一個操作數(shù)是數(shù)值诫尽,在比較相等性之前先將字符串轉(zhuǎn)換為數(shù)值
- 如果一個操作數(shù)是對象,另一個操作數(shù)不是炬守,則調(diào)用對象的valueOf()方法箱锐,用得到的基本類型值按照前面的規(guī)則進行比較
- null和undefined是相等的
- 要比較相等性之前,不能將null和undefined轉(zhuǎn)換成其他任何值
- 如果有一個操作數(shù)是NaN劳较,則相等操作符返回false驹止,而不相等操作符返回true。重要提示:即使兩個操作數(shù)都是NaN观蜗,相等操作符也返回false
- 如果兩個操作數(shù)都是對象臊恋,則比較他們是不是同一個對象。如果兩個操作數(shù)都指向同一對象墓捻,則相等操作符返回true抖仅,否則,返回false
全等(===)和相等(==)最大的不同之處是它不會對操作數(shù)進行強制轉(zhuǎn)換砖第。
參數(shù)傳遞
ECMAScript中所有函數(shù)的參數(shù)都是按值傳遞的撤卢。也就是說,把函數(shù)外部的值復制給函數(shù)內(nèi)部的參數(shù)梧兼,就和把值從一個變量復制到另一個變量一樣放吩。基本類型值的傳遞如同基本類型變量的復制一樣羽杰,而引用類型值的傳遞渡紫,則如同引用類型變量的復制一樣到推。有不少開發(fā)者在這一點上可能會感到困惑,因為訪問變量有按值和按引用兩種方式,而參數(shù)只能按值傳遞。
在向參數(shù)傳遞基本類型的值時益涧,被傳遞的值會被復制給一個局部變量(即命名參數(shù)扭弧,或者用ECMAScript的概念來說泽腮,就是arguments對象中的一個元素)碘箍。在向參數(shù)傳遞引用類型的值時四濒,會把這個值在內(nèi)存中的地址復制給一個局部變量,因此這個局部變量的變化會反映在函數(shù)的外部台腥。
先看一個基本類型值傳遞的例子:
function addTen(num) {
num += 10;
return num;
}
var count = 10;
var result = addTen(count);
console.log(count); // => 10
console.log(result); // => 20
上邊的代碼中,addTen函數(shù)并沒有改變count的值黎侈,按照上邊的理論察署,我們可以這么看addTen函數(shù):
function addTen(num) {
num = count; // 當調(diào)用了函數(shù)的時候,函數(shù)內(nèi)部做了這一個操作
num += 10;
return num;
}
再來看看引用類型的值傳遞的例子:
function setName(obj) {
obj = person; // 當調(diào)用了函數(shù)的時候峻汉,函數(shù)內(nèi)部做了這一個操作
obj.name = "James";
obj = new Object();
obj.name = "Bond";
}
var person = new Object();
setName(person);
console.log(person.name); // => "James"
在函數(shù)內(nèi)部贴汪,同樣為參數(shù)賦值了一個引用類型值的復制數(shù)據(jù)。在函數(shù)內(nèi)部休吠,obj就是一個指針扳埂,當給他重新賦值一個新的對象的時候,他指向了另一個數(shù)據(jù)瘤礁,因此阳懂,即使給它的name賦值,也不會影響函數(shù)外部的對象的值柜思,說白了岩调,還是內(nèi)存地址的問題。
Array
數(shù)組的length
屬性很有特點------他不是只讀的赡盘。因此通過設(shè)置這個屬性誊辉,可以從數(shù)組的末尾移除項或向數(shù)組中添加新項:
var colors = ["red", "blue", "green"];
colors.length = 2;
alert(colors[2]); // => undefined
colors.length = 4;
上邊的代碼給colors設(shè)置了length后,最后邊的那個數(shù)據(jù)就變成了undefined亡脑,說明通過設(shè)置length能夠修改數(shù)組的值堕澄,如果這個值大于數(shù)組元素的個數(shù),那么多出來的元素就賦值為undefined霉咨。
數(shù)組的sort()方法會調(diào)用每個數(shù)組項的toString()轉(zhuǎn)型防范蛙紫,然后比較得到的字符串,以確定如何排序途戒。即使數(shù)組中的每一項都是數(shù)值坑傅,sort()方法比較的也是字符串。 看個例子:
var array = [1, 4, 5, 10, 15];
array = array.sort();
console.log(array.toString()); // => 1,10,15,4,5
可見喷斋,即使例子中值的順序沒有問題唁毒,但sort()方法也會根據(jù)測試字符串的結(jié)果改變原來的順序蒜茴。
數(shù)組有5種迭代方法:
- every(): 對數(shù)組中的每一項運行給定函數(shù),如果該函數(shù)對每一項都返回true浆西,則返回true粉私,就跟它的名字一樣,測試數(shù)組中是否每一項都符合函數(shù)的條件
- some(): 對數(shù)組中的每一項運行給定函數(shù)近零,如果該函數(shù)對任一項返回true诺核,則返回true,同樣久信,就跟它的名字一樣窖杀,測試數(shù)組中是否存在至少一項是符合函數(shù)的條件
- filter(): 對數(shù)組中的每一項運行給定的函數(shù),返回該函數(shù)會返回true的項組成的數(shù)組裙士, 主要用于過濾數(shù)據(jù)
- forEach(): 對數(shù)組中華的每一項運行給定函數(shù)入客,這個方法沒有返回值,就是遍歷方法
- map(): 對數(shù)組中的每一項運行給定函數(shù)腿椎,返回每次函數(shù)調(diào)用的結(jié)果組成的數(shù)組痊项,這個算是對數(shù)組中的項進行加工
var numbers = ["1", "2", "3", "4", "5", "6"];
// every() 檢測數(shù)組中的每一項是否都大于2
var everyResult = numbers.every(function (item, index, array) {
return item > 2;
});
console.log(everyResult); // => false
// some() 檢測數(shù)組中是否至少有一項大于2
var someResult = numbers.some(function (item, index, array) {
return item > 2;
});
console.log(someResult); // => true
// filter() 過濾數(shù)組中大于2的值
var filterResult = numbers.filter(function (item, index, array) {
return item > 2;
});
console.log(filterResult); // => ["3", "4", "5", "6"]
// map() 加工數(shù)組中的數(shù)據(jù)
var maoResult = numbers.map(function (item, index, array) {
return item * 2;
});
console.log(maoResult); // => [2, 4, 6, 8, 10, 12]
Function
使用函數(shù)作為返回值是一件很奇妙的事情,我們使用一個例子來看看:
function createComparisonFunction(propertyName) {
return function (object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}
var data = [{
name: "zhangsan",
age: 20
}, {
name: "lisi",
age: 30
}];
data.sort(createComparisonFunction("name"));
console.log(data[0]); // => {name: "lisi", age: 30}
data.sort(createComparisonFunction("age"));
console.log(data[0]); // => {name: "zhangsan", age: 20}
在函數(shù)內(nèi)部酥诽,有兩個特殊的對象:arguments和this鞍泉。其中,arguments是一個類數(shù)組對象肮帐,包含著傳入函數(shù)中的所有參數(shù)咖驮。雖然arguments的主要用途是保存函數(shù)參數(shù),**但這個對象還有一個名叫callee的屬性训枢,該屬性是一個指針托修,指向擁有這個arguments對象的函數(shù),我們看下邊這個非常經(jīng)典的階乘函數(shù):
function factorial(num) {
if (num < 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
console.log(factorial(5)); // => 120
定義階乘函數(shù)一般都要用到遞歸算法恒界,如上邊的代碼所示睦刃,在函數(shù)有名字,而且名字以后都不會變的的情況下十酣,這樣定義沒問題涩拙。但問題是這個函數(shù)的執(zhí)行與函數(shù)名factorial僅僅耦合在了一起。為了消除這種緊密耦合的現(xiàn)象耸采,可以像下面這樣是喲很難過arguments.callee:
function factorial(num) {
if (num < 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
console.log(factorial(5)); // => 120
我們修改factorial函數(shù)的實現(xiàn)后:
const anotherFactorial = factorial;
factorial = function () {
return 0;
}
console.log(anotherFactorial(5)); // => 120
console.log(factorial(5)); // => 0
使用call()或apply()來擴充作用域的最大好處兴泥,就是對象不需要與方法有任何耦合關(guān)系。
屬性類型
ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性虾宇。
1.數(shù)據(jù)屬性
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置搓彻。在這個位置可以讀取和寫入值。數(shù)據(jù)屬性有4個描述其行為的特性:
- configurable
- enumerable
- writable
- value
我們先看幾個例子:
const person = { };
Object.defineProperty(person, "name", {
writable: false,
value: "James"
});
console.log(person.name); // => James
person.name = "Bond";
console.log(person.name); // => James
上邊的代碼設(shè)置了person中的屬性name的特性,把它的writable設(shè)置為false旭贬,因此當我們重寫它的name屬性的時候是不起作用的怔接,使用value可以給屬性賦值。我們再看一個例子:
const person = { };
Object.defineProperty(person, "name", {
configurable: false,
value: "James"
});
console.log(person.name); // => James
delete person.name;
console.log(person.name); // => James
當我們把confugurable設(shè)置為false的時候稀轨,就把name屬性的可配置性給鎖死了扼脐,一旦把confugurable設(shè)為false,后續(xù)的再次對這個屬性設(shè)置特性的時候就會出錯靶端。下邊的代碼會報錯:
Object.defineProperty(person, "name", {
writable: true,
value: "JJJJJ"
});
console.log(person.name);
2.訪問器屬性
訪問器屬性不含數(shù)據(jù)值谎势,但可以通過set或get方法來設(shè)置或獲取值凛膏,就像制定了一套這樣的規(guī)則杨名。我跟喜歡稱這個特性為計算屬性。
const book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
console.log(book.edition);
在這個例子中猖毫。_year很想一個私有變量台谍,我們通過set,get方法來寫了一個year屬性吁断,當然也可以使用這種方式來控制屬性是否只讀或只寫特性趁蕊。
有一點值得注意,上邊說的這些內(nèi)容算是為對象創(chuàng)建屬性的方法仔役,我們也可以采用person.name這種方式創(chuàng)建屬性掷伙,只不過后邊這種創(chuàng)建的方式給里邊的特性賦了默認的值。
創(chuàng)建對象
在JavaScript中Object的總結(jié)這篇文章中又兵,我介紹了多種創(chuàng)建對象的方法:
工廠方法
核心思想是通過函數(shù)來創(chuàng)建對象任柜,函數(shù)會返回一個根據(jù)參數(shù)創(chuàng)建的新的對象,這個方法雖然解決了創(chuàng)建多個相似對象的問題沛厨,但沒有解決對象識別的問題宙地,因為在函數(shù)內(nèi)容,知識把參數(shù)賦值給了任何對象的屬性
構(gòu)造函數(shù)
構(gòu)造函數(shù)的使用方法我就不提了逆皮,我只說幾點需要注意的地方宅粥,構(gòu)造函數(shù)的第一個字母要大寫,內(nèi)部使用this來指定屬性和方法电谣。在創(chuàng)建對象的時候要加上new關(guān)鍵字秽梅。
其實構(gòu)造函數(shù)的本質(zhì)也是一個函數(shù),如果在調(diào)用的時候不加關(guān)鍵字new剿牺,那么它內(nèi)部的屬性將會創(chuàng)建為全局變量的屬性风纠。**任何加上new關(guān)鍵字的函數(shù)都會變成構(gòu)造函數(shù),而構(gòu)造函數(shù)的本質(zhì)是:
var a = {};
a.__proto__ = F.prototype;
F.call(a);
構(gòu)造函數(shù)能夠讓我們通過類似.constructor或instanceof來判斷對象的類型牢贸,但它的缺點是會為相同的屬性或方法創(chuàng)建重復的值竹观,我們都知道在JavaScript中函數(shù)也是對象,這種返回創(chuàng)建統(tǒng)一對象的過程,肯定給性能帶來了很大的挑戰(zhàn)臭增,因此這種模式還需要升級懂酱。
原型模式
原型模式是非常重要的一個概念,我們會使用很長的篇幅來介紹這方面的內(nèi)容誊抛。
首先我們應(yīng)該明白函數(shù)名字本質(zhì)上是一個指向函數(shù)對象的指針列牺,因此他能表示這個函數(shù)對象,在JavaScript中每個函數(shù)**內(nèi)部都有一個prototype屬性拗窃,這個屬性是一個指針瞎领,指向一個對象,而這個對象的用于是包含屬性和方法随夸。因此我們有這樣的啟發(fā)九默,如果我給構(gòu)造函數(shù)的prototype賦值屬性和方法,那么我在使用構(gòu)造函數(shù)創(chuàng)建對象的時候宾毒,是不是就可以繼承這些共有的屬性呢驼修? 答案是肯定的:
function Person() {
}
Person.prototype.name = "James";
Person.prototype.sayName = function () {
console.log(this.name);
};
const person1 = new Person();
person1.sayName(); // => James
const person2 = new Person();
person2.sayName(); // => James
console.log(person1.name == person2.name); // => true
1.理解原型對象
無論什么時候,只要創(chuàng)建了一個新函數(shù)诈铛,就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性乙各。這個屬性指向函數(shù)的原型西鄉(xiāng),在默認情況下幢竹,這個prototype又會自動獲取一個叫做constructor的屬性耳峦,這個屬性包含一個指向prototype屬性所在函數(shù)的指針,可以說這是一個回路焕毫。
那么創(chuàng)建一個實例的過程是怎么樣的呢蹲坷?
當我們用構(gòu)造函數(shù)創(chuàng)建一個實例后,該實例內(nèi)部也會有一個指針指向構(gòu)造函數(shù)的原型對象咬荷,一般情況下冠句,這個指針的名字并不是prototype,我們必須記住一點幸乒,prototype只是函數(shù)內(nèi)部的一個屬性懦底。大部分瀏覽器的這個指針是__proto__
。我們看一張圖:
上圖很好的展示了構(gòu)造函數(shù)和實例對象之間原型的關(guān)系罕扎。我們在這里就不一一說明了聚唐。雖然我們通過__proto__
能訪問到原型對象,但這絕對不是推薦做法腔召。我們可以通過isPrototypeOf()
方法來確定對象之間是否存在這種關(guān)系:
console.log(Person.prototype.isPrototypeOf(person1)); // => true
上邊的代碼很好的演示了這一說法杆查,實例對象person1的原型就是構(gòu)造函數(shù)Person的prototype。臀蛛,還有一個方法是獲取原型對象getPrototypeOf()
:
console.log(Object.getPrototypeOf(person1) == Person.prototype); // => true
每當代碼讀取某個對象的某個屬性時亲桦,都會執(zhí)行一次搜索崖蜜,目標是具有給定名字的屬性。搜索首先從對象實例本身開始客峭。如果在實例中找到了具有給定名字的屬性豫领,則返回該屬性,如果沒有找到舔琅,則繼續(xù)搜索指針指向的原型對象等恐,在原型對象中查找具有給定名字的屬性,如果在原型對象中找到這個屬性备蚓,則返回該屬性课蔬。具體的例子我們就不演示了。
值得注意的是郊尝,當給對象的屬性賦值時二跋,如果屬性的名稱與原型對象的屬性名稱相同,對象內(nèi)部會創(chuàng)建這個屬性虚循,原型中的屬性保持不變同欠。样傍,我們可以這么認為横缔,原型對象大部分時候只提供讀取功能,它的目的是共享數(shù)據(jù)衫哥。但如果給引用類型的屬性賦值的時候會有不同的情況茎刚,比如修改原型的對象,數(shù)組就會導致原型的數(shù)據(jù)遭到修改撤逢。這個在JavaScript中Object的總結(jié)這篇文章中我已經(jīng)詳細的給出了解釋膛锭。
2.原型與in操作符
通過上邊的距離,我們大概明白了對象屬性與原型之間的關(guān)系蚊荣,那么現(xiàn)在就引出了一個問題初狰。如何區(qū)分某個屬性是來自對象本身還是原型呢?為了解決這個問題互例,我們引出in
操作符奢入。
有兩種方式使用in操作符:單獨使用和在for-in循環(huán)中使用。在單獨使用時媳叨,in操作符會在通過對象能夠訪問給定屬性時返回true腥光,無論該屬性存在于實例中還是原型中。我們看下邊這個例子:
function Person() {
}
Person.prototype.name = "James";
Person.prototype.sayName = function () {
console.log(this.name);
};
const person1 = new Person();
console.log(person1.hasOwnProperty("name")); // => false
console.log("name" in person1); // => true
hasOwnProperty()
方法能夠判斷對象本身是否存在某個屬性糊秆,而in能夠判斷對象是否能夠訪問某個屬性武福,結(jié)合這兩種方法,我們就能判斷某個屬性的來源痘番,我們舉個簡單的例子:
function hasPrototypeProperty(object, name) {
return (!object.hasOwnProperty(name)) && (name in object);
}
console.log(hasPrototypeProperty(person1, "name")); // => true
for-in可以遍歷對象中的屬性捉片,**但是要依賴屬性中的enumerable
這個特性的值平痰,如果這個值為false,那么就無法遍歷到屬性伍纫,跟for-in很相似的方式是Object.keys()
他返回一個字符串數(shù)組觉增,如果要想遍歷出對象的屬性,忽略enumerable
的影響翻斟,可以使用Object.getOwnPropertyNames()
這個方法逾礁,下邊是一個簡單的例子:
function Person() {
}
Person.prototype.name = "James";
Person.prototype.sayName = function () {
console.log(this.name);
};
const person1 = new Person();
console.log(hasPrototypeProperty(person1, "name")); // => true
Object.defineProperty(person1, "age", {
enumerable: false
});
for (const pro in person1) {
console.log(pro);
}
const keys = Object.keys(person1);
console.log(keys);
const keys1 = Object.getOwnPropertyNames(person1);
console.log(keys1);
3.原型的動態(tài)性
在上邊的內(nèi)容中,我們已經(jīng)明白访惜,JavaScript中尋找屬性或方法是通過搜索來實現(xiàn)的嘹履,因此我們可以動態(tài)的為原型添加屬性和方法。這一方面沒什么好說的债热,但有一點值得注意砾嫉,如果把原型修改為另一個對象,就會出現(xiàn)問題窒篱。焕刮,還是先看一個實例:
function Person() {
}
const person = new Person();
Person.prototype = {
constructor: Person,
name: "James",
sayName: function () {
console.log(this.name);
}
};
console.log(person.sayName()); // 會報錯
上邊的代碼會報錯,根本原因是對象的原型對象指向了原型墙杯,而不是指向了構(gòu)造函數(shù)配并,這就好比這樣的代碼:
var person1 = person;
var person2 = person;
person1 = son;
上邊的代碼中,person1換了一個對象高镐,但是person2依然指向了person溉旋。用下邊這個圖開看更直接4.原生對象的原型
這一小節(jié)是一個很重要的小結(jié),我們慢慢的增加了對JavaScript語言的理解嫉髓。原型模式的重要性不僅體現(xiàn)在創(chuàng)建自定義類型方面观腊,就連所有原生的引用類型,都是采用這種模式創(chuàng)建的算行。所有原生引用類型(Object梧油,Array,String等等)都在器構(gòu)造函數(shù)的原型上定義了方法州邢。
alert(typeof Array.prototype.sort); // => function
因此我們就通過這種手段為原生的引用類型擴展更多的屬性和方法儡陨。
String.prototype.startsWith = function (text) {
return this,indexOf(text) == 0;
}
這種方式非常像面向?qū)ο笳Z言中的分類,分類使用好了偷霉,能夠增加程序的可讀性迄委,但在JavaScript中,不建議用這種方法為原生對象做擴展类少。因為這么做的后果是可能讓程序失控叙身。