一、創(chuàng)建對象
-
使用工廠模式創(chuàng)建對象
?????用函數來封裝以特定接口創(chuàng)建對象的細節(jié),函數createPerson()能夠根據接受的參數來創(chuàng)建一個包含所有必要信息的Person對象,可以無數次地調用這個函數,而每次它都會返回一個包含三個屬性和一個方法的對象,工廠模式雖然解決了創(chuàng)建多個相似對象的問題,但卻沒有解決對象識別的問題,即怎樣知道一個對象的類型;
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person1 = createPerson('name1', 12, 'job1');
var person2 = createPerson('name2', 34, 'job2');
-
使用構造函數創(chuàng)建對象:
使用new操作符創(chuàng)建對象實例的四個步驟:
1)創(chuàng)建一個新對象;
var obj = {};
2)將構造函數的作用域賦給新對象(因此this就指向了這個新對象);
// 設置新對象的constructor屬性為構造函數的名稱;
// 設置新對象的__proto__屬性指向構造函數的prototype對象;
obj.constructor = ClassA
obj.__proto__= ClassA.prototype;
3)執(zhí)行構造函數中的代碼(為這個新對象添加屬性);
// 使用新對象調用函數,函數中的this被指向新實例對象
ClassA.call(obj);
4)返回新對象;
?????構造函數內部使用了this變量,對構造函數使用new運算符,可以生成實例,并且this變量會綁定在實例對象上;
?????使用new操作符創(chuàng)建對象
// 與工廠模式的不同之處
// 沒有顯示的創(chuàng)建對象
// 直接將屬性和方法賦給了this對象
// 沒有return語句
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.type = 'person';
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person('name1',23,'job1');
var person2 = new Person('name2',12,'job2');
console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true
console.log(person1 instanceof Object); // true
console.log(person2 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Person); // true
// 缺點:浪費內存,每個Person實例都包含一個不同的Function實例
console.log(person1.sayName == person2.sayName); // false
console.log(person1.type == person2.type); // true
// 改進:將sayName屬性設置成全局的sayName函數
// person1和person2對象共享了全局作用域中定義的sayName函數
// 缺點:如果對象需要定義很多方法,那就需要定義多個全局函數
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.type = 'person';
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person('name1',23,'job1');
var person2 = new Person('name2',12,'job2');
console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true
console.log(person1 instanceof Object); // true
console.log(person2 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Person); // true
console.log(person1.sayName == person2.sayName); // true
console.log(person1.type == person2.type); // true
-
使用Prototype模式創(chuàng)建對象:
?????js規(guī)定每一個構造函數都有一個prototype屬性,指向另一個對象,這個對象的所有屬性和方法,都會被構造函數的實例繼承,因此我們可以把那些不變的屬性和方法直接定義在prototype對象上,這樣就保證了不變的屬性和方法不會被多次生成;
function Person(){}
Person.prototype.name = 'name1';
Person.prototype.age = 23;
Person.prototype.job = 'job1';
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.sayName();
console.log(person1.sayName == person2.sayName); // true
console.log(Person.prototype.isPrototypeOf(person1)); // true
console.log(Person.prototype.isPrototypeOf(person2)); // true
person1.name = 'name2'; // 設置后可以阻止訪問原型中的屬性
console.log(person1.hasOwnProperty('name')); // true
delete person1.name; // delete操作符可以完全刪除實例屬性,從而可以重新訪問原型中的屬性
console.log(person1.name); // name1
console.log(person1.hasOwnProperty('name')); // false
console.log('name' in person1); // true in操作符不管該屬性是位于實例還是原型中,只要能訪問到到返回true
console.log(hasPrototypeProperty(person1,'name')); // true
person1.name = 'name3';
console.log(hasPrototypeProperty(person1,'name')); // false
for-in循環(huán):
// 返回所有能夠被對象訪問、可枚舉的屬性
// 屬性既包括實例中的屬性也包括原型中的屬性
// 屏蔽了原型中不可枚舉屬性的實例屬性也會for-in循環(huán)中返回
var o = {
toString: function() {
return 'My Object';
}
};
for (var prop in o) {
if (prop == 'toString') {
alert('Found toString');
}
}
Object.keys()方法:
// 獲取對象上所有可枚舉的實例屬性
function Person(){}
Person.prototype.name = 'name1';
Person.prototype.age = 25;
Person.prototype.job = 'job1';
Person.sayName = function(){
alert(this.name);
}
var keys = Object.keys(Person.prototype);
console.log(keys); // ["name", "age", "job"]
var person1 = new Person();
var person1Keys = Object.keys(person1);
console.log(person1Keys); // []
person1.name = 'name2';
person1.age = 23;
var person1Keys = Object.keys(person1);
console.log(person1Keys); // ["name", "age"]
Object.getOwnPropertyNames()方法
// 返回對象的所有實例屬性,包括不可枚舉的constructor屬性
function Person(){}
Person.prototype.name = 'name1';
Person.prototype.age = 25;
Person.prototype.job = 'job1';
Person.sayName = function(){
alert(this.name);
}
var keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // ["constructor", "name", "age", "job"]
var person1 = new Person();
person1.name = 'name1';
var person1Keys = Object.getOwnPropertyNames(person1);
console.log(person1Keys); // ['name']
二、繼承
???????原型鏈:B繼承了A,B創(chuàng)建了實例instance,當調用instance的方法時,會經歷三個步驟:1)搜索實例instance;2)搜索B.prototype;3)搜索A.prototype;最后一步才會找到要調用的方法,在找不到屬性或方法的情況下,搜索過程總是要一環(huán)一環(huán)地前行到原型鏈末端才會停下來;
???????B繼承了A,而A繼承了Object,當調用instance.toString()時,實際上調用的是保存在Object.prototype中的方法;
- 原型鏈繼承
function Animal() {
this.colors = ['red', 'green', 'blue'];
}
function Cat() {}
Cat.prototype = new Animal();
var cat1 = new Cat();
// 包含引用類型值的原型屬性會被所有實例共享
// 創(chuàng)建子類型的實例時,不能向超類型的構造函數中傳遞參數
cat1.colors.push('black');
console.log(cat1.colors); // ["red", "green", "blue", "black"]
var cat2 = new Cat();
console.log(cat2.colors); // ["red", "green", "blue", "black"]
// 使用prototype模式綁定
function Animal(){
this.species = "動物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
// Cat的prototype對象指向Animal的實例,Cat的所有實例也就能繼承Animal了
Cat.prototype = new Animal();
// 為避免繼承鏈的紊亂
// 任何一個prototype對象都有一個constructor屬性,指向它的構造函數
// 上一行將Cat.prototype.constructor指向了Animal,因此需要將Cat.prototype.constructor指回Cat
// 每一個實例都有一個constructor屬性,默認調用prototype對象的constructor屬性
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(Cat.prototype.constructor == Animal); // false
alert(cat1.constructor == Cat.prototype.constructor); // true
alert(cat1.constructor == Animal); // false
alert(cat1.species); // 動物
- 借用構造函數
// 使用構造函數綁定
function Animal(age) {
this.age = age;
this.species = "動物";
this.colors = ['red','green','blue'];
}
function Cat(name, color) {
// 繼承了Animal,同時傳遞了參數
Animal.call(this, 24); // 此處應使用call方法
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛", "黃色");
cat1.colors.push('black');
console.log(cat1.colors); // ["red", "green", "blue", "black"]
console.log(cat1.species); // 動物
console.log(cat1.age); // 24
var cat2 = new Cat('二毛','白色');
// 解決了引用類型值的原型屬性會被所有實例共享的問題
console.log(cat2.colors); // ["red", "green", "blue"]
// 缺陷:方法都在構造函數中定義了,函數復用就無從談起了
- 組合繼承:
// 將原型鏈和借用構造函數技術組合到一起,集二者之長
// 使用instanceof和isPrototypeOf可以識別基于組合繼承創(chuàng)建的對象
function Animal(name){
this.name = name;
this.colors = ['red','blue','green'];
}
Animal.prototype.sayName = function(){
console.log(this.name);
}
function Cat(name,age){
Animal.call(this,name); // 繼承屬性,第二次調用Animal()
this.age = age;
}
// 繼承方法
Cat.prototype = new Animal(); // 第一次調用Animal()
Cat.prototype.sayAge = function(){
console.log(this.age);
}
var cat1 = new Cat('大毛',3);
cat1.colors.push('black');
console.log(cat1.colors); // ["red", "blue", "green", "black"]
cat1.sayName(); // 大毛
cat1.sayAge(); // 3
var cat2 = new Cat('二毛',2);
console.log(cat2.colors); // ["red", "blue", "green"]
cat2.sayName(); // 二毛
cat2.sayAge(); // 2
- 原型式繼承:
// 必須有一個對象來作為原型
// object方法對傳入其中的對象執(zhí)行了一次淺拷貝
// 缺陷:包含引用類型值的屬性始終共享相應的值
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var animal = {
name: '大毛',
colors: ['red', 'green', 'blue']
}
var cat1 = object(animal);
cat1.name = '二毛';
cat1.colors.push('black');
var cat2 = object(animal);
cat2.name = '三毛';
cat2.colors.push('yellow');
console.log(animal.colors); // ["red", "green", "blue", "black", "yellow"]
// Object.create()方法
var animal = {
name: '大毛',
colors: ['red', 'green', 'blue']
}
var cat1 = Object.create(animal);
cat1.name = '二毛';
cat1.colors.push('black');
var cat2 = Object.create(animal);
cat2.name = '三毛';
cat2.colors.push('yellow');
console.log(animal.colors); // ["red", "green", "blue", "black", "yellow"]
// object方法
var Chinese = {
nation: '中國'
};
// 將子對象的prototype屬性指向父對象,從而使得子對象與父對象連在一起
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var Doctor = object(Chinese);
Doctor.career = '醫(yī)生';
alert(Doctor.nation); // 中國
- 寄生式繼承:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
// 與寄生構造函數和工廠模式類似
// 創(chuàng)建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后再返回對象
function createAnother(original) {
var clone = object(original); // 通過調用函數創(chuàng)建一個新對象
clone.sayHi = function() { // 以某種方式來增強這個對象,但不能做到函數復用使得效率不高
console.log('hi'); // 新對象不僅有original的所有屬性和方法,而且還有自己的sayHi方法
};
return clone; // 返回這個對象
}
var animal = {
name: '大毛',
colors: ['red', 'green', 'blue']
};
var cat = createAnother(animal);
cat.sayHi(); // hi
- 寄生組合式繼承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(cat, animal) {
var prototype = object(animal.prototype); // 創(chuàng)建超類型原型的一個副本
// 增強對象,為創(chuàng)建的副本添加constructor屬性,從而彌補因重寫原型而失去的默認constructor屬性
prototype.constructor = cat;
cat.prototype = prototype; // 將新創(chuàng)建的副本對象賦值給了類型的原型
}
function animal(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
animal.prototype.sayName = function() {
console.log(this.name);
}
function cat(name, age) {
animal.call(this, name);
this.age = age;
}
inheritPrototype(cat, animal);
cat.prototype.sayAge = function() {
console.log(this.age);
}
var cat1 = new cat('二毛', 3);
cat1.sayName(); // 二毛
cat1.sayAge(); // 3
- 直接繼承prototype(紅寶書未提到,阮一峰提到的一種方法)
// 直接繼承prototype
function Animal(){}
Animal.prototype.species = "動物";
function Cat(name,color){
this.name = name;
this.color = color;
}
// Cat()直接跳過Animal(),直接繼承Animal.prototype
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
// 此時Cat.prototype和Animal.prototype現在指向了同一個對象
// 任何對Cat.prototype的修改都會反應到Animal.prototype
alert(Animal.prototype.constructor); // Cat
- 利用空對象作為中介(其實就是原型式繼承方法)
// 利用空對象作為中介
function Animal(){
this.species = "動物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
// F是空對象幾乎不占內存,此時修改Cat的prototype對象,不會影響到Animal的prototype對象
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
alert(Animal.prototype.constructor); // Animal
// 改進版
function Animal(){
this.species = "動物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
function extend(Child,Parent){
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
// 為子對象設一個uber屬性,這個屬性直接指向父對象的prototype屬性,
// 這相當于在子對象上打開了一條通道,可以直接調用父對象的方法,實現繼承的完備性
Child.uber = Parent.prototype;
}
extend(Cat,Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species);
- 拷貝繼承(網上的一種方法)
// 拷貝繼承
function Animal(){}
Animal.prototype.species = "動物";
function Cat(name,color){
this.name = name;
this.color = color;
}
// 將父對象的prototype對象中的屬性一一拷貝給Child對象的prototype對象
function extend2(Child,Parent){
var p = Parent.prototype;
var c = Child.prototype;
for(var i in p){
c[i] = p[i];
}
c.uber = p;
}
extend2(Cat,Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動物
三、深拷貝和淺拷貝
- 淺拷貝方法
// 淺拷貝方法
var Chinese = {
nation: '中國'
};
// 將父對象的屬性全部拷貝給子對象,也能實現繼承
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
var Doctor = extendCopy(Chinese);
Doctor.career = '醫(yī)生';
alert(Doctor.nation); // 中國
// 存在的問題
// 如果父對象的屬性等于數組或另一個對象,那么子對象獲得的只是一個內存地址
// 而不是真正的拷貝,因此存在父對象被篡改的可能
var Chinese = {
nation: '中國'
};
Chinese.birthPlaces = ['北京','上海','香港'];
// 將父對象的屬性全部拷貝給子對象,也能實現繼承
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
var Doctor = extendCopy(Chinese);
Doctor.career = '醫(yī)生';
Doctor.birthPlaces.push('廈門');
alert(Doctor.birthPlaces); // 北京,上海,香港,廈門
alert(Chinese.birthPlaces); // 北京,上海,香港,廈門
- 深拷貝
// 深拷貝
var Chinese = {
nation: '中國'
};
Chinese.birthPlaces = ['北京', '上海', '香港'];
// 實現對象和數組和深拷貝
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i],c[i]);
}else{
c[i] = p[i];
}
}
return c;
}
var Doctor = deepCopy(Chinese);
Doctor.birthPlaces.push('廈門');
alert(Doctor.birthPlaces); // 北京,上海,香港,廈門
alert(Chinese.birthPlaces); // 北京,上海,香港
總結:
- 原型鏈繼承
function Animal(){
this.colors = ['red','blue','green'];
}
function Cat(){}
Cat.prototype = new Animal();
- 借用構造函數繼承
function Animal(){
this.colors = ['red','blue','green'];
}
function Cat(){
Animal.call(this)
}
- 組合繼承
function Animal(){
this.colors = ['red','blue','green'];
}
Animal.prototype.sayColors = function(){
console.log(this.colors);
}
function Cat(){
Animal.call(this); // 繼承屬性
}
Cat.prototype = new Animal(); // 繼承方法
- 原型式繼承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var animal = {
name: '大毛',
colors: ['red', 'green', 'blue']
}
var cat1 = object(animal);
- 寄生式繼承
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original);
clone.sayHi = function() {
console.log('hi');
};
return clone;
}
var animal = {
name: '大毛',
colors: ['red', 'green', 'blue']
};
var cat = createAnother(animal);
- 寄生組合式繼承:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(cat, animal) {
var prototype = object(animal.prototype);
prototype.constructor = cat;
cat.prototype = prototype;
}
function animal(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
animal.prototype.sayName = function() {
console.log(this.name);
}
function cat(name, age) {
animal.call(this, name);
this.age = age;
}
inheritPrototype(cat, animal);