本內容是《Web前端開發之Javascript視頻》的課件,請配合大師哥《Javascript》視頻課程學習。
對象是ES的的一種複合數據類型,即引用類型;即,對象就是一組屬性與方法的集合;
ES沒有類的概念,所以它的對象也與其他語言中的對象有所不同;
ES把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或者函數;相當於説對象是一組無序的值,對象的每個屬性或方法都有一個名字,而每個名字都映射到一個值,正因為這樣,可以把ES的對象看作成一個散列表,其中是一組名值對,它們的值可以是數據或函數;
每個對象都是基於一個引用類型創建的,這個引用類型可以原生類型,也可以是自定義的函數;
一.理解對象:
可以創建一個最簡單的自定義對象,就是使用Object,然後再為它添加屬性和方法,如:
var person = new Object();person.name = "wangwei";person.age = 18;person.jog = "Engineer";person.sayName = function(){ alert(this.name);}
在多種場景中,常用對象字面量創建對象,如:
var person = { name:"wangwei", age:18, job:"Engineer", sayName:function(){ alert(this.name); }};
對象中的this:
當一個函數作為對象的屬性存在時,並且通過對象調用這個方法,那麼函數中的this就指向調用函數的對象;
this的好處在於,可以更加方便的訪問對象內部成員;
早綁定和晚綁定:
綁定:把對象的成員與對象實例結合在一起的方法。
早綁定:
指在實例化對象之前定義它的屬性和方法,這樣編譯器或解釋程序就能夠提前轉換機器代碼。ES不是強類型語言,所以不支持早綁定;
晚綁定:
編譯器或解釋程序在運行前,不知道對象的類型。使用晚綁定,無需檢查對象的類型,只需檢查對象是否支持屬性和方法即可;ES中的所有變量都採用晚綁定方法;這樣就允許執行大量的對象操作;
屬性訪問錯誤:
屬性訪問並不總是返回或設置一個值,如果訪問一個不存在的屬性並不會報錯,會返回undefined;但如果試圖訪問一個不存在的對象的屬性就會報錯;null和undefined值是沒有屬性的,因此,訪問這兩個值的屬性就會報錯,如:
var book = {};console.log(book.subtitle); // undefined// console.log(book.subtitle.length); // 異常// 解決方案var len = book && book.subtitle && book.subtitle.length;console.log(len); // undefined,不會報錯
有些屬性是隻讀的,不能重新賦值,有一些對象不允許新增屬性,但如果操作這些屬性,也不會報錯,如:
// 內置構造函數的原型是隻讀的// 賦值失敗,但沒有報錯,Object.prototype沒有修改Object.prototype = 0;
這是歷史遺留問題,但在嚴格模式下會拋出異常;
刪除屬性:
delete刪除對象的屬性,但只是斷開屬性和宿主對象的聯繫,而不會去操作屬性中的屬性;
var a = {p:{x:1}};var b = a.p;delete a.p;console.log(b.x); // 1
刪除的屬性的引用還存在,因此在某些實現中,有可能會造成內存泄漏;因此,在銷燬對象時,要遍歷屬性中的屬性,依次刪除;
delete刪除成功或者沒有任何副作用時,它返回true;或者刪除的不是一個屬性訪問表達式,同樣返回true,如:
var o = {x:1};delete o.x;delete o.x;console.log(delete o.toString); // 什麼也沒做,toString是繼承來的console.log(delete 1) // 無意義
delete不能刪除那些可置性為false的屬性,
某些內置對象的屬性是不可配置的,比如通過變量聲明和函數聲明創建的全局對象的屬性;在嚴格模式下,刪除一個不可配置屬性會報一個類型錯誤,在非嚴格模式中,這些操作會返回false,如:
console.log(delete Object.prototype);// 不能刪除,屬性是不可配置的var x = 1;console.log(delete this.x); // 不能刪除function f(){}console.log(delete this.f); // 不能刪除
在非嚴格模式中,刪除全局對象的可配置屬性時,可以省略對全局對象的引用,但在嚴格模式下會報錯,如:
"use strict";this.x = 1;console.log(delete this.x);console.log(delete x); // 嚴格模式下異常
因此,必須顯式指定對象及其屬性;
雖然Object構造函數或對象字面量都可以用來創建單個對象,但這些方法有個明顯的缺點:使用同一個接口創建很多對象,會產生大量的重複代碼;為解決這個問題,可以使用工廠模式的方式創建對象;
工廠模式:
工廠模式是軟件工程領域一種廣泛使用的設計模式,其抽象了創建具體對象的過程(還有其他設計模式);在ES中無法創建類,所以就發明了一種函數,用該函數來封裝特定接口創建對象的細節,如:
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(o.name); }; return o;}var p1 = createPerson("wangwei",18,"Engineer");var p2 = createPerson("wujing",28,"doctor");alert(p1.name);alert(p2.name);
在工廠函數外定義對象方法,再通過屬性指向該方法;
// 在上面的代碼中改function sayName(){ alert(this.name);}// 在原來的o.sayName = function(){…}改成如下o.sayName = sayName;
構造函數:
可以使用構造函數來創建特定類型的對象,如:Object和Array這種原生構造函數,在運行時會自動出現在執行環境中;
構造函數內能初始化對象,並返回對象;
此外,也可以創建自定義的構造函數,從而自定義對象類型的屬性和方法;使用此種方式的目的:更加類似真正的面向對象創建(類)對象方法,也就是首先創建類;如:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); };}var p1 = new Person("wangwei",18,"Engineer");var p2 = new Person("wujing",28,"Doctor");alert(p1.name);alert(p2.name);
這裏的Person本身就是函數,只不過可以用來創建對象而已;
要創建實例對象,必須使用new實例化對象;以這種方式調用函數實際上會經歷經下4個步驟:
創建一個新對象;
將構造函數的作用域賦給新對象(因此this就指向了這個新對象)
執行構造函數中的代碼(為這個新對象添加屬性);
返回新對象,即隱式的返回了this;
關於構造函數的返回值:
// 先使用this,再使用ofunction Person(name,age){ var o = {}; o.name = name; o.age = age; return o;}var p = new Person("wangwei",18);console.log(p);
但如果返回是一個原始值,如:return 100,此時無任何影響,説明構造函數內返回的一定是一個對象;
在構造函數內還可以使用閉包:
function Person(name,age){ var money = 100; this.name = name; this.age = age; function show(){ money ++; console.log(money); } this.say = show;}var p1 = new Person();p1.say();p1.say();var p2 = new Person();p2.say();
constructor(構造函數)屬性:
實例都有一個constructor(構造函數)屬性,該屬性指向Person;
即:構造函數方式創建的實例有constructor(構造函數)屬性,該屬性指向類函數,如:
alert(p1.constructor == Person);alert(p2.constructor == Person);
對象的constructor屬性最初是用來標識對象類型的。但檢測對象類型,instanceof操作符更可靠;
alert(p1 instanceof Object);alert(p1 instanceof Person);
構造函數的特點:
構造函數與其他函數的唯一區別:就在於調用它們的方式不同;構造函數也是函數,不存在定義構造函數的特殊語法;
任何函數,只要通過new操作符來調用,那它就可以作為構造函數;而任何函數,如果不通過new操作符調用,就是一個普通函數,如:
// 當作構造函數使用var p1 = new Person("wangwei",18,"Engineer");p1.sayName();// 當作普通函數調用Person("wujing",28,"Doctor");window.sayName();// 在另一個對象的作用域中調用var o = new Object();Person.call(o,"Hello",38,"Worker");o.sayName();
構造函數的缺點:
這種方式雖然比較方便好用,但也並非沒有缺點;缺點是:每個方法都要在每個實例上重新創建一遍,如sayName()方法,每個實例擁有的sayName(),但都不是同一個Function實例,如:
alert(p1.sayName == p2.sayName); // false
在ES中的函數是對象,因此每定義一個函數,也就實例化了一個對象,從邏輯上説,相當於:
this.sayName = new Function("alert(this.name)");
以這種方式創建函數,會導致不同的作用域鏈和標識符解析;但創建Function新實例的機制仍然是相同的;
可以把函數定義在構造函數外部;如:
function sayName(){ alert(this.name);}function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName;}// 當作構造函數使用var p1 = new Person("wangwei",18,"Engineer");var p2 = new Person("wangwei",18,"Engineer");alert(p1.sayName == p2.sayName); // true
Web前端開發之Javascript-零點程序員-王唯