一炕桨、對象的創(chuàng)建方式
-
生成實例對象的原始模式 (對象直接量)
假定我們把貓看成一個對象西篓,它有"名字"和"顏色"兩個屬性巫玻。
//單個對象創(chuàng)建
var Cat = {
name : '',
color : ''
}
也可能有讀者說榜配,你上面不是用字面量定義了一個對象嗎? 不過這真的是一種“快捷方式”,在編程語言中慈格,一般叫做“語法糖”怠晴。
javascript的數(shù)據(jù)有兩種,簡單數(shù)據(jù)和復(fù)雜數(shù)據(jù)浴捆。簡單數(shù)據(jù)有undefined蒜田,null,boolean汤功,number和string這五種。復(fù)雜數(shù)據(jù)只有一種溜哮,即對象(object)滔金。
那么色解,在javascript中,數(shù)組是對象餐茵,函數(shù)也是對象,基本類型(number, string, boolean)包裝類(Number,String,Boolean)也是對象科阎。
現(xiàn)在,我們需要根據(jù)這個原型對象的規(guī)格(schema)忿族,生成兩個實例對象锣笨。
//多個對象創(chuàng)建
var cat1 = {}; // 創(chuàng)建一個空對象
cat1.name = "大毛"; // 按照原型對象的屬性賦值
cat1.color = "黃色";
var cat2 = {};
cat2.name = "二毛";
cat2.color = "黑色";
好了,這就是最簡單的封裝了道批,把兩個屬性封裝在一個對象里面错英。但是,這樣的寫法有兩個缺點隆豹,一是如果多生成幾個實例椭岩,寫起來就非常麻煩移迫;二是實例與原型之間宿接,沒有任何辦法看出有什么聯(lián)系。
-
通過普通函數(shù) / 構(gòu)造對象 返回一個對象
2.1. 通過(普通)函數(shù)對象 返回一個對象
function Cat(name,color) {
return {
name:name,
color:color
}
}
var cat1 = Cat("大毛","黃色");
var cat2 = Cat("二毛","黑色");
然后生成實例對象谤绳,就等于是在調(diào)用函數(shù)
這種方法的問題依然是碉考,cat1和cat2之間沒有內(nèi)在的聯(lián)系塌计,不能反映出它們是同一個原型對象的實例。
2.2. 通過(構(gòu)造)函數(shù)對象 (通過new創(chuàng)建對象)
為了解決從原型對象生成實例的問題侯谁,Javascript提供了一個構(gòu)造函數(shù)(Constructor)模式锌仅。
所謂"構(gòu)造函數(shù)",其實就是一個普通函數(shù)良蒸,但是內(nèi)部使用了this
變量技扼。對構(gòu)造函數(shù)使用new
運算符,就能生成實例嫩痰,并且this
變量會綁定在實例對象上剿吻。
比如,貓的原型對象現(xiàn)在可以這樣寫串纺,
function Cat(name,color){
this.name=name;
this.color=color;
}
//創(chuàng)建對象
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.name); // 大毛
alert(cat1.color); // 黃色
//這時cat1和cat2會自動含有一個constructor屬性丽旅,指向它們的構(gòu)造函數(shù)。
alert(cat1.constructor == Cat); //true
alert(cat2.constructor == Cat); //true
//Javascript還提供了一個instanceof運算符纺棺,驗證原型對象與實例對象之間的關(guān)系榄笙。
alert(cat1 instanceof Cat); //true
alert(cat2 instanceof Cat); //true
2.2.1 系統(tǒng)自帶的的,eg:new Object(), Array(), Number(),Boolean(), Date()...
單個對象創(chuàng)建
var obj = new Object();
obj.name = 'lyl';
console.log(obj.name); //lyl
多個對象創(chuàng)建
工廠模式:使用的系統(tǒng)內(nèi)部Object構(gòu)造函數(shù),創(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('Nike',29,'teacher');
var person2 = createPerson('Arvin',20,'student');
在使用工廠模式創(chuàng)建對象的時候祷蝌,我們都可以注意到茅撞,在createPerson函數(shù)中,返回的是一個對象。那么我們就無法判斷返回的對象究竟是一個什么樣的類型米丘。于是就出現(xiàn)了使用自定義構(gòu)造函數(shù)創(chuàng)建對象剑令。
2.2.2 自定義的:為了和普通函數(shù)區(qū)分,首字母大寫拄查,采用大駝峰式寫法(普通函數(shù)采用小駝峰式寫法)
//創(chuàng)建單個對象
function Obj (name) {
this.name = name;
this.age = 18;
}
var obj = new Obj('lyl');
console.log(obj.name); //lyl
console.log(obj.age); //18
//創(chuàng)建多個對象
function Obj (name) {
this.name = name;
this.age = 18;
}
var obj = new Obj('lyl');
var obj = new Obj('dfv');
//
自定義構(gòu)造函數(shù)的基本構(gòu)造原理:
首先吁津,文字理論解釋一番,其實一切的關(guān)鍵全在與new這個操作符上堕扶,用new和不用new返回的結(jié)果大相徑庭碍脏。不用new,則Obj('lyl')根本就是一個函數(shù)的正常執(zhí)行稍算,沒有返回值典尾,則默認返回undefined,而是用new操作符后js引擎就會將該函數(shù)看作構(gòu)造函數(shù)看待邪蛔,經(jīng)過內(nèi)部的一系列隱士操作急黎,返回值就是一個對象了。
看下如下demo:
demo1:用new和不用new的區(qū)別演示:
function Obj () {
this.age = 18;
}
// 不用new
console.log(Obj()); // undefined
// 用new
console.log(new Obj()); //Obj {age: 18}
demo2 用new返回值為對象的基本原理:
不用new侧到,函數(shù)內(nèi)的this指向的是window勃教,所以this.xxx定義的變量都是window上的屬性,但為什么使用new后其中的this就不是window對象了呢匠抗?那是因為:
用new后故源,js引擎會在函數(shù)被進行兩步隱士操作(假設(shè)構(gòu)造函數(shù)名為Person):
第一步, var this = Object.create(Peson.prototype); (也是創(chuàng)建對象的一種方法汞贸,下邊會講到) 隱士的改變函數(shù)內(nèi)this的含義绳军,現(xiàn)在函數(shù)內(nèi)的this是一個原型為Person.prototype, 構(gòu)造函數(shù)為Person的對象(其實此過程就將想要的對象基本創(chuàng)造成功了矢腻,只是差些屬性而已门驾,從此可是看出構(gòu)造函數(shù)創(chuàng)建對象的最根本原理是借用Object.create()方法來實現(xiàn)的,只不過被封裝功能化了)多柑;
第二步, 在創(chuàng)建的對象設(shè)置完所需要的屬性后奶是,隱士的將創(chuàng)建的對象this通過return返回 return this;
通過代碼的展現(xiàn):
// 構(gòu)造函數(shù)的原型
Person.prototype = {
say: function () {
console.log('I am saying');
}
}
// 構(gòu)造函數(shù)
function Person () {
// 隱士操作
// var this = Object.create(Person.prototype);
//返回對象屬性的設(shè)置
this.name = 'lyl';
this.age = 18;
// 隱士操作
// return this;
}
var person1 = new Person();
console.log(person1.name); // lyl
person1.say(); //I am saying
上述兩步理論的驗證:
第一步:現(xiàn)在函數(shù)內(nèi)的this是一個原型為Person.prototype, 構(gòu)造函數(shù)為Person的對象
// 構(gòu)造函數(shù)的原型
Person.prototype = {
say: function () {
console.log('I am saying');
}
}
// 構(gòu)造函數(shù)
function Person () {
this.name = 'lyl';
this.age = 18;
// 打印this對象的原型
console.log(this.__proto__); //
// 驗證this是否是Person構(gòu)造函數(shù)的實例
console.log(this instanceof Person); //true
}
new Person();//打印結(jié)果如下
// Object say: ()__proto__: Object
// true
Person();//打印結(jié)果如下
// Window
// false
第二步:隱士的將創(chuàng)建的對象this通過return返回:
由以上一些代碼竣灌,我們已經(jīng)可以看出返回的是滿足條件的對象聂沙,現(xiàn)在我們創(chuàng)建對象時不用new,并顯示的模擬這兩步隱士操作來驗證(我們不用new則兩步隱士操作就不會產(chǎn)生)
// 構(gòu)造函數(shù)的原型
Person.prototype = {
say: function () {
console.log('I am saying');
}
}
// 構(gòu)造函數(shù)
function Person () {
var that = Object.create(Person.prototype);
that.name = 'lyl';
that.age = 18;
return that;
//提前返回that導(dǎo)致return this無法執(zhí)行而失效
}
var person = new Person();
//此處不用new也是可以成功返回一個滿足條件的對象初嘹,因為顯示的返回了that
console.log(person.name); //lyl
person.say(); //I am saying
p.s. 關(guān)于顯示返回that的問題及汉,當(dāng)我們用new生成對象,若我們顯示return的是一個對象 / 引用值屯烦,則會導(dǎo)致return this失效坷随,若返回的是原始值房铭,則return this不會失效
-
原型創(chuàng)建對象模式(通過原型創(chuàng)建對象)
構(gòu)造函數(shù)方法很好用,但是存在一個浪費內(nèi)存的問題温眉。
function Cat(name,color){
this.name = name;
this.color = color;
this.type = "貓科動物";
this.eat = function(){alert("吃老鼠");};
}
//生成實例
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat ("二毛","黑色");
alert(cat1.type); // 貓科動物
cat1.eat(); // 吃老鼠
//表面上好像沒什么問題育叁,但是實際上這樣做,有一個很大的弊端芍殖。那就是對于每一個實例對象,type屬性和eat()方法都是一模一樣的內(nèi)容谴蔑,每一次生成一個實例豌骏,都必須為重復(fù)的內(nèi)容,多占用一些內(nèi)存隐锭。這樣既不環(huán)保窃躲,也缺乏效率。
alert(cat1.eat == cat2.eat); //false
能不能讓type屬性和eat()方法在內(nèi)存中只生成一次钦睡,然后所有實例都指向那個內(nèi)存地址呢蒂窒?回答是可以的。
Javascript規(guī)定荞怒,每一個構(gòu)造函數(shù)都有一個prototype屬性洒琢,指向另一個對象。這個對象的所有屬性和方法褐桌,都會被構(gòu)造函數(shù)的實例繼承衰抑。
這意味著,我們可以把那些不變的屬性和方法荧嵌,直接定義在prototype對象上呛踊。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "貓科動物";
Cat.prototype.eat = function(){alert("吃老鼠")};
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 貓科動物
cat1.eat(); // 吃老鼠
//這時所有實例的type屬性和eat()方法,其實都是同一個內(nèi)存地址啦撮,指向prototype對象谭网,因此就提高了運行效率。
alert(cat1.eat == cat2.eat); //true
3.2 Prototype模式的驗證方法
為了配合prototype屬性赃春,Javascript定義了一些輔助方法愉择,幫助我們使用它。
1) isPrototypeOf()
這個方法用來判斷聘鳞,某個proptotype對象和某個實例之間的關(guān)系薄辅。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "貓科動物";
Cat.prototype.eat = function(){alert("吃老鼠")};
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 貓科動物
cat1.eat(); // 吃老鼠
alert(Cat.prototype.isPrototypeOf(cat1)); //true
alert(Cat.prototype.isPrototypeOf(cat2)); //true
2)hasOwnProperty()
每個實例對象都有一個hasOwnProperty()方法,用來判斷某一個屬性到底是本地屬性抠璃,還是繼承自prototype對象的屬性站楚。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "貓科動物";
Cat.prototype.eat = function(){alert("吃老鼠")};
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 貓科動物
cat1.eat(); // 吃老鼠
alert(Cat.prototype.isPrototypeOf(cat1)); //true
alert(Cat.prototype.isPrototypeOf(cat2)); //true
alert(cat1.hasOwnProperty("name")); // true
alert(cat1.hasOwnProperty("type")); // false
3) in運算符
in運算符可以用來判斷,某個實例是否含有某個屬性搏嗡,不管是不是本地屬性窿春。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "貓科動物";
Cat.prototype.eat = function(){alert("吃老鼠")};
var cat1 = new Cat("大毛","黃色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 貓科動物
cat1.eat(); // 吃老鼠
alert(Cat.prototype.isPrototypeOf(cat1)); //true
alert(Cat.prototype.isPrototypeOf(cat2)); //true
alert("name" in cat1); // true
alert("type" in cat1); // true
//in運算符還可以用來遍歷某個對象的所有屬性拉一。
for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }
3.3. 組合使用構(gòu)造函數(shù)模式和原型模式
根據(jù)原型與構(gòu)造函數(shù)的特點,可以組合使用構(gòu)造函數(shù)模式和原型模式
function Person(name,age,job){
this.name =name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor:Person,
sayName: function(){
alert(this.name);
};
}
var person1 = new Person('Nike',20,'teacher');
- Object.create(原型)旧乞; 創(chuàng)建一個繼承該原型的實例對象(Object.creat())
var f4 = {x: 1};
var t4 = Object.create(f4);
//如果f4的屬性改變蔚润,t4相應(yīng)的屬性也會改變
關(guān)于此方法的一些注意事項:
(1)、若傳參為Object.prototype尺栖,則創(chuàng)建的原型為Object.prototype嫡纠,和 new Object()創(chuàng)建的對象是一樣的
Object.create(Object.prototype) <==>new Object();
(2)、若傳參為空 或者 null延赌,則創(chuàng)建的對象是沒有原型的除盏, 導(dǎo)致該對象是無法用document.write()打印會報錯,因為document.write()打印的原理是調(diào)用Object.prototype.toString()方法挫以,該對象沒有原型者蠕,也就沒有該方法,所以document.write()無法打印
由此延伸的知識點: 引用值都也是算作是對象掐松,所以都可以用document.write()打吁饴隆;原始值numebr, boolean, string都有自己對象的包裝類大磺,借助此機制也是可以用document.write()打印出的抡句;但undefined 和 null既不是引用值,也沒有對應(yīng)的包裝類杠愧,所以應(yīng)該無法打印的玉转,但大家會發(fā)現(xiàn)這兩個值也是可是用document.write()打印的,因為這兩個值被設(shè)定為特殊值殴蹄,document.write()打印其是不用調(diào)用任何方法的究抓,而是之直接打印其值
二、創(chuàng)建的對象的本質(zhì)
以上四種方法看似不同袭灯,但他們的本質(zhì)是一樣的刺下,都是通過最基本的new Object()
與原型來實現(xiàn)對象的創(chuàng)建的。
使用構(gòu)造器創(chuàng)建對象稽荧,其本質(zhì)如下:
對象的__proto__
指向了構(gòu)造器的prototype
橘茉,所以修改構(gòu)造器的屬性不會影響對象,但是修改構(gòu)造器原型會姨丈。
使用Object.create()
:
對象的__proto__
指向了構(gòu)造器畅卓,所以可以通過修改父對象影響對象的屬性
三、__proto__
與prototype
__proto__
和prototype
是比較容易混淆的兩個屬性蟋恬。__proto__
是對象的屬性翁潘,用來在原型鏈上查找你需要的方法的實際對象。prototype
是函數(shù)獨有的屬性當(dāng)我們使用關(guān)鍵詞new
并且將函數(shù)作為構(gòu)造函數(shù)來構(gòu)造對象的時候, 它被用來構(gòu)建對象的__proto__
屬性.
對于非函數(shù)的對象來說比較簡單只有__proto__
歼争,但是函數(shù)也是對象拜马,所以在函數(shù)中__proto__
和prototype
同時存在渗勘,一個函數(shù)作為構(gòu)造器時,參與其中的是prototype
屬性俩莽,但是被創(chuàng)建時旺坠,__proto__
指向了Function.prototype
。
參考:
JS對象創(chuàng)建的幾種方式整理javascript技巧腳本之家 http://www.jb51.net/article/107012.htm
js之對象(經(jīng)典) - 最騷的就是你 - 博客園 https://www.cnblogs.com/libin-1/p/5911190.html
Javascript 面向?qū)ο缶幊蹋ㄒ唬悍庋b - 阮一峰的網(wǎng)絡(luò)日志 http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
js對象的創(chuàng)建 - 簡書 http://www.reibang.com/p/6377784b62b2?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
簡單理解javascript中apply()和call() - CSDN博客 https://blog.csdn.net/iamchuancey/article/details/78177028