之前講過什么是面向?qū)ο缶幊?/a>府适,然后寫了兩個簡單的小實例唯绍,今天繼續(xù)研究。
一 包裝對象
1.什么是包裝對象
先來看這段代碼:
var str = 'hello';
alert( typeof str ); //string
str.charAt(0);
str.indexOf('e');
很明顯,str的類型是string窘游,按理說只有對象才會有屬性和方法窜觉,為什么字符串可以使用方法呢总珠?
其實這就是包裝對象。
分別與數(shù)值、字符串椰于、布爾值相對應的
Number
、String
骨望、Boolean
這三個原生對象可以把原始類型的值變成(包裝成)對象粪牲。
這三個對象作為構(gòu)造函數(shù)使用(帶有new)時,可以將原始類型的值轉(zhuǎn)為對象赢织;作為普通函數(shù)使用時(不帶有new)亮靴,可以將任意類型的值,轉(zhuǎn)為原始類型的值于置。
2.原始類型的自動轉(zhuǎn)換
在上面的代碼中茧吊,執(zhí)行到str.charAt(0);
的時候, 基本類型會找到對應的包裝對象類型八毯,然后包裝對象把所有的屬性和方法給了基本類型搓侄,然后包裝對象消失。這就叫原始類型的自動轉(zhuǎn)換话速。
但是這個對象是臨時的讶踪,我們不能對它進行修改。
var str = 'hello';
alert(str.length); //5
str.number = 10;
alert( str.number ); //undefined
上面代碼中泊交,str是一個字符串俊柔,本身不是對象,不能調(diào)用length屬性活合。JavaScript引擎自動將其轉(zhuǎn)為包裝對象雏婶,在這個對象上調(diào)用length屬性。這個臨時對象是只讀的白指,無法修改留晚。所以,字符串無法添加新屬性告嘲。
3.包裝對象自定義方法
包裝對象還可以在原型上添加自定義方法和屬性错维,供原始類型的值直接調(diào)用。
var str = 'hello';
String.prototype.lastValue = function(){
return this.charAt(this.length-1);
};
alert( str.lastValue() ); //o
二 原型鏈
1 什么是原型鏈
在下面的例子中橄唬,我們創(chuàng)建了兩個構(gòu)造函數(shù)赋焕,其中LandRover的原型對象又是以Car的原型對象為原型創(chuàng)建的。圖示中的紅色線即為原型鏈仰楚。
function Car(Logo) {
this.logo = logo || "unknown name";
}
//設(shè)置Car的prototype屬性
Car.prototype = {
start:function() {
console.log('%s start',this.logo);
},
run:function() {
console.log('%s run',this.logo);
},
stop:function() {
console.log('%s stop',this.logo);
}
};
//又創(chuàng)建一個構(gòu)造函數(shù)landRover
function LandRover(serialno) {
this.serialNumber = serialno; //序列號
}
//設(shè)置LandRover的prototype屬性
LandRover.prototype = new Car("landRover"); //使用Car創(chuàng)建一個新對象隆判,并把它賦給LandRover構(gòu)造函數(shù)的原型
//創(chuàng)建LandRover對象
var landRover1 = new LandRover("1000");
var landRover2 = new LandRover("1001");
Car.prototype對象也是由Object來創(chuàng)建的犬庇,所以它的原型指向Object.prototype
2 修改、刪除屬性時侨嘀,只能修改臭挽、刪除對象自身的屬性
①訪問:假如我們想訪問landRover1的serialNumber屬性,那么直接在它的本身就可以找到咬腕;如果我們要訪問landRover1的toString方法欢峰,它本身是沒有的,就會順著原型鏈一直網(wǎng)上尋找涨共,知道找到或者沒有纽帖。
②修改及刪除:
操作1:
landRover1.Logo = landRover1;
因為landRover1本身并沒有Logo,雖然它可以訪問landRover.protoype的Logo屬性举反,但是它無法修改抛计,所以這時它會在自己身上加一個Logo屬性,并賦值為landRover1照筑,對landRover.protoype中的Logo屬性并無影響
操作2:
delete(landRover1.Logo);
我們把landRover1的Logo屬性刪除了吹截,但是當我們訪問landRover1.Logo時,還是可以訪問到的凝危,因為它刪除的是自身的屬性波俄,對原型上的屬性無影響,即使再次做修改操作蛾默,也是無效的
3 在javascript中懦铺,一切都是對象
①函數(shù)在JS中也是對象,所以支鸡,Car和LandRover 這些構(gòu)造函數(shù)其實也是一個對象冬念,既然是對象,肯定就有原型牧挣,而Car和LandRover 則是由new Function創(chuàng)建出來的急前,所以LandRover和Car都有一個原型即Function.prototype。
②而Function.prototype是由new Object創(chuàng)建出來的瀑构,所以Function.prototype的原型指向Object的原型裆针,也就是Object.prototype。
③Object.prototype是所有原型的頂層寺晌。
三 面向?qū)ο蟮囊恍傩院头椒?/h3>
1.hasOwnProperty()
1)作用:判斷傳入的屬性是否為對象自身的屬性
理解原型查找原理:對象查找先在該構(gòu)造函數(shù)內(nèi)查找對應的屬性世吨,如果該對象沒有該屬性的話,那么javascript會試著從該原型上去查找呻征,如果原型對象中也沒有該屬性的話耘婚,那么它們會從原型中的原型去查找,直到查找的Object.prototype也沒有該屬性的話陆赋,那么就會返回undefined沐祷。
2)實例
因此我們僅想在該對象內(nèi)查找的話嚷闭,為了提高性能,我們可以使用hasOwnProperty()來判斷該對象內(nèi)有沒有該屬性戈轿,如果有的話,就執(zhí)行代碼(使用for-in循環(huán)查找)阵子,如下:
var obj = {
"name":'moon',
"age":'28'
};
// 使用for-in循環(huán)
for(var i in obj) {
if(obj.hasOwnProperty(i)) {
console.log(obj[i]); //moon 28
}
}
如上使用for-in循環(huán)查找對象里面的屬性思杯,但是我們需要明白的是:for-in循環(huán)查找對象的屬性,它是不保證順序的挠进,for-in循環(huán)和for循環(huán)最本質(zhì)的區(qū)別是:for循環(huán)是有順序的色乾,for-in循環(huán)遍歷對象是無序的,因此我們?nèi)绻枰獙ο蟊WC順序的話领突,可以把對象轉(zhuǎn)換為數(shù)組來暖璧,然后再使用for循環(huán)遍歷即可;
2.constructor
1)作用:查看對象的構(gòu)造函數(shù)
function Aaa(){
}
var a1 = new Aaa();
alert( a1.constructor ); //Aaa
2)每個原型都會自動添加constructor屬性
prototype對象有一個constructor屬性,默認指向prototype對象所在的構(gòu)造函數(shù)君旦。
function Aaa(){
}
//系統(tǒng)會自動添加上
//Aaa.prototype.constructor = Aaa;
//每一個函數(shù)都會有的澎办,都是自動生成的
由于constructor屬性定義在prototype對象上面,意味著可以被所有實例對象繼承金砍。
3)避免修改construtor屬性
function A() {}
var a = new A();
a instanceof A // true
function B() {}
A.prototype = B.prototype;
a instanceof A // false
上面代碼中局蚀,a是A的實例。修改了A.prototype以后恕稠,constructor屬性的指向就變了琅绅,導致instanceof運算符失真。
所以鹅巍,修改原型對象時千扶,一般要同時校正constructor屬性的指向。
// 避免這種寫法
C.prototype = {
method1: function (...) { ... },
// ...
};
// 較好的寫法
C.prototype = {
constructor: C,
method1: function (...) { ... },
// ...
};
// 好的寫法
C.prototype.method1 = function (...) { ... };
3.instanceof運算符
1)作用:對象與構(gòu)造函數(shù)在原型鏈上是否有關(guān)系
function Aaa(){
}
var a1 = new Aaa();
alert( a1 instanceof Object ); //true
2)可以用來判斷數(shù)據(jù)類型
4.toString() :
1)作用:把對象轉(zhuǎn)成字符串
這個方法是原型上的骆捧,我們可以修改這個方法澎羞,但是不建議這樣做
var arr = [1,2,3];
Array.prototype.toString = function(){
return this.join('+');
};
alert( arr.toString() ); //'1+2+3'
2)利用toString方法來做類型判斷
四 對象的繼承
1.什么是繼承
- 在原有對象的基礎(chǔ)上,略作修改敛苇,得到一個新的對象
- 不影響原有對象的功能
當新創(chuàng)建的對象添加了新的屬性和方法時煤痕,肯定不能直接用new+構(gòu)造函數(shù)這種方法來直接創(chuàng)建了,因為在添加屬性或者方法的時候接谨,也會影響到原來的對象摆碉。那我們應該如何實現(xiàn)呢?
2.拷貝繼承
方式:
- 屬性的繼承 : 調(diào)用父類的構(gòu)造函數(shù) call
- 方法的繼承 : for in : 拷貝繼承 (jquery也是采用拷貝繼承extend)
function Person(name) {
this.name = name;
}
Person.prototype.talk = function(name) {
console.log('My name is '+this.name);
};
var p1 = new Person('moon');
// console.log(p1.name);
// p1.talk();
function Person1(name,age) {
Person.call(this,name); //這里需要改變this的指向
this.age = age;
}
function extend(obj1,obj2){
for (var attr in obj2) {
obj1[attr] = obj2[attr]; //這個地方不能用"."
}
}
extend(Person1.prototype,Person.prototype);
var p2 = new Person1('yao',18);
console.log(p1.name); //moon
console.log(p2.name); //yao
p2.talk(); //My name is yao
3.類繼承
利用構(gòu)造函數(shù)(類)繼承的方式脓豪。
首先聲明巷帝,JS中是沒有類的概念的,這里說的類是把JS中的構(gòu)造函數(shù)看做類扫夜。
先來看看所謂的一步繼承法
function Person1() {
this.name = [1,2,3];
}
Person1.prototype.talk = function(name) {
alert(this.name);
};
function Person2() {}
Person2.prototype = new Person1();
var p1 = new Person2();
p1.talk();
alert( p1.constructor ); //Person1
p1.name.push(4);
var p2 = new Person2();
console.log(p1.name); //[1,2,3,4]
console.log(p2.name); //[1,2,3,4]
缺陷:
1.修改了構(gòu)造函數(shù)的指向
2.創(chuàng)建出來的對象之間會相互影響
正確做法:
屬性和方法分開繼承楞泼。
function Person1() {
this.name = [1,2,3];
}
Person1.prototype.talk = function(name) {
alert(this.name);
};
function Person2() {
Person1.call(this); //屬性繼承仍采取之前的call調(diào)用父類的構(gòu)造函數(shù)的方法
}
function Foo() {} //避免繼承屬性驰徊,只繼承方法
Foo.prototype = Person1.prototype;
Person2.prototype = new Foo();
Person2.prototype.constructor = Person2; //修正指向問題
var p1 = new Person2();
p1.talk();
alert( p1.constructor );
p1.name.push(4);
var p2 = new Person2();
console.log(p1.name); //[1,2,3,4]
console.log(p2.name); //[1,2,3]
4.原型繼承
利用原型prototype來實現(xiàn)繼承,一般用來下面這種情況:
function cloneObj(obj){
var F = function(){};
F.prototype = obj;
return new F();
}
var a = {
name : '小明'
};
var b = cloneObj(a);
alert( b.name ); // 小明
b.name = '小強';
alert( b.name ); //小強
alert( a.name ); //小明
我們來看一下關(guān)系圖堕阔,假設(shè) new F() =f1
第一次執(zhí)行alert( b.name );時棍厂,因為b沒有這個屬性,所以它會順著原型鏈往上找超陆,知道找到a下面的name:小明
執(zhí)行b.name = '小強';因為b本身是沒有name這個屬性的牺弹,它雖然可以訪問a下面的name屬性,但是無法修改时呀,所以這時它會在自己身上加一個name屬性张漂,并賦值為“小強”,對a中的name屬性并無影響
執(zhí)行alert( a.name ); 的時候谨娜,根據(jù)原型鏈的查找規(guī)則航攒,它肯定不會查找到b下面去,更何況a本身就有name屬性趴梢,所以彈出來的還是“小明”
5.小結(jié):
拷貝繼承: 通用型的,有new或無new的時候都可以
類式繼承: 通過new構(gòu)造函數(shù)形式
原型繼承: 無new的對象
如有錯誤漠畜,煩請告知,非常感謝坞靶。