標籤:
一、對象的定義:
對象是JavaScript的一個基礎資料型別 (Elementary Data Type),是一種複合值,它將很多值(原始值或者其他對象)彙總在一起,可通過名字訪問這些值。即屬性的無序集合。
二、對象的建立(多種方法)
1、對象直接量 / 字面量
var obj = { name: ‘lyl‘, age: 18 } console.log(obj.name); // lyl
2、建構函式:
(1)、系統內建的的, eg: new Object(), Array(), Number(),Boolean(), Date()...
var obj = new Object(); obj.name = ‘lyl‘; console.log(obj.name); //lyl
(2)、自訂的:為了和普通函數區分,首字母大寫,採用大駝峰式寫法(普通函數採用小駝峰式寫法)
function Obj (name) { this.name = name; this.age = 18; } var obj = new Obj(‘lyl‘); console.log(obj.name); //lyl console.log(obj.age); //18
自訂建構函式的基本構造原理:
首先,文字理論解釋一番,其實一切的關鍵全在與new這個操作符上,用new和不用new返回的結果大相徑庭。不用new,則Obj(‘lyl‘)根本就是一個函數的正常執行,沒有傳回值,則預設返回undefined,而是用new操作符後js引擎就會將該函數看作建構函式看待,經過內部的一系列隱士操作,傳回值就是一個對象了。
看下如下demo:
demo1:用new和不用new的區別示範:
function Obj () { this.age = 18; } // 不用new console.log(Obj()); // undefined // 用new console.log(new Obj()); //Obj {age: 18}
demo2 用new傳回值為對象的基本原理:
不用new,函數內的this指向的是window,所以this.xxx定義的變數都是window上的屬性,但為什麼使用new後其中的this就不是window對象了呢?那是因為
用new後,js引擎會在函數被進行兩步隱士操作(假設建構函式名為Person):第一步, var this = Object.create(Peson.prototype); (也是建立對象的一種方法,下邊會講到) 隱士的改變函數內this的含義,現在函數內的this是一個原型為Person.prototype, 建構函式為Person的對象(其實此過程就將想要的對象基本創造成功了,只是差些屬性而已,從此可是看出建構函式建立對象的最根本原理是借用Object.create()方法來實現的,只不過被封裝功能化了); 第二步, 在建立的對象設定完所需要的屬性後,隱士的將建立的對象this通過return返回 return this;
通過代碼的展現:
// 建構函式的原型 Person.prototype = { say: function () { console.log(‘I am saying‘); } } // 建構函式 function Person () { // 隱士操作 // var this = Object.create(Person.prototype); //返回對象屬性的設定 this.name = ‘lyl‘; this.age = 18; // 隱士操作 // return this; } var person1 = new Person(); console.log(person1.name); // lyl person1.say(); //I am saying
上述兩步理論的驗證:
第一步:現在函數內的this是一個原型為Person.prototype, 建構函式為Person的對象
// 建構函式的原型 Person.prototype = { say: function () { console.log(‘I am saying‘); } } // 建構函式 function Person () { this.name = ‘lyl‘; this.age = 18; // 列印this對象的原型 console.log(this.__proto__); // // 驗證this是否是Person建構函式的執行個體 console.log(this instanceof Person); //true } new Person();//列印結果如下 // Object say: ()__proto__: Object // true Person();//列印結果如下 // Window // false
第二步:隱士的將建立的對象this通過return返回
由以上一些代碼,我們已經可以看出返回的是滿足條件的對象,現在我們建立對象時不用new,並顯示的類比這兩步隱士操作來驗證(我們不用new則兩步隱士操作就不會產生)
// 建構函式的原型 Person.prototype = { say: function () { console.log(‘I am saying‘); } } // 建構函式 function Person () { var that = Object.create(Person.prototype); that.name = ‘lyl‘; that.age = 18; return that; //提前返回that導致return this無法執行而失效 } var person = new Person();
//此處不用new也是可以成功返回一個滿足條件的對象,因為顯示的返回了that console.log(person.name); //lyl person.say(); //I am saying
p.s. 關於顯示返回that的問題,當我們用new產生對象,若我們顯示return的是一個對象 / 引用值,則會導致return this失效,若返回的是原始值,則return this不會失效
3、Object.create(原型); 建立一個繼承該原型的執行個體對象
關於此方法的一些注意事項:
(1)、若傳參為Object.prototype,則建立的原型為Object.prototype,和 new Object()建立的對象是一樣的 Object.create(Object.prototype) <==>new Object();
(2)、若傳參為空白 或者 null,則建立的對象是沒有原型的, 導致該對象是無法用document.write()列印會報錯,因為document.write()列印的原理是調用Object.prototype.toString()方法,該對象沒有原型,也就沒有該方法,所以document.write()無法列印
由此延伸的知識點: 引用值都也是算作是對象,所以都可以用document.write()列印;原始值numebr, boolean, string都有自己對象的封裝類,藉助此機制也是可以用document.write()列印出的;
但undefined 和 null既不是引用值,也沒有對應的封裝類,所以應該無法列印的,但大家會發現這兩個值也是可是用document.write()列印的,因為這兩個值被設定為特殊值,document.write()列印其是不用調用任何方法的,而是之直接列印其值
三、對象的增、刪、改、查
1、增: 所謂增添一個對象的屬性,就是直接對該屬性進行賦值操作即可,這就相當於為該對象添加了一個新屬性,而列印未添加的屬性,瀏覽器不會報錯,而是會列印出undefined
var obj = {}; console.log(obj.name); //undefined (不會報錯) obj.name = ‘lyl‘; console.log(obj.name); // lyl
2、刪:我們通過delete操作符來刪除一個對象的屬性
var obj = { name : ‘lyl‘ }; console.log(obj.name); //lyl delete obj.name; console.log(obj.name); //undefined
3、改: 修改一個對象的屬性是最簡單的了,直接通過賦值操作賦予其其他的值即可
var obj = { name: ‘lyl‘ }; console.log(obj.name); // lyl obj.name = ‘obj‘; console.log(obj.name); // obj
4、查:查詢一個對象的屬性值有兩種方法
var obj = { name: ‘lyl‘ }; // 第一種方法 console.log(obj[‘name‘]); //lyl // 第二種方法 console.log(obj.name); // lyl //p.s.最本質的是第一種方法,因為在使用第二種方法時,後台自動將其轉換為第一種字串的形式來查詢
p.s.以上的增、刪、改三種操作都只是針對當前對象的屬性進行操作,而不會影響到當前對象的原型的屬性。
而查詢是先看看當前對象本身是否設定了該屬性,如果當前對象未設定該屬性,則再看該對象的原型中是否設定了該屬性,若兩者都沒有,則返回undefined
四、封裝類:
1、五個原始值:number, string , boolean, undefined, null
其中number, string, boolean是分別擁有自己的封裝類,而undefined和null是沒有自己的封裝類的
2.原始值不是對象,無法擁有自己的屬性,但因為的封裝類的存在,原始值就好似可以擁有自己的屬性了,但其擁有的屬性又有點特殊之處,如下用string來舉例:
先看一段code
// str是string類型的,非對象,不能擁有屬性,為什麼能列印出str.length? var str = ‘abcd‘; console.log(str.length); //4
上邊code中問題的解釋:
// 因為每次執行完一條完整js語句後該類型對象的封裝類就會將該語句封裝,所以也就不會導致報錯了,這些都是後台自己寫的 var str = ‘abcd‘; // var str1 = new String(‘abcd‘); console.log(str.length); //4 // var str1 = new String(‘abcd‘); // console.log(str1.length);
注意:每次封裝類封裝完一次完整語句後就會被銷毀。(即解釋一條語句,用封裝類封裝一次,然後銷毀),這回導致其和正常對象的一些不同之處,如下例子
var str = ‘abcd‘; str.len = 4; console.log(str.len); // undefiend
//???
關鍵在於‘銷毀’一詞上,解釋如下:
var str = ‘abcd‘; // var str1 = new String(‘abcd‘); // 銷毀 str.len = 4; // var str1 = new String(‘abcd‘); // str1.len = 4; // 銷毀 console.log(str.len); // undefiend // var str1 = new String(‘abcd‘); // console.log(str.len); str1為剛建立的對象,其len屬性自然為undefiend // 銷毀
一個易錯的坑:
// 總之記得‘原始值封裝類‘‘銷毀‘這兩句話就行 var str = ‘abcd‘; str.lenth = 2; console.log(str); // ab or abcd ? answer is abcd console.log(str.length); // 2 or 4 ? answer is 4
五、原型:
1、原型的定義: 原型是function對象的一個屬性,它定義了建構函式製造出的對象的公用祖先。通過改建構函式產生的對象,可以繼承該原型的屬性和方法。原型也是對象。
2、利用原型特點和概念,可以提取共有屬性。將一類對象的共有屬性提取出來,放到該類對象的原型中,從而不需要每次用new操作符時都重新定義一遍該共有屬性。
如下,定義一個Person建構函式,而屬於Person多構造對象共有的屬性方法,則定義到Person的原型中
Person.prototype = { eat: function (food) { console.log(‘I have eated ‘ + food); }, sleep: function () { console.log("I am sleeping"); } } // 人的建構函式 function Person (name, age) { this.name = name; this.age = age; } var person1 = new Person(‘lyl‘, 18); console.log(person1.name); //lyl person1.eat(‘apple‘); //I have eated apple
3、如何查看原型:
之前是不允許我們查看建構函式的原型的,但後來提供了一個可查看建構函式原型的介面:隱士屬性__proto__(其實我們能夠訪問原型的屬性,或者說繼承原型,靠的就是__proto__屬性串連著建構函式和原型,可以說沒有__proto__屬性的存在,就無法實現原型的繼承)
(1)、首先我們先說明一下__proto__這個介面是存放到哪裡的
看過以上對象建立過程的都應該知道在用new建立一個對象時,內部會隱士自動建立一個this的對象,進過一系列處理後再隱士將this對象返回。而__proto__就對於隱士建立的this對象中,如下代碼:
// 原型 Person.prototype = { say: function () { console.log("I am saying "); }, play: function () { console.log("I am playing"); } } // 建構函式 function Person (name) { // var this = Object.create(Person.prototype); // p.s.在隱士建立的this對象中存在一個屬性,即__proto__,該屬性儲存區了Person.prototype this.name = name; // return this; } // 對象的建立 var person1 = new Person(‘lyl‘); // 列印原型 console.log(person1.__proto__);
(2)、如何查看原型:直接通過new操作符建立的對象訪問__proto__屬性即可,如上代碼示範
4、如何查看對象的建構函式,我們通過屬性constructor來查看:
contructor屬性位於建構函式的原型中,其中儲存的是建構函式資訊,所以在不知道原型的情況下,由原型繼承原理,我們可以用執行個體對象來直接存取constructor,即擷取建立該執行個體的建構函式
function Person () { this.name = ‘myName‘; this.age = 18; } var person = new Person(); console.log(person.constructor); // function Perso(){...}
六、原型鏈:
1、定義:顧名思義,原型鏈就是將一個個原型串聯起來,形成一條原型繼承的鏈子。
2、原型鏈的構成:
如下代碼例子, Child繼承Parent, Parent繼承GrandParent, 而GrandParent沒有自訂原型,所以預設為原型鏈的最頂端new Object();
(為什麼Object為最頂端,因為Object.prototype為null,為null是沒有原型的)
// 原型鏈: Child -> new Parent() -> new GrandParent() -> new Object(); function GrandParent() { this.name = ‘GrandParent‘; this.a = 3; } Parent.prototype = new GrandParent(); function Parent() { this.name = ‘parent‘; this.b = 2; } Child.prototype = new Parent(); function Child() { this.name = ‘child‘; this.c = 1; } var child = new Child(); console.log(child); // Child {name: "child", c: 1} console.log(child.a); // 3 console.log(child.b); //2 console.log(child.c); //1
3、原型鏈的增刪改查:
使用如上的原型鏈說明, Child -> new Parent() -> new GrandParent() -> new Object(), 執行個體對象為child
(1)、增:
為child執行個體對象添加屬性,總是添加為其自己本身的屬性,為對原型和原型鏈是沒有影響的。(再具體說即對和其相同建構函式構造的執行個體對象無法造成影響,以下說法同此)
(2)、刪:
使用delete操作符只能刪除child執行個體對象自己本身的屬性,而無法刪除由原型繼承而來的屬性
(3)、改:
分兩種情況:
若修改的屬性為繼承自原型的,且實值型別為原始值,則僅僅修改的是該執行個體對象的屬性,對原型無法造成影響。
若修改的屬性為繼承自原型的,屬性值類型為引用值,則對引用值的修改又分兩種情況:
第一種是直接對該修改的屬性賦值 => 此情況僅僅修改的是執行個體對象的該屬性,無法對原型造成影響。
第二種是對該修改的屬性新增內容或去除內容,而不是對其重新賦值 => 此情況會對原型造成影響。如下例子:
Person.prototype = { has: [1, 2, 3] } function Person () { this.name = ‘lyl‘; } var person1 = new Person(); var person2 = new Person(); person1.has.push(4); // person1 和 person2都改變了,因為person1的修改影響到了原型,進而影響到了另一個執行個體對象 console.log(person1.has); //[1, 2, 3, 4] console.log(person2.has); // [1, 2, 3, 4]
(4)、查:
查詢過程如下,首先看建構函式中是否有要查詢的屬性,若有,則直接返回,若沒有,則看其原型有沒有要查詢的屬性,若沒有,則再看原型的原型上是否有要查詢的屬性,以此順序在原型鏈上查詢,若一直到原型鏈頂端後仍沒有要查詢的屬性,則返回undefined
p.s.理解了在原型鏈上的增刪改查後,自然就能理解在原型上的增刪改查了,只要把在原型上的增刪改查當成只有一個原型的很短的原型鏈即可。
4、絕大多數對象最終都會繼承自Object.prototype
為什麼事絕大多數呢?因為null,和undefined是沒有原型的,上文有詳細提到
七、對象繼承史
由於之前已經總結過這些,所以此處直接就粘連結了: http://www.cnblogs.com/Walker-lyl/p/5592048.html
八、命名空間:
我們可以利用對象建立命名空間來管理變數,防止汙染全域,適用於模組開發,如下簡單的小demo:
var workSpace = { person1: { name: ‘one‘, age: 18 }, person2: { name: ‘two‘, age: 20 } } // 這樣兩個人雖然有同名變數,但不會相互影響,因為位於不同命名空間 // 訪問第一個人的姓名 console.log(workSpace.person1.name); // one console.log(workSpace.person2.name); //two
九、實作類別似jquery中的鏈式調用: return this;
如下小demo,其中的this不懂的沒關係,上面會說,你只要把次demo中的this當成person對象就行
var person = { foodCount: 10, eat: function () { this.foodCount--; return this; }, buy: function () { this.foodCount++; return this; }, print: function () { console.log(this.foodCount); } } // foodCount初始值為10, 在連續吃了三次後,變為7 person.eat().eat().eat(); person.print(); //7
十、對象的枚舉:
1.obj.hasOwnProperty(‘prop‘);
該方法的作用是來判斷對象obj的自身屬性中是否含有屬性prop,
自身屬性是在建構函式中產生的或者執行個體對象後來自己添加的,而繼承屬性則是從原型上繼承的屬性,
所以該方法就是判斷該屬性是從原型繼承來的還是自身的。
作用: 遍曆一個對象的所有自身屬性,因為es5中的對象遍曆是預設列印包括繼承自原型的屬性的,demo如下
Person.prototype.age = 18; function Person () { this.name = ‘lyl‘; } var person = new Person(); // 未用hasOwnProperty // print: // lyl for(var prop in person) { console.log(person[prop]); } // 使用hasOwnProperty // print: // 18 lyl for(var prop in person) { if(person.hasOwnProperty(prop)) { console.log(person[prop]); } }
2、prop in obj;
in操作符用來判斷該對象obj上是否有該屬性prop,prop既可以是自身屬性,也可以是繼承屬性,如下demo
Person.prototype.age = 18; function Person () { this.name = ‘lyl‘; } var person = new Person(); console.log(‘age‘ in person); // true console.log(‘name‘ in person); //true delete person.name; console.log(‘name‘ in person); //false
3、object instanceof Object;
instanceof操作符用來判斷object執行個體對象是否為Object建構函式建立的,如下demo
Person.prototype.age = 18; function Person () { this.name = ‘lyl‘; } var person = new Person(); console.log(person instanceof Person); // true console.log(new Object() instanceof Person); //false
十一、this基本介紹:
1、函數先行編譯過程 this —> window
2、全域範圍裡 this —> window
3、obj.func(); func()裡面的this指向obj), 可以這樣理解,誰調用func,則this就指向誰
4、call/apply 可以改變函數運行時this指向,
(1)、call用法:
func.call(要改變後的this, arg1, arg2, ... );
(2)、apply用法:
func.apply(要改變後的this, [arg1, arg2, arg2]);
(3)、apply和call共同點:都是改變this指向
apply和call不同點:傳參形式不同,call是將參數一個個傳進來,而apply是將所有參數存進一個數組中,然後將該數組傳
如下demo:
// demo1 function demo1() { console.log(this); } // demo1() <==> this.demo1(); <==> window.demo1() demo1(); // window// demo2 var demo2 = { retThis: function () { console.log(this); } } demo2.retThis(); // demo2 = {...}// call / apply改變this demo1.call(demo2); // demo2 = {} demo2.retThis.call(window); // window
十二、對象的複製:
你可能回想,直接用等號賦值不久完成複製了嗎?這還用說,但你忽略了對象是一個引用值,賦值操作賦的是該對象的引用,然後會產生很壞的影響。
舉例來說,將obj1賦值給obj2,然後我們向obj2中添加了一個屬性,然後我們會驚喜的發現obj1中也有了此剛剛向obj2中添加的屬性,但我obj1並不需要該屬性啊,則造成obj1不開心了,為了讓obj1開心起來,我們向下看,嘿嘿:
對象複製,分為淺複製和深複製,而上邊的直接賦值的複製操作為淺複製,為什麼稱為淺複製呢?因為,複製的和被複製的對象在複製操作完成後,指向同一個地址引用,改變其中一個(注意:此處的改變為增加或刪除對象的屬性,而不是為該對象重新賦值一個對象),另一個也會改變,而深複製則不會產生此現象。
深複製/深拷貝code如下,這是我從網上搜到的一個目前我見到的最完整最簡短的code,直接貼上:
// 對象的深度複製(Array / obj /...) function deepClone(obj) { var str, retObj = Object.prototype.toString.call(obj) === ‘[object Array]‘ ? [] : {}; if(typeof obj !== ‘object‘) { return; }else if(window.JSON) { str = JSON.stringify(obj); retObj = JSON.parse(str); } else { for(var prop in obj) { retObj[prop] = typeof obj[prop] === ‘object‘ ? deepClone(obj[prop]) : obj[prop]; } } return retObj; }
------------------------------------------end 至此基本包含了對象的i基本知識點
js之對象(經典)