創(chuàng)建對象的方案
1.字面量方式
//使用字面量的方式創(chuàng)建三個對象
var p1 = {
name: "小名",
age: 30,
sex: "male",
running: function () {
console.log(this.name + "is running!");
},
};
var p2 = {
name: "小紅",
age: 25,
sex: "female",
running: function () {
console.log(this.name + "is running!");
},
};
var p3 = {
name: "小麗",
age: 30,
sex: "female",
running: function () {
console.log(this.name + "is running!");
},
};
console.log(p1)
console.log(p2)
console.log(p3)
缺點:
- 1.做了很多重復工作
- 2.這樣創(chuàng)建的對象沒有標出所屬的類別
- 3.每創(chuàng)建一個對象,都需要在堆內(nèi)存中開辟一塊新內(nèi)存,存儲創(chuàng)建的running函數(shù)對象,如果創(chuàng)建100個對象,就創(chuàng)建了100running函數(shù),就需要100個這樣的堆內(nèi)存來存儲100個這樣的running函數(shù)對象
2.工廠模式
//使用工廠模式創(chuàng)建對象
function createPerson(name, age, sex) {
var p = {};
p.name = name;
p.age = age;
p.sex = sex;
p.running = function () {
console.log(this.name + "is running!");
};
return p;
}
var p1 = createPerson("小名", 30, "male");
var p2 = createPerson("小紅", 25, "female");
var p3 = createPerson("小麗", 30, "female");
console.log(p1);
console.log(p2);
console.log(p3);
缺點:
- 這樣創(chuàng)建的對象沒有標出所屬的類別
- 每創(chuàng)建一個對象,都需要在堆內(nèi)存中開辟一塊新內(nèi)存,存儲創(chuàng)建的running函數(shù)對象,如果創(chuàng)建100個對象,就創(chuàng)建了100running函數(shù),就需要100個這樣的堆內(nèi)存來存儲100個這樣的running函數(shù)對象
3.構造函數(shù)
3.1.什么是構造函數(shù)
當一個函數(shù)被new操作符
調(diào)用的時候,這個函數(shù)就是構造函數(shù)
new操作符調(diào)用的作用
- 1.在內(nèi)存中創(chuàng)建一個新的對象(空對象)
- 2.這個對象內(nèi)部的[[prototype]]屬性會被賦值為該構造函數(shù)的prototype屬性的值
- 3.構造函數(shù)內(nèi)部的this,會指向創(chuàng)建出來的新對象
- 4.執(zhí)行構造函數(shù)內(nèi)部的代碼(函數(shù)體代碼)
- 5.如果構造函數(shù)沒有返回非空對象,則返回創(chuàng)建出來的這個新對象
function person() {}
const p = new person();
console.log(p);
可以看到:通過構造函數(shù)創(chuàng)建出來的對象,有標注出來這個對象所屬的類的類名person的
說明,構造函數(shù)的調(diào)用創(chuàng)建出了一個有標明所屬類的類名的對象;那我們使用構造函數(shù)去創(chuàng)建對象,不就解決了創(chuàng)建出的帶向沒有標注出所屬類的類名的問題?
補充:
function person() {}
//1.當構造函數(shù)創(chuàng)建對象的時候,不需要傳參,可以省略小括號
const p1 = new person;
console.log(p1)
//2.獲取構造函數(shù)創(chuàng)建出來的對象所屬類的類名
console.log(p1.__proto__.constructor.name;
//3.為了區(qū)分構造函數(shù)和普通函數(shù)的區(qū)別,一般情況下,構造函數(shù)的名字會大寫
function Fish() {}
const fish = new Fish();
3.2使用構造函數(shù)創(chuàng)建對象
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.running = function () {
console.log(this.name + "is running!");
};
}
var p1 = new Person("小名", 30, "male");
var p2 = new Person("小紅", 25, "female");
var p3 = new Person("小麗", 30, "female");
console.log(p1);
console.log(p2);
console.log(p3);
優(yōu)點:
- 通過構造函數(shù)創(chuàng)建出的對象,有標注出所屬類的類名
缺點:
- 每創(chuàng)建一個對象,都需要在堆內(nèi)存中開辟一塊新內(nèi)存,存儲創(chuàng)建的running函數(shù)對象
- 如果創(chuàng)建100個對象,就創(chuàng)建了100running函數(shù),就需要100個這樣的堆內(nèi)存來存儲100個這樣的running函數(shù)對象,浪費內(nèi)存
4.構造函數(shù)和原型相結合
4.1對象的原型理解
每個對象都有一個特殊的內(nèi)置屬性[[prototype]]
,指向一個對象,這個對象叫做對象的原型
,
- 為了和函數(shù)的原型做區(qū)分,我們一般稱之為
隱式原型對象
早起的ECMA是沒有規(guī)范如何去查看內(nèi)置屬性[[prototype]]這個原型對象的
- 1.瀏覽器給對象提供了一個屬性
__proto__
,可以通過此屬性去查看對象的原型對象 - 2.ES5提供了一個方法
Object.getPrototype(obj)
,去查看目標對象的原型對象
var obj = { name: "why" };
console.log(obj.__proto__); //[Object: null prototype] {}
console.log(Object.getPrototypeOf(obj)); //[Object: null prototype] {}
console.log(obj.__proto__ === Object.getPrototypeOf(obj)); //true
可以看到通過__proto__
和Object.getPrototype(obj)
獲取到的同一個對象的原型對象,也是同一個對象
原型的用處
當我們從一個對象中獲取一個屬性的時候,它會觸發(fā)[[get]]
操作:
- 1.在當前對象obj中查找此屬性,如果找到,就返回對應的值
- 2.如果沒有找到,會去此對象的原型對象
obj.__proto__
上查找此屬性,如果找到,就返回對應的值 - 3.如果沒有找到,繼續(xù)去原型對象的原型對象
obj.__proto__.__proto__
上查找此屬性,如果找到,就返回對應的值 - ....
- 4.直到找到
Object.prototype
為止,,此時Object.prototype.__proto__
為null,則停止查找,返回undefined
var obj = { name: "why" };
obj.__proto__.age = 18;
console.log(obj.age); //18 雖然obj對象上沒有age屬性,但是在obj.__proto__對象上找到了age屬性,就返回其值18
4.2函數(shù)的原型理解
函數(shù)作為一個對象而言,它也是有內(nèi)置屬性[[prototype]]這個隱式原型對象的
作為函數(shù),函數(shù)還有一個屬性prototype,指向一個對象,我們一般稱之為顯式原型對象
函數(shù)作為構造函數(shù),被new調(diào)用的時候,會把內(nèi)部創(chuàng)建的
新的對象的[[prototype]]隱式原型對象
指向構造函數(shù)的prototype顯示原型對象
function foo() {}
console.log(foo.__proto__); //{}
console.log(foo.prototype); //{}
//函數(shù)作為構造函數(shù),被new調(diào)用的時候,
//會把內(nèi)部創(chuàng)建的新的對象的[[prototype]]隱式原型對象指向構造函數(shù)的prototype顯示原型對象
const f = new foo();
console.log(f.__proto__); //{}
console.log(foo.prototype); //{}
console.log(f.__proto__ === foo.prototype); //true
4.3函數(shù)的原型對象prototype上的constructor
4.3.1.查看函數(shù)原型對象上有哪些屬性
function foo() {}
console.log(foo.prototype); //{}
console.log(Object.getOwnPropertyDescriptors(foo.prototype));
說明,函數(shù)原型對象上有一個屬性constructor,并且constructor屬性是不可枚舉的
function foo() {}
console.log(foo.prototype.constructor === foo); //true 函數(shù)原型對象上的constructor屬性指向函數(shù)本身
//獲取函數(shù)原型對象上的constructor函數(shù)的名字
console.log(foo.prototype.constructor.name); //foo 函數(shù)的名字可以通過訪問函數(shù)的name屬性獲取
說明:函數(shù)原型對象上的constructor屬性指向函數(shù)本身
4.4改變函數(shù)的prototype的指向
獲取一個對象的屬性,其實實在進行[[get]]操作:
先在當前對象上尋找屬性,找到就返回對應的值;找不到就去對象的隱式原型對象[[prototype]]對象上找
對象的隱式原型對象[[prototype]]指向其構造函數(shù)的prototype對象
foo.prototype.name = "why";
foo.prototype.age = 18;
var f = new foo();
console.log(f.name, f.age); //"why" 18
如果需要在函數(shù)的原型對象上添加很多屬性,一個一個添加太麻煩,可以通過改變函數(shù)prototype指向到一個新對象的方式添加
//可以直接改變函數(shù)的prototype的指向,指向一個新對象
foo.prototype = {
name: 'why',
age: 18
}
Object.defineProperty(foo.prototype, "constructor", {
configurable: true,
enumerable: false,
writable: true,
value: foo
})
console.log(foo.prototype)
console.log(Object.getOwnPropertyDescriptors(foo.prototype));
4.5使用構造函數(shù)和原型相結合創(chuàng)建對象
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.running = function () {
console.log(this.name + "is running!");
};
var p1 = new Person("小名", 30, "male");
var p2 = new Person("小紅", 25, "female");
var p3 = new Person("小麗", 30, "female");
console.log(p1);
console.log(p2);
console.log(p3);
//當前對象上沒有running方法,會去對象的[[prototype]]隱式原型對象上去找,
//而對象的隱式原型對象正是創(chuàng)建對象的構造函數(shù)的prototype顯示原型對象
p1.running()
p2.running()
p3.running()
優(yōu)點:
- 通過構造函數(shù)創(chuàng)建出的對象,有標注出所屬類的類名
- 如果創(chuàng)建100個對象,只會創(chuàng)建一個running函數(shù),避免了內(nèi)存浪費
非常感謝王紅元老師的深入JavaScript高級語法讓我學習到很多 JavaScript
的知識