摘要:構造函數第一種途徑是使用的構造函數,方式。一個構造函數和其他函數一樣除了自身細節上有些許區別慣常的做法是將函數名首字母大寫以表示其存在目的是作為一個構造函數。關鍵字的作用就是創建一個新對象,并將構造函數內的指向這個新創建的對象。
ECMAscript 說明文檔對這門語言的定義是“一門適于在宿主環境中執行計算及操作計算對象的面向對象的編程語言”。簡單的說,JavaScript是一門面向對象(OO)的語言。
面向對象講究的是專注于對象本身——它們的結構,它們互相間是如何影響的。本文是@堂主 對《Pro JavaScript with Mootools》一書的第三章 Object 部分的翻譯,最早譯于 2012 年。因為面向對象編程本身已經超出了本書的敘述范圍,所以我們在本章所談的只是 JavsScript 自身在面向對象方面的那些特點。
本篇譯文字數約 3 萬字,各位看官如發現翻譯錯誤或有優化建議,歡迎留言指教,共同成長。另外,同樣的建議——非本土產技術類書籍,建議還是優先閱讀英文原版。
JavaScript是基于原型的 JavaScript is Prototypal(-ish)所有面向對象的語言在其核心都會對對象進行處理,對象的創建及構造的過程將大部分的面向對象語言分為2個陣營:
基于類 (Classical or class-based) 的面向對象語言采用類來創建對象。類是一個為創建對象提供藍本的特殊數據類型。在一個基于類的面向對象的語言中,我們通過創建類來定義一個對象的結構,并通過創建該類的實例來創造這個對象本身。這一過程被稱為實例化 (instantiation)。
基于原型 (Prototypal or prototype-based) 的面向對象語言沒有類的概念,它以其他的對象對藍本。在一個基于原型的語言中,prototype 是一個由你創建、體現著你期望的結構的對象,這個對象之后會成為其他對象創建所參照的藍本。通過拷貝其本身 prototype 屬性來創建實例的方式被稱為克隆(cloning)。對一個純粹的原型語言而言,任何一個對象都能被作為創建其他對象的原型。
JavaScript 是一本基于原型的語言:這里沒有類的概念,所有對象都是由其他對象創建而來。不過,JavaScript 不是一門純粹的原型語言,在本章的后面我們會看到 JavaScript 還保留著一些基于類的殘存特征。如果你已經對面向對象的語言很熟悉了,你很可能會覺得 JavaScript 是奇異的,因為相對你之前的那些面向對象的經驗,這門語言的怪異特質是如此明顯。
哈哈,先別打退堂鼓:JavaScript,一門面向對象的語言,因為兼備了基于類和原型的特征,使得它具備了處理復雜、龐大應用的實力。
一門關于對象的語言 (A Language of Objects)從本質上講,一個 JavaScript 的對象就是一些名值對(key-value pairs)的聚合體。相比于簡單的如字符串、數字等基本數據類型而言,JavaScript 對象是一種混合的復合數據類型。對象內的每一個名值對被稱為一個屬性(property),key 被稱為屬性名(property name),value 被稱為屬性值(property value)。
屬性名一向是字符串,而屬性值則可能是任何數據類型:字符串、數字、布爾值或者是復合型的數據類型如數組、函數或對象。盡管 JavaScript 并未將對象屬性值可承載的數據類型做任何區分,但我們還是習慣的將用函數類型作為值的屬性稱為方法(methods)以與其他值為非函數類型的屬性作區分。為了避免困惑,在后面的探討中我們采用如下的慣例:以函數為值的屬性稱之為“方法”,其他的統稱為“屬性”。如果我們所指的同時可能為一個對象的方法或屬性,那我們會稱它們為這個對象的成員(members)。
注意:在面對 JavaScript 是一門一等對象語言這個現實時,屬性和方法間的區分會顯得不那么清晰。本章的觀點是:不論值是什么,一個對象內的成員都是一個屬性,甚至是函數本身也可以被作為值來傳遞。
一個對象可以擁有多少屬性是沒有數量上的限制的,甚至一個對象可以擁有0個屬性(此時表示這是一個空對象)。依照其用途,一個對象可以在某些情況下被稱為是一個哈希(hash)、字典(dictionary) 或表(table),折射出其結構是一組名值對。不過我們還是堅持在討論時采用“對象”這一稱呼。
創建一個對象最簡單的辦法是使用對象字面量(object literal)。
// 一個對象字面量 var person = { name : "Mark", age : 23 };
這里我們創建了一個具有2個屬性的新對象,一個鍵名是 name,另一個鍵名是 age,這個對象被存儲在 person 變量里——這為我們提供了一個有2個成員的 person 對象。注意雖然 key 是字符串但我們并將其包含在引號里,只要是非保留字的有效標識符,在 JavaScript 中這就是容許的。對于下面的情況,我們需要用引號將 key 圍起來:
// 一個對象字面量 var person = { "name of the person" : "Mark", "age of the person" : 23 };
為了引用一個對象中的成員,我們可以使用點記法(dot notation),這可以使我們通過在屬性名標識符之前置入一個句點來引用其對應的屬性值;我們還可以使用括號記法(bracket notation),這個方法通過為字符串的屬性名標識符圍上一個中括號 [ ] 來達到同樣的引用屬性值的目的。
// 一個對象字面量 var person = { name : "Mark", age : 23 }; // 點記法 console.log(person.name); // "Mark" // 括號記法 console.log(person["age"]); // 23
實際上點記法是括號記法的快捷方式、語法糖(syntactic sugar),實際中大多數情況下我們都使用點記法。當然,點記法被限制在標識符是適當的情形下。在其他情況中,你需要使用括號記法。
var person = { "name of the person" : "Mark", "age of the person" : 23 }; console.log(person["name of the person"]); // "Mark"
當你不是采用一個字符串 key 而是采用一個對象來引用的時候,也需要使用括號記法
var person = { name : "Mark", age : 23 }; var key = "name"; console.log(person[key]); // "Mark"
訪問一個不存在的對象成員會返回 undefined。
var person = {}; console.log(person.name); // undefined
同時我們還可以在一個對象創建之后動態的為其新增成員或改變某個成員的屬性值。
var person = {name : "Mark"}; person.name = "Joseph"; console.log(person.name); // "Joseph" console.log(person.age); // undefined person.age = 23; console.log(person.age); // 23
你可以通過為對象成員賦值為函數來創建方法。
var person = { name : "Mark", age : 23, sayName : function() { console.log(this.name); } }; console.log(typeof person.sayName); // "function" person.sayName(); // "Mark" person.sayAge = function() { console.log(this.age); // 23 }; console.log(typeof person.sayAge); // "function" person.sayAge(); // 23
你應該會注意到我們在方法中引用 person 對象的 name、age 屬性使用的是 this.name 和 this.age 的方式。回顧一下我們前一章討論過的部分,你會知道 this 關鍵字指的是包含方法等屬性的對象的本身,所以在本例中 this 指代的就是 person 對象。
對象的構建模塊(The Buliding Blocks of Objects)雖然對象字面量是一種創建對象的快捷方式,但它并不能完整的展示 JavaScript 面向對象的優勢。比如,如果你需要創建 30 個 person 對象,那么對象字面量會是一種非常耗時的方式——為每一個對象都寫一個對象字面量是不切實際的。為了更有效率,我們需要為我們需要的對象創建一個藍本結構,并使用這個藍本來創造對象的實例。
在基于類的面向對象語言中,我們可以為創建一個類來明確對象需要的結構;在基于原型的面向對象語言中,我們可以簡化的創建一個 Person 對象來提供這個結構,之后克隆這個對象來獲得我們需要的新對象。
構造函數(Constructor Functions)第一種途徑是使用 JavaScript 的構造函數(constructor functions,or constructors)方式。對象字面量是對這種方式的一種簡化版。下面2個對象是等價的。
// 使用對象字面量 var personA = { name : "Mark", age : 23 }; // 使用構造器 var personB = new Object(); personB.name = "Mark"; personB.age = 23;
Object 函數是我們的構造器,采用 “var personB = new Object()” 方式和采用 “var personA = {}” 是等價的。采用 new Object(),我們創建了一個空對象,這個空對象被成為是 Object 的一個實例。
Object constructor 因其代表著JavaScript 的基礎對象而顯得與眾不同:所有的對象,不論這些對象是由哪個 constructor 創建出來的,本質上都是 Object 的實例。使用 instanceof 操作符可以判斷一個對象是否是一個 constructor 的實例。
// 使用對象字面量 var personA = {}; // 使用構造器 var personB = new Object(); // 檢測上面2個對象是否是Object的實例 conlose.log(personA instanceof Object) // true conlose.log(personB instanceof Object) // true
每一個對象都有一個名字為 constructor 的特殊屬性,其是對創建該對象本身的 constructor 函數的引用。在我們上面的簡單例子中,constructor的屬性值是 Object constructor:
// 使用對象字面量 var personA = {}; // 使用構造器 var personB = new Object(); // 檢測是否使用了Object的constructor conlose.log(personA.constructor == Object) // true conlose.log(personB.constructor == Object) // true
就像它的名字所示,constructor 函數,顯然的,是一個函數。事實上,任何一個 JavaScript 函數都能被用作構造函數。這是JavaScript 對象處理方面的一個獨特的地方。不同于在對象實例化時創建一個新的構造,Javascript 是依賴于現有的構造。
當然,你不必將你創造的所有函數都用作構造函數。大部分情況下,你會為你的類創建一個專用于構造目的的函數。一個構造函數和其他函數一樣——除了自身細節上有些許區別——慣常的做法是將函數名首字母大寫以表示其存在目的是作為一個構造函數。
// 一個person構造函數 var Person = {}; // 以正規函數方式使用Person var result = Person(); console.log(result); // undefined // 以構造器函數調用Person var person = new Person(); console.log(typeof person); // "object" console.log(person instanceof Person); // true console.log(person.constructor == Person); // true
我們通過一個簡單的空函數來創建一個構造器。當Person函數被采用常規方式調用時,它返回 undefined。當我們在調用之前加上一個 new 關鍵字的時候,情況就變了:它返回了一個新對象。配合使用 new 關鍵字可以使一個函數被作為構造器使用進而產生一個對象的實例化。
在我們的例子中,new Person() 返回了一個空對象,這和使用 new Object() 的返回是一樣的。這里的區別是,返回的對象不單單是 Object 的實例,同時也是 Person 的實例,并且該對象的 constructor 屬性現在指向的是新的 Person 對象而非 Object 對象。不過返回的總歸還是一個空對象。
回顧一下上一章講到的,函數內的 this 關鍵字指向的是一個對象。在這個關于我們的 Person 函數的例子中,當它被作為平臺函數調用時,引起被定義在全局作用域中,所以 this 關鍵字指向的對象是 global 對象。但當 Person 被作為一個構造函數時,情況就變了。this 關鍵字不再指向 global 對象,而是指向新創建出來的那個對象:
// 一個全局變量 var fruit = "banana"; // 我們的constructor var Person = function() { console.log(this.fruit); }; // 被作為普通函數使用時 fruit(); // "banana" // 被作為constructor使用時 new Person(); // undefinded
最后一行的代碼輸出的是 undefined,這是因為 this.fruit 不再指向一個已存在的變量標識符。new 關鍵字的作用就是創建一個新對象,并將構造函數內的 this 指向這個新創建的對象。
在本章的開始部分,我們遇到了一個使用對象字面量創建多個對象的問題——我們需要一個方法來批量的創建對象的拷貝而非一個個的去敲代碼把它們全寫一遍。現在我們知道構造函數可以做到這一點,并且其內的 this 關鍵字指向的就是新創建的對象。
var Person = function(name, age) { this.name = name; this.age = age; }; var mark = new Person("Mark", 23); var joseph = new Person("Joseph", 22); var andrew = new Person("Andrew", 21); console.log(mark.name); // "Mark" console.log(joseph.age); // 22 console.log(andrew.name + ", " + andrew.age); // "Andrew, 21"
你會注意到這里我們對構造函數進行了一些修改使其可以接受參數。這是因為構造函數和普通函數一樣,只不過其內部的 this 關鍵字指向的是新創建的對象。當 new Person 被執行的時候,一個新的對象被創建出來,并且 Person 函數被調用。在構造函數內部,參數 name、age 被設置為同名對象屬性的值,之后這個對象被返回。
使用構造函數可以很輕松的創建出和構造函數具有類似結構的新對象,并且不用費事的每次都為新對象用字面量的方式書寫一遍結構。你可以在編碼的開始階段就創建一個定義了基本結構的構造函數,這對你以后為實例化的對象們增加新的屬性或方法遲早會有幫助。
var Person = function(name, age) { this.name = name; this.age = age; this.log = function() { console.log(this.name + ", " + this.age); } }; var mark = new Person("Mark", 23); var joseph = new Person("Joseph", 22); var andrew = new Person("Andrew", 21); mark.log(); // "Mark, 23" joseph.log(); // "Joseph, 22" andrew.log(); // "Andrew, 21"
這里你會看到我們在構造函數里新增了一個 log 方法,該方法會將對象的 name 和 age 信息打印出來。這樣就避免了在對象實例化之后還要手工的為每一個對象增加 log 方法。
原型(Prototypes)看起來似乎構造函數已經是關于 JavaScript 對象創建的終極知識點了,但請注意,還沒結束呢!我們現在還只說了二分之一而已。如果我們把自己局限在僅僅使用構造函數的范圍,那么很快就會遇到新問題。
問題之一就是代碼組織。在上一節的開頭,我們想有一種簡單的方法可以批量創建具有 name 和 age 屬性的 person 對象,并且期望同時具備 setName、getName、setAge、getAge 等方法。如果按照我們現在的需求,沿用上一節的方式,最終我們的代碼會變成下面這個樣子:
var Person = function(name, age) { // 屬性 this.name = name; this.age = age; // 方法 this.setName = function(name) { this.name = name; } this.getName = function() { return this.name; } this.setAge = function(age) { this.age = age; } this.getAge = function() { return this.age; } };
現在我們的 Person 構造器開始變得腫脹了——這還僅是包含了2個屬性和4個方法的時候!想想如果你要創建一個很復雜的應用,那構造函數得變得多么龐大!
另一個問題是可擴展性。假設我們有如下代碼:
// constructor.js var Person = function(name, age) { this.name = name; this.age = age; this.log = function() { console.log(this.name + ", " + this.age); } }; // program.js var mark = new Person("Mark", 23); mark.log(); // "Mark, 23"
現在Person是在外部引入的一個JS文件中定義的,我們在這個頁面里引入定義了 Person 構造函數的 constructor.js 文件,并實例化了一個 mark 對象。現在問題來了,因為我們現在無法修改構造函數本身,那該如何為實例增加 setName、getName、setAge、getAge 等方法呢?
解決方案似乎很簡單,既然不能通過修改構造函數來增加方法,那就直接給實例增加方法不就行了么~很快隨著鍵盤的敲打,代碼變成了下面這個樣子。
// constructor.js var Person = function(name, age) { this.name = name; this.age = age; this.log = function() { console.log(this.name + ", " + this.age); } }; // program.js var mark = new Person("Mark", 23); mark.log(); // "Mark, 23" mark.getName = function() {return this.name;} mark.getAge = function() {return this.age;} mark.getName(); // "Mark" mark.getAge(); // 23 var joseph = new Person("Joseph", 22); mark.log(); // "Joseph, 22" // 下面的代碼會引起報錯 joseph.getName(); joseph.getAge();
雖然我們成功的為 mark 實例添加了需要的方法,但 joseph 實例并不能同樣獲得這些方法。此時我們遇到了和使用對象字面量一樣的問題:我們必須為每一個對象的實例做同樣的設置才行,這顯然是不實用的。我們需要一個更有“療效”的方法。
在本章的開頭我們說過,Javascript 是一門基于原型的語言,基于原型的語言最重要的特征就是創建對象是通過對一個目標對象的拷貝來實現,而非通過類。但我們目前還未提及過拷貝,或者作為原型的目標對象,我們目前為止看到的都是構造函數配合著new關鍵字。
我們的線索就是new關鍵字。記住當我們使用 new Object 時,new 關鍵字創建了一個新的對象,并將該對象作為構造函數內this 關鍵字指向的對象。實際上,new 關鍵字并未創建一個新的對象:它只是拷貝了一個對象。這個被拷貝的對象不是別的,正是原型(prototype)。
所有能被作為構造函數使用的函數都有一個 prototype 屬性,這個屬性對象定義了你實例化對象的結構。當使用 new Object 時,一個對 Object.prototype 的拷貝被創造出來,這個拷貝就是新創建的那個實例對象。這是 Javascript 的另一個有趣的特點:不同于其它的原型語言——對它們來說,任何對象都能作為原型使用;但在Javascript中,卻有一個專為作為原型使用 prototype 對象存在。
注意:對 Javascript 而言,這是一種對其他原型性語言的模仿:對其他原型性語言而言,你可以直接克隆一個對象來得到新的對象,在 Javascript 中則是依賴克隆目標對象的 prototype 屬性。在本章的最后一節你會學到實現這一做法。
prototype 對象,和其他對象一樣,對其內部可容納的成員沒有數量上的限制,對其增加一個成員基本上就是簡單的附加一個值而已。下面我們對之前的 Person 函數進行一番改寫:
var Person = function(name, age) { this.name = name; this.age = age; }; Person.prototype.log = function() { console.log(this.name + ", " this.age); } var mark = new Person("Mark", 23); mark.log(); // "Mark, 23"
可以看到,我們將 log 方法的定義移出構造函數,通過 Person.prototype.log 的方式去定義,這樣我們就能告訴解析器所有從 Person 構造函數實例化出來的對象都將具有 log 方法,所以最后一行的 mark.log() 會執行。剩余的構造函數還是保持原樣,我們并未把 this.name 和 this.age 也放在 prototype 中去,因為我們還是希望在對象實例化之時就能初始化這些值。
有了 prototype 這個利器,我們就可以對開頭的代碼進行重構,并使其變得更具可維護性:
var Person = function(name, age) { this.name = name; this.age = age; }; Person.prototype.setName = function(name) { this.name = name; }; Person.prototype.getName = function() { return this.name; }; Person.prototype.setAge = function(age) { this.age = age; }; Person.prototype.getAge = function() { return this.age; };
上面這段代碼還可以像下面這樣合并著來寫:
var Person = function(name, age) { this.name = name; this.age = age; }; Person.prototype = { setName : function(name) { this.name = name; }, getName : function() { return this.name; }, setAge : function(age) { this.age = age; }, getAge : function() { return this.age; } }
現在好多了,再也沒有那么多的東西擁擠在構造函數內了。而且以后一旦需要增加新的方法,只需要按照給 prototype 增加即可,而不用去重新整理構造函數。
我們曾經有的另一個問題(第一個是快捷創建多個實例對象,見上面)是在無法修改構造函數的情況下給實例成員添加新的方法,現在隨著我們打通了一個通往構造函數的大門(prototype屬性),我們可以輕松的在不通過構造函數的情況下為實例對象添加方法。
// person.js var Person = function(name, age) { this.name = name; this.age = age; }; // program.js Person.prototype.log = function() { console.log(this.name + ", " + this.age); }; var mark = new Person("Mark", 23); mark.log(); // "Mark, 23" var joseph = new Person("Joseph", 22); joseph.log(); // "Joseph, 22"
在前面我們已經看到了一些簡單的動態豐富 prototype 的例子。一個函數對象,以構造函數來確定其形式,并可通過Mootools 的 Function.implement 函數為其增加新的方法。所有 Javascript 函數其實都是 Function 對象的實例,Function.implement 實際上就是通過修改 Function.prototype 對象來實現的。雖然我們并不能直接操作 Function 的構造函數——一個由解析器提供的內置構造——但我們依然可以通過 Function.prototype 來為 Function 對象增加新的方法。對原生方法類型的增益我們將會在后面“衍生與原生”(Types and Natives)一節中進行討論。
繼承(Inheritance)為了更高的理解 Javascript 是一門基于原型的語言,我們需要區分原型與實例之間的區別。原型(prototype)是一個對象,它就像一個藍本,用來定義我們需要的對象結構。通過對原型的拷貝,我們可以創造出一個該原型的實例(instance):
// 動物的構造器 var Animal = function(name) { this.name = name; }; // 動物的原型 Animal.prototype.walk = function() { console.log(this.name + " is walking."); }; // 動物的實例 var cat = new Animal("Cat"); cat.walk(); // "Cat is walking"
上面的代碼中,構造函數 Animal 和它的 prototype 一起定義了 Animal 對象的結構,cat 對象是 Animal 的一個實例。當我們執行 new Animal() 語句,一個 Animal.prototype 的拷貝就被創建,我們稱這個拷貝為一個實例(instance)。Animal.prototype 是一個只有一個成員的對象,這個唯一的成員是 walk 方法。自然,所有 Animal 的實例都會自動擁有 walk 這個方法。
那么,當我們在一個實例已經被創建之后再去修改 Animal.prototype ,會發生什么呢?
// 動物的構造器 var Animal = function(name) { this.name = name; }; // 動物的原型 Animal.prototype.walk = function() { console.log(this.name + " is walking."); }; // 動物的實例 var cat = new Animal("Cat"); cat.walk(); // "Cat is walking" // 難道動物不應該擁有吃(eat)這個方法嗎? console.log(typeof cat.eat); // undefined --> 沒有 TT // 給動物增加一個“吃”的方法 Animal.prototype.eat = function() { console.log(this.name + " is eating."); }; console.log(typeof cat.eat); // "function" cat.eat(); // "Cat is eating"
嘿,現在這發生的事有點意思哈?在我們創建好 cat 實例時候,檢測 eat 方法顯示的是 undefined。在我們給 Animal.prototype 對象新增了一個 eat 方法之后,cat 實例就擁有了吃的能力!實際上,cat 的“吃”的能力就是我們給 Animal.prototype 增加的那個函數。
看起來,似乎是不論我們什么時候給原型增加新的方法,這都會自動觸發全部的實例進行一次更新。但記住當我們新創建一個對象,那么這個新的操作就會創建一個新的原型拷貝。當我們創建 cat 時,原型還僅擁有一個方法。如果這是一個純粹的拷貝,那就不應該擁有我們之后才設置的 eat 方法。畢竟,當你復印了一份文檔,之后在源文檔上又寫上一句 “天朝人民最幸福”,你不能指望那份復印的文檔上也立即出現同樣的字句,不是嗎?
或者是解析器知道什么時候 prototype 新增了成員并自動給全部的實例都增加上這個方法?也許是當我們給原型增加了 eat 這個方法后,解析器便立刻給全部的 Animal 實例增加上了這個方法?對于這一點的驗證是很簡單的:我們可以先給實例設置一個 eat 的方法,之后再給原型增加 eat 方法。如果上面的猜測是對的,那么后增加的原型的 eat 方法會覆蓋掉較早給 Animal 實例多帶帶設置的那個 eat 方法。
// 動物的構造器 var Animal = function(name) { this.name = name; }; // 動物的原型 Animal.prototype.walk = function() { console.log(this.name + " is walking."); }; // 動物的實例 var cat = new Animal("Cat"); cat.walk(); // "Cat is walking" // 給cat增加一個eat的方法 cat.eat = function() { console.log("Meow. Cat is eating."); }; // 給動物增加一個“吃”的方法 Animal.prototype.eat = function() { console.log(this.name + " is eating."); }; cat.eat(); // "Meow. Cat is eating."
很明顯,前面的猜測是錯誤的。Javascript 解析器不會更新實例。那真實的情況到底是什么呢?
所有的對象都有一個叫做 proto 的內置屬性,該屬性指向該對象的原型。解析器利用該屬性將對象“鏈接”到它對應的原型上。雖然在使用 new 關鍵字的時候確實是創建了一個原型的拷貝,且這個拷貝看起來確實很像原型本身,但它實際上卻是一個“淺拷貝”。真相是,當這個實例被創建時,它實際上只是一個空對象,這個空對象的 proto 屬性指向了其構造函數的 prototype 對象。
你可能會問:“等等,既然這個新的實例是一個空對象,那為什么它還會像其來源的原型那樣具有屬性和方法呢”?其實這就是 proto 屬性的作用。實例對象通過 proto 屬性鏈接到它的原型,這樣它原型上的屬性和方法也能被其實例對象訪問到。在我們的例子中,cat 對象本身被沒有 walk 的方法。當解析器讀取到 cat.walk() 語句時,它首先檢測 cat 對象自身的prototype 對象中有無 walk 這個方法成員,如果沒有,就通過 cat 的 proto 屬性上溯到其原型的 prototype 中去尋找 walk 方法。而正好在這里解析器找到了它需要的方法,于是我們的 cat 就能執行“走”的動作了。
這也能解釋為什么上面的代碼中最后 log 出的信息是“Meow. Cat is eating.”,因為我們給實例對象 cat 的 prototype 屬性對象增加了 eat 這個方法成員,于是解析器先在這里找到了它需要的 “eat 方法,進而 cat 的原型 prototype 中的 eat 方法就不會起作用了。
一個實例對象的成員(屬性啊方法啊神馬的)來自于它的原型(而非是針對這個實例對象多帶帶設置),被稱為繼承(inheritance)。對所有對象,你都能使用 hasOwnProperty 方法來檢測某個成員是不是隸屬于它。
var Animal = function() {}; Animal.prototype.walk = function() {}; var dog = new Animal(); var cat = new Animal(); cat.walk = function() {}; console.log(cat.hasOwnProperty("walk")); // true console.log(dog.hasOwnProperty("walk")); // false
這里,我們對 cat 使用 .hasOwnProperty(walk) 檢測,返回為true,這是因為我們已經對 cat 多帶帶設置了一個它自己的 walk 方法。對應的,因為 dog 對象并未被賦以一個多帶帶的 walk 方法,所以檢測結果為 false。另外,如果對 cat 采用 .hasOwnProperty(hasOwnProperty),返回的同樣會是 false。這是因為 hasOwnProperty 實際上是 Obiect 對象的方法,而 cat 對象由 Object 處繼承而來。
現在有一個家伙需要我們好好的去考慮一下:this。在構造函數內的 this,其永遠指向構造函數的實例化對象而非構造函數的 prototype 對象。但是在原型內定義的函數則遵循另一個法則:如果該方法是直接的由原型方式來調用,則該方法內的 this 指向的是這個原型對象本身;如果該方法由這個原型的實例化對象來引用,則方法內的 this 關鍵字就會指向這個實例化對象。
var Animal = function(name) { this.name = name; }; Animal.prototype.name = "Animal"; Animal.prototype.getName = function() { return this.name; }; // 直接使用原型方法來調用“getName” Animal.prototype.getName(); // 返回 "Animal" var cat = new Animal("Cat"); cat.getName(); // 返回 "Cat"
這里我們對代碼進行了一些小的修改,以便 Animal.prototype 可以有其自己的 name 屬性。當我們直接用原型方式調用 getName 時,返回的是 Animal.prototype 的 name 屬性。但當我們通過實例化對象去執行 cat.getName() 時,返回的就是 cat 的 name 屬性。
原型和實例是不同的對象,它們之間唯一的聯系是:針對原型做的修改會反射到所有該原型的實例對象,但對某具體實例對象的修改卻只對該實例對象本身起作用。
記住在 Javascript 中同時存在著基本數據類型和復合數據類型。如字符串、數字以及布爾值等都屬于基本數據類型:當它們被作為參數傳遞給函數或被賦值于一個變量時,被使用的都是它們的拷貝。而像數組、函數、對象這樣的復合數據類,被使用的則是它們的引用。
// 創建一個對象 var object = {name : "Mark"}; // 把這個對象“拷貝”給另一個變量 var copy = object; console.log(object.name); // "Mark" console.log(copy.name); // "Mark" // 更改copy對象的name值 copy.name = "Joseph"; console.log(object.name); // "Joseph" console.log(copy.name); // "Joseph"
當 var copy = object 被執行時,沒有新的對象被創建出來。copy 變量其實只是指向了 object 所指向的同一個對象。object 和 copy 現在都是指向同一個對象,自然從 copy 處對其指向對象做的改動,object 也會得到反射。
對象可以擁有復合數據類型的成員,對象自身的 prototype 也同樣如此。所以便出現了下面這個需要被注意的問題:當給一個指向復合數據類型的原型增加新的成員時,因為所有該原型的實例對象也都指向該原型本身,所以對原型的改動也會被繼承。
var Animal = function() {}; Animal.prototype.data = { name : "animal", type : "unknow" }; Animal.prototype.setData = function(name, type) { this.data.name = name; this.data.type = type; }; Animal.prototype.getData = function() { console.log(this.data.name + ": " + this.data.type); }; var cat = new Animal(); cat.setData("Cat", "Mammal"); cat.getData(); // "Cat: Mammal" var shark = new Animal(); shark.setData("Shark", "Fish"); shark.getData(); // "Shark: Fish" cat.getData(); // "Shark: Fish"
因為我們的 cat 和 shark 對象都沒有自己的 data 屬性,所以它們從 Animal.prototype 處繼承而來,所以 cat.data 和 shark.data 都指向了 Animal.prototype 中定義的 data 對象,對任何一個實例的 data 對象的更改都會引起我們不希望看到的行為。
最簡單的解決辦法就是將 data 屬性從 Animal.prototype 中移除并在每個實例對象中多帶帶定義它們。通過構造函數來實現這一點是很簡單的。
var Animal = function() { this.data = { name : "animal", type : "unknow" }; }; Animal.prototype.setData = function(name, type) { this.data.name = name; this.data.type = type; }; Animal.prototype.getData = function() { console.log(this.data.name + ": " + this.data.type); }; var cat = new Animal(); cat.setData("Cat", "Mammal"); cat.getData(); // "Cat: Mammal" var shark = new Animal(); shark.setData("Shark", "Fish"); shark.getData(); // "Shark: Fish" cat.getData(); // "Cat: Mammal"
因為此時構造函數內的 this 關鍵字在此處是指向實例化對象的,所以 this.data 也就為每一個對象多帶帶賦予了一個 data 屬性,且不會影響到構造函數的原型。進而會看到,最后的輸出結果也正是我需要的那樣。
原型鏈(The Prototype Chain)在 Javascript 中,Object 是基礎對象模型。其他對象不論是具備如何不同的構造,都是會從 Object 對象處獲得繼承。下面的代碼足夠幫助我們來理解這一點:
var object = new Object(); console.log(object instanceof Object); // true
因為我們是按照 Object 的構造函數來創建的 object 對象,所以我們可以說 object 對象的內部屬性 proto 指向的就是 Object 的 prototype 屬性。現在,再來看下面這段代碼。
var Animal = function()
{};
var cat = new Animal(); console.log(cat instanceof Animal); // true console.log(cat instanceof Object); // true console.log(typeof cat.hasOwnProperty()); // "function"
因為使用 new Animal() 的緣故,所以我們知道 cat 實際上是一個 Animal 的實例。而且我們還知道所有對象都有一個繼承自 Object 的 hasOwnProperty 屬性。于是我們就要問了,既然 object 對象的 proto 屬性現在指向的是 Animal 的原型,那這里又是怎么做到的 object 能在未涉及 Object 構造函數的情況下還能同時從 Animal 和 Object 獲得繼承呢?
答案就在原型之間。默認情況下,構造函數的 prototype 對象是一個不含任何方法只含有其構造函數中設置的屬性的基本對象。這聽起來很熟悉不是嗎?這和我們使用 new Object() 創造出來的對象是一樣的!實際上我們的代碼還可以像下面這樣來寫。
var Animal = function() {}; Animal.prototype = new Object(); var cat = new Animal(); console.log(cat instanceof Animal); // true console.log(cat instanceof Object); // true console.log(typeof cat.hasOwnProperty()); // "function"
現在就已經很清晰了,Animal.prototype 由 Object.prototype 處繼承而來。對于一個實例而言,除了會從它自身的 prototype 對象繼承之外,還會從 它原型的原型的 prototype 對象處繼承。
感到費解?那就通過對上面的代碼進行分析來加強一下對這點的理解。我們的 cat 對象是由 Animal 對象實例化而來,所以 cat 會繼承 Animal.prototype 的屬性和方法。而 Animal.prototype 是由 Object 實例化而來,所以 Animal.prototype 會繼承 Object.prototype 的屬性和方法。進而 cat對象 會同時繼承 Animal.prototype 和 Object.prototype 的屬性和方法,所以我們說 cat 是間接繼承(indirectly inherits)了 Object.prototype 對象。
我們的 cat 對象的 proto 屬性指向了 Animal.prototype 對象;而 Animal 的 proto 屬性則指向 Object.prototype 對象。這種 prototype 原型之間持續的鏈向被稱為原型鏈(prototype chain)。進而我們說 cat 對象的原型鏈展度為從其自身一直到 Object.prototype。
注意:所有對象原型鏈的終點都是 Object.prototype,且 Object 的 proto 屬性不指向任何一個對象——否則原型鏈就會變得沒有邊界而導致基于原型鏈的上溯流程變得無法終止。Object.prototype 對象本身非由任何構造函數產生,而是由解析器內置的方法創建,這使得 Object.prototype 成為唯一一個不是由 Object 實例化而來的對象。
沿著一個對象的原型鏈查找屬性或方法的行為我們稱之為遍歷(traversal)。當解析器遇到 cat.hasOwnProperty 語句時,解析器首先在當前對象的 prototype 對象中查找相關方法。如果沒有,則順序的在原型鏈上下一個對象—— Animal.prototype 上查找。還是沒有,則繼續在下一個對象的 prototype 上查找,以此類推。一旦解析器找到了它要的方法,解析器就會使用當前找到的這個方法,其在原型鏈上的遍歷也會停止。如果解析器在整個原型鏈上都找不到它需要的方法,它就會返回 undefined。在我們的例子中,解析器最后在 Object.prototype 對象上找到了 hasOwnProperty 方法。
一個對象總是屬于至少一個構造函數的實例:不論是使用對象字面量還是對象構造函數創造出來的對象,總都屬于 Object 的實例。對那些非直接由 Object 構造函數創造出來的對象而言,它們既是直接創建它們的構造函數的實例,同時還是它們原型鏈上所有 prototype 對象對應的構造函數的實例。
有考量的原型鏈(Deliberate Chains)一旦我們要創建更為復雜的對象,原型鏈就會變得非常有用。比如我們現在要創建一個 Animal 對象:所有的動物都有名字(name),所有的動物還要能夠吃東西(eat)來活下去。OK,下面是我們的代碼:
var Animal = function(name) { this.name = name; }; Animal.prototype.eat = function() { console.log("The " + this.name + " is eating."); }; var cat = new Animal("cat"); cat.eat(); // "The cat is eating" var bird = new Animal("bird"); bird.eat(); // "The bird is eating"
目前為止一切都還好。不過現在需要動物們能發出聲音,于是我們需要增加新的方法。顯然,這些動物發出的聲音應該是不一樣的:貓咪的叫聲是“meow”,小鳥的叫聲是“tweet”。我們可以為每一個動物實例多帶帶設置發聲的方法,但顯然在面對一個需要創造多個貓咪和小鳥的需求面前,這種做法是不合事宜的。我們似乎還可以通過為 Animal.prototype 增加方法來達到貓咪和小鳥等實例都具備發聲的能力,但這還是在浪費精力:因為貓咪不會發出“tweet”的聲音,小鳥也不會“meow”的叫。
那我們為每個實例對象自身的構造函數多帶帶設置方法行不行呢?我們可以制造出 Cat、Bird 的構造器并為其分別設置不同的發聲方式。而“吃”的能力則還是從 Animal.prototype 那繼承而來:
var Animal = function(name) { this.name = name; }; Animal.prototype.eat = function() { console.log("The " + this.name + " is eating."); }; var Cat = function() {}; Cat.prototype = new Animal("cat"); Cat.prototype.meow = function() { console.log("Meow!"); }; var Bird = function() {}; Bird.prototype = new Animal("bird"); Bird.prototype.tweet = function() { console.log("Tweet!"); }; var cat = new Cat(); cat.eat(); // "The cat is eating" cat.meow(); // "Meow!" var bird = new Bird(); bird.eat(); // "The bird is eating" bird.tweet(); // "Tweet!"
可以看到,我們保留了原有的 Animal 構造函數,并且基于它新建了另外兩個更具體的構造函數——Cat 和 Bird。之后我們分別為 Cat 和 Bird 設置了它們自己的發聲方式。這樣,我們最終的實例對象貓咪和小鳥就都能發出它們各自不同的叫聲了。
在基于類的程序語言中,這種直接繼承了其實例化來源的類的特征,且更具針對性的分支被稱為子類(subclassing)。Javascript,則是一門基于原型的語言,并沒有類的概念,就其本質而言,我們唯一所做的就是創造了一個有考量的原型鏈(deliberate prototype chain)。這里之所以用“有考量”這個詞,是因為我們顯然是有意的設計了哪些對象應該出現在我們的實例原型鏈上。
原型鏈上的成員數量沒有限制,你還可以通過豐富原型鏈上的對象來滿足更有針對性的需求。
var Animal = function(name) { this.name = name; }; Animal.prototype.eat = function() { console.log("The " + this.name + " is eating."); }; var Cat = function() {}; Cat.prototype = new Animal("cat"); Cat.prototype.meow = function() { console.log("Meow!"); }; var Persian = function() { this.name = "persian cat"; }; Persian.prototype = new Cat(); Persian.prototype.meow = function() { console.log("Meow..."); }; Persian.prototype.setColor = function() { this.color = color; }; Persian.prototype.getColor = function() { return this.color; }; var king = new Persian(); king.setColor("black"); king.getColor(); // "black" king.eat(); // "The persian cat is eating" king.meow(); // "Meow..." console.log(king instanceof Animal); // true console.log(king instanceof Cat); // true console.log(king instanceof Persian); // true
這里我們創造了一個名為 Persian(波斯貓) 的 Cat 分支。你會注意到這里我們設置了一個 Persian.prototype.meow 的方法,這個方法在 Persian 的實例中會覆蓋掉 Cat.prototype.meow。如果你檢查一下,會發現 king 對象分別是 Animal、Cat 和 Persian 的實例,這也說明了我們原型鏈的設計是正確的。
原型鏈真正的威力在于繼承與原型鏈遍歷的結合。因為原型鏈上所有的 prototype 對象都是鏈起來的,所以原型鏈上某一點的改變會立即反射到它所指向的其他成員對象。如果我們給 Animal.prototype 新增一個方法,那么所有 Animal 的實例都會新增加上這個方法。這位我們批量的為對象擴充方法提供了簡易快捷的方式。
如果你的程序正變得愈加龐大,那么有考量的原型鏈會幫助你的代碼更具結構性。不同于把所有的代碼都塞進一個 prototype 對象中,你可以創建多重的具備良好設計的 prototype 對象,這對減少代碼量、提升代碼的可維護性都很有好處。
簡化原型的編程(Simplified Prototypal Programming)現在你應該已經意識到 Javascript 的面向對象風情有其獨到的范式。Javascript 所謂的“基于原型的程序語言”很大程度上是僅限于名義上的。Javascript 中有著本應是在基于類的語言中才會出現的構造函數和 new 關鍵字的組合,同時將從原型——這個顯著的原型式語言的特征——處繼承來的東西作為其用以實現針對性 prototype 對象的依據,而這些更具針對性的 prototype 對象,則是那么的類似類式語言中的子類。這門語言在對象機制實現方面的設計一定程度上受到了當時程序語言潮流的影響:在這門語言被創建的那個時代,基于類的程序語言處于正統的標準地位。所以,最終的決定就是為這門新語言賦予一些同類式語言相似的特征。
盡管如此,Javascript 依然是一門靈活的語言。雖然我們不能改變在其核心中定義的對象的實現機制,但我們依然可使用現有手段令這門語言散發出更純粹的原型式風格(當然我們在下一章中會看到另一種流派——如何使這門語言在實際中更具備類式風格)。
在我們現在所討論的簡化原型的范疇內,讓我們把視線從 Javascript 本身那具備復合性特征的原型上先移開,只先關注對象本身。不同于先創建一個構造函數之后再設置其 prototype,我們使用真的對象作為原型來創建新的對象,并將其prototype屬性“克隆”到新創建的對象身上。為了更明確的說明我們要做的,這里先舉一個例子,這個例子來自另一個純粹的原型式程序語言 IO:
Animal := Object clone Animal name := "animal" Cat := Animal clone Cat name := "cat" myCat := Cat clone
雖然這不是一本關于 IO 語言的書,但我們還是從基礎講起。同 Javascript 一樣,IO 中的基礎對象也是 Object。不過,這里的 Object 并不是一個構造器(厄,一個函數),而是一個真正的對象。在我們代碼的開始部分,我們創造了一個新的對象—— Animal,這個新對象由源對象 Object 處克隆而來。因為在 IO 語言中,空格用來訪問屬性,所以 Object clone 語句的含義就是“使用 Object 的 clone 方法并執行它”。之后我們為 Animal 的 name 屬性設置了一個字符型的值,通過克隆 Animal 創建了一個名為 Cat 的新對象,同時我也為這個 Cat 對象設置了 name 屬性,最后我們克隆 Cat 得到一個 myCat 對象。
我們可以在 Javascript 中實現類似的事:
var Animal = function() {}; Animal.prototype = new Object(); Animal.prototype.name = "animal"; var Cat = function() {}; Cat.prototype = new Object(); Cat.prototype.name = "cat"; var myCat = new Cat();
很像,但卻不完全一樣。在 IO 的例子中,最終的 myCat 是直接由 Cat、Animal、Object 處克隆而來的,這些都是純粹的對象而非構造器。但在我們的 Javascript 的例子中,最終的 myCat 對象則是由Cat、Animal、Object 等對象的 prototype 屬性繼承而來,Cat、Animal、Object 等也都是函數而非對象。換句話說。IO 沒有構造函數的概念,一切都是直接從對象克隆而來。但 Javascript 卻有構造函數,且克隆的是 prototype。
如果我們能控制內部屬性 proto,那么我們就能在 Javascript 中實現和 IO 一樣特性。 例如,假如我們有一個 Animal 對象和一個 Cat 對象,我們可以改變 Cat 對象的 proto屬性使之直接鏈向 Animal 對象(而非鏈向 Animal 的 prototype 對象)本身,這樣 Cat 就能直接繼承 Animal 對象。
因為 proto 屬性是內置屬性不能直接修改它,但一些 Javascript 解析器卻引入了一個和其類似的名為 proto 的屬性。一個對象的 proto 屬性被用作更改其內置的 proto 屬性,以使其可以直接鏈向其他對象。
var Animal = { name : "animal", eat : function() { console.log("The " + this.name + " is eating.") } }; var Cat = {name : "cat"}; Cat.__proto__ = Animal; var myCat = {}; myCat.__proto__ = Cat; myCat.eat(); // "The cat is eating."
這里不存在構造函數,Animal 和 Cat 對象直接由字面量創建。通過 Cat.__proto__ = Animal 語句我們告訴解析器 Cat 的 proto 屬性直接指向 Animal 對象。最后 myCat 對象都直接從 Cat 和 Animal 處得到繼承,在 myCat 的原型鏈上也不存在任何為 prototype 的對象。這個簡化的原型模型不包含任何的構造器或原型屬性,而是替代的將真實的對象本身放置其原型鏈上。
類似的,你可以使用 Object.create 方法來達到同樣的效果,這個新函數目前已經被 ECMAScript 5 正式引入。它只接受一個參數,該參數為一個對象,其執行的結果是創建一個空對象,而這個對象的 proto 屬性將被指向作為參數傳入的那個對象。
var Animal = { name : "animal", eat : function() { console.log("The " + this.name + " is eating.") } }; var Cat = Object.create(Animal); Cat.name = "cat"; var myCat = Object.create(Cat); myCat.eat(); // "The cat is eating."
注意這里的 Object.create 方法和 IO 里的 clone 方法很相像,實際上,它們實現的也是同一件事。我們可以使用 Object.create 方法非常高仿的實現 IO 語言的那個片段:
var Animal = Object.create({}); Animal.name = "animal"; var Cat = Object.create(Animal); Cat.name = "cat"; myCat = Object.create(Cat);
不幸的是,雖然上面的兩種方式都很美妙,但它們卻不能兼容所有平臺。__proto__ 屬性目前還不屬于正式的 ECMAScript 規范,所以并不是所有的解析器都對其提供支持。而 Object.create() 方法,雖然是規范中的一員,但該規范卻是指 ECMAScript 5。因該規范是2009年才頒布的,所以目前也不是所有解析器都能提供完整的支持。如果你希望寫出具有更好兼容性的代碼(尤其是 web app 程序),就尤其要記住這2種方式都不是通用方案。
現在有一種方案可以使較為古老的解析器也能支持 Object.create 方法。就是記住 Javascript 對象通過引用來起作用,如果你將一個對象存儲在變量 x 中,之后操作 y = x,那么 y 和 x 將同時指向同一個對象。同時,一個函數的 prototype 屬性也是一個對象,而這個對象的初始值可以很輕易的通過被分配給一個新的對象值來覆蓋:
var Animal = { name : "animal", eat : function() { console.log("The " + this.name + " is eating.") } }; var AnimalProto = function() {}; AnimalProto.prototype = Animal; var Cat = new AnimalProto(); console.log(typeof cat.purr); // "undefinded" Animal.purr = function() {}; console.log(typeof cat.purr); // "function"
這段代碼現在看來應該有些眼熟了吧。我們首先創建了一個有著2個成員(一個name 屬性、一個 eat 方法)的 Animal 對象,之后我們創建了一個名為 AnimalProto 的“跳板級”構造函數,并將它的 prototype 屬性設置為 Animal 對象。因為引用的緣故,AnimalProto.prototype 屬性 和 Animal 現在都指向了同一個對象。這就意味著,當我們創建了 cat 實例時,它實際上是直接繼承自 Animal 對象 —— 這就像是使用 Object.create 方法創造出來的一樣。
采用這個點子,我們可以模擬出 Javascript 解析器所不支持的 Object.create 方法。
if (!Object.create) Object.create = function(proto) { var Intermediate = function() {}; Intermediate.prototype = proto; return new Intermediate(); }; var Animal = { name : "animal", eat : function() { console.log("The " + this.name + " is eating.") } }; var Cat = Object.create(Animal); console.log(typeof cat.purr); // "undefinded" Animal.purr = function() {}; console.log(typeof cat.purr); // "function"
最開始,我們使用一個 IF 語句來判斷當前解析器是否支持 Object.create 方法。如果支持,則直接執行下面的語句,如果不支持,就模擬一個該方法:它首先創造一個名為 Intermediate 的構造器,之后將該構造器的 prototype 屬性指向作為參數傳入的那個對象。最后該函數返回一個 Intermediate.prototype 的實例。因為這里我們使用的方法都是當下解析器所支持的,所以我們可以說這個模擬的 Object.create 方法是具備普適性的。
總結(The Wrap Up)在這一章,我們詳細的討論了有關 Javascript 對象機制的所有話題,并展示了它和其他語言之間的區別。雖然它是一門基于原型的語言,但因為其自身的一些獨特性,使其實際上是兼具類式和原型式語言的特征。我們看到了如何使用字面量和構造器的 prototype 屬性來新建對象。我們還展示了繼承的奧秘、Javascript 原型鏈上的遍歷是如何工作的。最后我們還實踐了一個將 Javascript 本身的原型混雜性隱藏起來的簡便原型式模型。
因為 Javascript 的核心是一門面向對象的語言,所以在這里所寫的針對該點的知識,會在我們開發復雜應用時候提供莫大的幫助。雖然面向對象本身已經超越了本書所要講述的范圍,但我依然希望我在這里所提供的信息,可以為你在該話題上的深入學習提供一點幫助。
招賢納士(Recruitment)招人,前端,隸屬政采云前端大團隊(ZooTeam),50 余個小伙伴正等你加入一起浪~ 如果你想改變一直被事折騰,希望開始能折騰事;如果你想改變一直被告誡需要多些想法,卻無從破局;如果你想改變你有能力去做成那個結果,卻不需要你;如果你想改變你想做成的事需要一個團隊去支撐,但沒你帶人的位置;如果你想改變既定的節奏,將會是“5年工作時間3年工作經驗”;如果你想改變本來悟性不錯,但總是有那一層窗戶紙的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望參與到隨著業務騰飛的過程,親手參與一個有著深入的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長歷程,我覺得我們該聊聊。任何時間,等著你寫點什么,發給 ZooTeam@cai-inc.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/110040.html
摘要:特性本文將簡單列舉的核心特性。獲取自有屬性名列表。以給丁對象為創建新的對象并返回。將對象的每個自有自有屬性做如下操作屬性的特性置為屬性的特性置為同時,該對象將不可擴展。檢查對象是否是位于給定對象的原型鏈上。 原文: http://pij.robinqu.me/JavaScript_Core/ECMAScript/es5.html 源代碼: https://github....
摘要:很簡單,不是數組,但是有屬性,且屬性值為非負類型即可。至于屬性的值,給出了一個上限值,其實是感謝同學指出,因為這是中能精確表示的最大數字。如何將函數的實際參數轉換成數組 這篇文章拖了有兩周,今天來跟大家聊聊 JavaScript 中一類特殊的對象 -> Array-Like Objects。 (本文節選自 underscore 源碼解讀系列文章,完整版請關注 https://githu...
摘要:數據庫查詢對象有一個屬性,用來訪問在數據庫中跟這個類有關的對象。使用方法在數據不存在的時候會返回默認查詢默認情況下,的屬性返回一個一個對象,它并沒有進行任何篩選和過濾,它返回的是所有的數據對象。它返回結果是函數的返回值。 數據庫查詢 Document 對象有一個 objects 屬性,用來訪問在數據庫中跟這個類有關的對象。這個 objects 屬性其實是一個QuerySetManage...
摘要:數據庫查詢對象有一個屬性,用來訪問在數據庫中跟這個類有關的對象。使用方法在數據不存在的時候會返回默認查詢默認情況下,的屬性返回一個一個對象,它并沒有進行任何篩選和過濾,它返回的是所有的數據對象。它返回結果是函數的返回值。 數據庫查詢 Document 對象有一個 objects 屬性,用來訪問在數據庫中跟這個類有關的對象。這個 objects 屬性其實是一個QuerySetManage...
摘要:創建對象對象直接量構造函數原型繼承類繼承對象擁有自有屬性和繼承屬性。遍歷順序是以廣度優先遍歷所以使用便可以判斷是否是對象自有的屬性。可執行對象通過如下方法可以創建一個可執行對象既可以當作對象來使用有原型鏈,也可以當作函數來直接調用 原文: http://pij.robinqu.me/Javascript_Core/Javascript_Basics/Objects.html ...
摘要:固有對象由標準規定,隨著運行時創建而自動創建的對象實例。普通對象由語法構造器或者關鍵字定義類創建的對象,它能夠被原型繼承。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄學習【原文有winter的語音】,如有侵權請聯系我,郵箱:ka...
閱讀 2026·2021-11-19 11:37
閱讀 714·2021-11-11 16:54
閱讀 1161·2021-11-02 14:44
閱讀 3048·2021-09-02 15:40
閱讀 2368·2019-08-30 15:44
閱讀 951·2019-08-29 11:17
閱讀 1059·2019-08-26 14:06
閱讀 1552·2019-08-26 13:47