創(chuàng)建對象
對象是無序?qū)傩缘募?,其屬性可以包含基本值,對象或者函?shù),即由若干個(gè)“鍵值對”(key-value)構(gòu)成。
創(chuàng)建對象有3種方法:1.對象字面量;2.構(gòu)造函數(shù);3.對象繼承。
Object對象
JavaScript原生提供一個(gè)Object對象,所有其他對象都繼承自這個(gè)對象。
Object本身也是一個(gè)構(gòu)造函數(shù),可以直接通過它來生成新對象。
通過new Object()的寫法創(chuàng)建新對象,與字面量的寫法o = {}是等價(jià)的。工廠模式
工廠模式就是我們提供一個(gè)模子,然后通過這個(gè)模子復(fù)制出我們需要的對象。
function createPerson(name, age) {
// 1.聲明一個(gè)中間對象,該對象就是工廠模式的模子
var o = new Object();
// 2.添加屬性和方法
o.name = name;
o.age = age;
o.getName = function() {
return this.name;
}
// 3.返回中間對象
return o;
}
// 創(chuàng)建對象實(shí)例
var p1 = createPerson('Leo', 26);
var p2 = createPerson('Tom', 20);
console.log(p1 instanceof Object); // true
console.log(p1 instanceof createPerson); // false
工廠模式缺點(diǎn):無法識(shí)別對象實(shí)例的類型,因?yàn)闃?gòu)造函數(shù)都指向Object。
- 構(gòu)造函數(shù)
JavaScript中使用構(gòu)造函數(shù)作為對象的模板,專門用來生成對象。
構(gòu)造函數(shù)特點(diǎn):- 生成對象的時(shí)候,必需用new命令,調(diào)用構(gòu)造函數(shù)。
- 函數(shù)體內(nèi)部使用了this關(guān)鍵字,代表了所要生成的對象實(shí)例。
new 命令
new命令的作用就是執(zhí)行構(gòu)造函數(shù),并返回一個(gè)實(shí)例對象。
new命令本身就可以指向構(gòu)造函數(shù),所有new后面的構(gòu)造函數(shù)可以帶括號,也可以不帶。
如果沒有使用new命令,構(gòu)造函數(shù)就變成了普通函數(shù),并不會(huì)生成實(shí)例對象。
new 命令的原理 - 創(chuàng)建一個(gè)新對象,作為將要返回的實(shí)例對象。
- 將這個(gè)新對象的原型,指向構(gòu)造函數(shù)的prototype屬性。
- 執(zhí)行構(gòu)造函數(shù),將構(gòu)造函數(shù)內(nèi)部的this,指向這個(gè)新對象。
- 返回新對象。
在構(gòu)造函數(shù)內(nèi)部,this指的是這個(gè)新建的空對象,所有針對this的操作,都會(huì)發(fā)生在這個(gè)空對象上。
如果構(gòu)造函數(shù)內(nèi)部有return語句,而且后面跟一個(gè)對象,new命令會(huì)返回這個(gè)對象,否則返回this對象。
如果普通函數(shù)(內(nèi)部沒有this關(guān)鍵字)使用new命令,則會(huì)返回一個(gè)空對象。
var A = function () {
this.x = 1;
return {x: 2};
}
console.log( (new A()).x ); // 2
new 命令簡化的內(nèi)部流程:
// 創(chuàng)建構(gòu)造函數(shù)
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = function () {
return this.name;
}
}
Person.prototype.getAge = function () {
return this.age;
}
// 模擬new命令
function New(func) {
// 將類數(shù)組arguments對象轉(zhuǎn)換為數(shù)組
var args = [].slice.call(arguments);
// 取出構(gòu)造函數(shù)
var func = args.shift();
// 創(chuàng)建新對象,該對象的原型指向繼承構(gòu)造函數(shù)的原型,繼承構(gòu)造函數(shù)的屬性和方法
var obj = Object.create(func.prototype);
// result為構(gòu)造函數(shù)執(zhí)行的結(jié)果,通過apply將構(gòu)造函數(shù)內(nèi)的this指向修改為實(shí)例對象
var result = func.apply(obj, args);
// 當(dāng)構(gòu)造函數(shù)指定了返回的對象時(shí),New的執(zhí)行結(jié)果就返回該對象,否則返回實(shí)例對象
return (typeof result === 'object' && result != null) ? result : obj;
}
var p1 = New(Person, 'Tom', 20);
console.log(p1.getName()); // Tom
console.log(p1.getAge()); // 20
console.log(p1 instanceof Person); // true
// 其他的一些特殊處理,將var p1=New(Person,'Tom',20)等效于var p1 = new Person('Tom',20);
- 構(gòu)造函數(shù)模式
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = function () {
console.log(this.name);
};
}
var p1 = new Person('Leo', 26);
var p2 = new Person('Tom', 20);
console.log(p1.getName === p2.getName); // false
構(gòu)造函數(shù)模式的缺點(diǎn):
每次創(chuàng)建實(shí)例都要?jiǎng)?chuàng)建一遍方法,這既沒有必要,又浪費(fèi)系統(tǒng)資源。
原型
每個(gè)函數(shù)都有一個(gè)prototype屬性,該屬性指向一個(gè)對象,這個(gè)對象就是原型對象。
構(gòu)造函數(shù)的prototype屬性指向?qū)嵗龑ο蟮脑蛯ο蟆?br> 每個(gè)原型對象都有一個(gè)constructor屬性,該屬性默認(rèn)指回原型對象的構(gòu)造函數(shù)。
每個(gè)實(shí)例對象都可以通過__proto__訪問該實(shí)例對象的原型對象。
原型對象的作用就是定義所有實(shí)例對象共享的屬性和方法。
實(shí)例對象可以看作從原型對象衍生出來的子對象,原型對象上的所有屬性和方法,都能被實(shí)例對象共享。
實(shí)例對象本身沒有某個(gè)屬性或方法時(shí),會(huì)到原型對象去尋找該屬性或方法。
原型對象上的變動(dòng)會(huì)立刻體現(xiàn)在所有實(shí)例對象上,原型上的屬性和方法被所有實(shí)例共享。組合模式
組合使用構(gòu)造函數(shù)和原型:
構(gòu)造函數(shù)中通過this綁定的屬性與方法稱為私有變量與方法,它們被當(dāng)前被某一個(gè)實(shí)例對象所獨(dú)有。
原型中的屬性和方法稱之為共有屬性與方法,它們可以被所有的實(shí)例對象共享。
// 私有屬性和方法
function Person(name, age) {
this.name = name;
this.age = age;
this.getAge = function () {
console.log(this.age);
}
}
// 共有屬性和方法
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
var p1 = new Person('Tom', 10);
var p2 = new Person('Leo', 26);
console.log(p1.getName === p2.getName); // true
組合模式的缺點(diǎn):構(gòu)造函數(shù)和原型沒有封裝在一起。
- 動(dòng)態(tài)原型模式
封裝在一個(gè)構(gòu)造函數(shù)中,在構(gòu)造函數(shù)中初始化原型。
function Person(name, age) {
this.name = name;
this.age = age;
// 第一調(diào)用構(gòu)造函數(shù)初始化原型
if (typeof this.getName !== "function") {
Person.prototype.getName = function () {
console.log(this.name);
}
}
}
var person1 = new Person('Leo', 26); // 初次調(diào)用構(gòu)造函數(shù)時(shí),初始化原型
var person2 = new Person('Tom', 20); // 原型已初始化
person1.getName(); // Leo
person2.getName(); // Tom
注意:使用動(dòng)態(tài)原型模式時(shí),不能使用對象字面量重寫原型,這樣做會(huì)切斷所有實(shí)例對象與新原型的聯(lián)系。
重寫原型對象會(huì)切斷現(xiàn)有原型與任何之前已存在的實(shí)例對象直接的聯(lián)系。
function Person(name, age) {
this.name = name;
this.age = age;
if (typeof this.getName !== "function") {
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
}
}
var p1 = new Person('Leo', 26);
var p2 = new Person('Tom', 20);
p2.getName(); // Tom
p1.getName(); // 報(bào)錯(cuò),p1.getName不存在
console.log(Person.prototype === p1.__proto__); // false
console.log(Person.prototype === p2.__proto__); // true
console.log(p1.__proto__ === p2.__proto__); // false
// 第一次調(diào)用構(gòu)造函數(shù)時(shí),重寫原型,添加原型方法getName
// 而此時(shí)實(shí)例p1的__proto__仍然指向以前的原型對,此原型沒有g(shù)etName方法,報(bào)錯(cuò)
// 第二次調(diào)用構(gòu)造函數(shù)的時(shí)候,實(shí)例p2已繼承原型方法getName
解決辦法:
function Person(name, age) {
this.name = name;
this.age = age;
if (typeof this.getName !== "function") {
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
return new Person(name, age); //返回一個(gè)實(shí)例
}
}
var p1 = new Person('Leo', 26); // 第一次調(diào)用時(shí)執(zhí)行if語句返回一個(gè)具有新原型的對象實(shí)例
var p2 = new Person('Tom', 20);
p2.getName(); // Tom
p1.getName(); // Leo
- 寄生構(gòu)造函數(shù)模式
除了使用new操作符,并把封裝的函數(shù)稱為構(gòu)造函數(shù)之外,這個(gè)模式和工廠模式一樣。
function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.getName = function () {
console.log(this.name);
};
return o;
}
var p1 = new Person('Leo', 26); // 使用new操作符
console.log(p1 instanceof Person) // false 該模式不能識(shí)別構(gòu)造函數(shù)
console.log(p1 instanceof Object) // true
這個(gè)模式可以在特殊情況下來為對象創(chuàng)建構(gòu)造函數(shù)。
假如我們需要?jiǎng)?chuàng)建一個(gè)具有額外方法的特殊數(shù)組,由于不能直接修改Array構(gòu)造函數(shù),可以使用該模式。
function SpecialArray() {
// 創(chuàng)建新數(shù)組
var arr = new Array();
// 給數(shù)組添加值
arr.push.apply(arr, arguments);
// 給數(shù)組添加額外的方法
arr.toPipedString = function () {
return this.join('|');
}
// 返回該數(shù)組
return arr;
}
var prople = new SpecialArray('Leo', 'Tom', 'Bob');
console.log(prople.toPipedString()); // Leo|Tom|Bob
- 穩(wěn)妥構(gòu)造函數(shù)模式
穩(wěn)妥對象,是指沒有公共屬性,而且其方法也不引用this的對象。
與寄生構(gòu)造函數(shù)模式有兩點(diǎn)不同:
新創(chuàng)建的實(shí)例方法不引用this;不使用new操作符調(diào)用構(gòu)造函數(shù)。
穩(wěn)妥構(gòu)造函數(shù)模式也跟工廠模式一樣,無法識(shí)別對象所屬類型。
function person(name, age){
var o = new Object();
o.getName = function(){
console.log(name);
};
o.getAge = function(){
console.log(age);
};
return o;
}
var p1 = person('Leo', 26);
p1.getName(); // Leo
p1.getAge(); // 26
console.log(p1.name); // undefined,只能通過getName()方法訪問name值
《JavaScript高級程序設(shè)計(jì)》
《JavaScript 標(biāo)準(zhǔn)參考教程》