前言
在Es6之前,由於javascript沒有對類的支援,也就是說它並不具備如傳統後臺語言(比如java)擁有類的功能,所謂類就是用來描述事物中的屬性和行為的,類的特徵是由成員組成的,而屬性對應的就是類中的成員變數,而方法對應的就是類中的成員方法,這是傳統oop語言的描述,然而在javascript中,雖沒有類的概念,但是它往往是通過建構函式和原型物件來給物件模擬與類相似的功能,但是這些相似的功能並不一定表現的與類完全一致,其實建立建構函式的過程,就是建立模板的過程,類一定程度上與此相似,建立多個共享的特定的屬性和方法,用於生成物件的餅乾工具,主要目的是提高程式碼的可複用性,也提高了程式碼的效能,有時候,在我們無意間就已經在使用了這些特性,什麼建構函式,原型,個人覺得,初次理解起來很是抽象,也稀裡糊塗的覺得實際開發中到底有什麼卵用,也許後者在不涉及複雜的功能需求時,平時用得不多,顯然Es6中已新增了類class的功能,越來越嚴格,越來越像後端語言,Es6,Es7,Es8新增的諸多方法也越來越強大,標準也越來越完善,但是我覺得理解建構函式與原型物件是必須的,是js物件導向程式設計的基礎,今天就我的學習和使用跟大家分享一下學習心得
正文從這裡開始~
什麼是函式
先看下面一簡易程式碼
var funA = function(){
console.log("我是匿名函式儲存在變數funA中");
}
var funB = [function(){
console.log("我是匿名函式儲存在陣列funB中");
}]
var funC = {
method:function(){
console.log("我是匿名函式儲存在物件funC中");
}
}
// 函式的呼叫
funA(); // 普通函式的呼叫
funB[0](); // 函式存入陣列中的呼叫
funC.method(); // 物件呼叫方法的使用
// 函式可以作為引數傳遞,也可以作為返回值返回
var funD = function(funParm){
return funParm;
}
var runFunParmPassedToFunD = funD(function(){
console.log("歡迎關注微信itclanCoder公眾號");
})
runFunParmPassedToFunD();
// 函式是物件,也就是說函式也擁有屬性
var FunE = function(){}
FunE.property = "隨筆川跡";
console.log(FunE.property);
// 證明函式式物件
console.log("funA的資料型別是",typeof funA);
console.log("funA具體所屬",Object.prototype.toString.call(funA));
console.log("funA是由Object的一個例項物件?",funA instanceof Object);
console.log("funA函式下面的構造器是",funA.constructor);
console.log("funA函式是由Object構造出來的?",funA.constructor == Object); // false
console.log("funA下面的原型",funA.prototype); // funA下面的原型
console.log("Object下的原型",Object.prototype); // Object物件下原型
console.log("funA原型下構造器",funA.prototype.constructor);//function fun(){}
console.log("物件原型下的構造器",Object.prototype.constructor);複製程式碼
控制檯輸出結果如下:
結論
:
- 函式也是功能程式碼塊,一個封閉區間短小的指令碼,如果多次使用同一段程式碼,就可以把它封裝成一個函式,允許在程式碼裡隨調隨用,利用函式封裝來避免重複鍵入大量相同的內容,不過函式的真正威力在於傳參的能力,可以把不同的資料傳遞給它們,使用這些資料去完成預定的操作
- 函式是一等公民,是物件,是值,可以儲存在一個變數,陣列或者物件中
- 函式可以傳遞給函式,並由函式返回,函式擁有屬性
- 函式總有返回值(換句話說就是有return語句,當然構造器函式除外,因為它預設會返回構造器函式呼叫,當建構函式的呼叫執行時,會顯示的返回返回)
什麼是建構函式
定義:建構函式就是你用new
關鍵字建立物件時呼叫的函式
作用(優點):建立多個共享特定屬性和行為的物件,主要是用於生成物件的餅乾模具
缺點:當例項化多個物件時,會重複的建立物件,造成記憶體空間的浪費,增大CPU的開銷,並沒有消除程式碼的冗餘,(如後面程式碼所示,原型正好解決了此類問題)
// 宣告一建構函式,首字母大寫
function Animal(name,age){
// this == new Animal();new會自動的建立this物件,且型別就是該構造安徽省農戶的型別,建構函式不需要返回值,因為new會顯示的返回,return的值就等於函式名+()的呼叫
this.name = name; // 自定義屬性
this.age = age; // 同上
this.fun = function(){ // 自定義方法
return this.name+" "+this.age+"歲了";
}
}
// 例項化物件
var animal1 = new Animal("cat",2);
var animal2 = new Animal("dog",3); console.log(animal1.name,animal1.age,animal2.name,animal2.age); // cat 2 dog 3
console.log(animal1.fun(),animal2.fun()); // cat 2歲了 dog 3歲了
console.log(animal1.hasOwnProperty("name"));
console.log(animal1.hasOwnProperty("age"));
console.log(animal1 instanceof Animal); // true,證明animal1是Animal1是Animal建構函式建立出來的
console.log(animal2 instanceof Animal);
console.log(animal1.constructor === Animal); // true
console.log(animal2.constructor === Animal); // true
console.log(animal1.fun == animal2.fun); // false複製程式碼
示例程式碼截圖如下
問題:同一個建構函式建立出來不同的例項化物件,公用的方法不等同,也就是說,當你new一個構造器物件,上面的建構函式就執行一遍,每次都會新建一個function,會新開闢一個記憶體空間,每次都是指向一個新的堆的物件 ,這樣佔用記憶體消耗非常的大,怎麼解決這個問題解決辦法1
:將建構函式裡面自定義的方法拿出來,獨立放在建構函式外
如下示例程式碼所示
// 宣告一建構函式,首字母大寫
function Animal(name,age){
this.name = name; // 自定義屬性
this.age = age; // 同上
this.fun = fun;
}
// 把建構函式裡面自定義的方法拿出來
function fun(){
return this.name+" "+this.age+"歲了";
}
// 例項化物件
var animal1 = new Animal("cat",2);
var animal2 = new Animal("dog",3);
console.log(animal1.fun === animal2.fun); // true複製程式碼
控制檯截圖如下所示解決辦法2
利用原型正好解決例項化多個物件時,避免建構函式內的方法重複建立(如後面的示例程式碼所示)
普通函式與建構函式的區別
- 有new與無new的差別
- 寫法上,建構函式首字母大寫(目的只是用於區分普通函式與建構函式,提醒你在建立例項化物件前加new操作符)
- 當函式沒有被new呼叫時,建構函式中的this就能與全域性this物件(即window)
示例程式碼如下所示:
// 宣告函式
function Animal(name,age){
this.name = name;
this.age = age;
this.fun = function(){
return this.name+" "+this.age+"歲了";
}
//console.log(this); window
}
// 無new的情況
var animal1 = Animal("cat",2);
var animal2 = Animal("dog",3);
console.log(animal1 instanceof Animal); // false
console.log(animal2 instanceof Animal); // false
console.log(Object.prototype.toString.call(animal1));//[object Undefined]
console.log(Object.prototype.toString.call(animal2));
console.log(name,age); // dog 3
console.log(animal1.name,animal1.age); //報錯複製程式碼
控制檯輸出結果
從上面的程式碼中可以看出,當一個函式無new關鍵字的呼叫時,建構函式中的this物件指向的是全域性物件window,所以建構函式式依靠new提供返回值,上面的型別檢測,值為undefined,正是如此,沒有使用new,則為普通函式,只不過是一個沒有返回值的語句函式,對this賦值屬性和方法,相當於在全域性下新增屬性和方法,如果加了use strict,在嚴格模式下,還會報,嚴格模式下,並沒有全域性物件設定this,返回的是undefined
針對以上問題,如果想普通函式也具有建構函式的功能,怎麼做?如下程式碼所示
// 宣告建構函式
function Animal(name,age){
// 加一this條件判斷,用instanceof來檢查自己是否被new呼叫
if(this instanceof Animal){
this.name = name;
this.age = age;
this.fun = function(){
return this.name+" "+this.age+"歲了"; }
}else{
// 以new遞迴呼叫自己來為物件建立正確的例項,這樣做的目的是在不同的情況下表現出一致的行為,常常是為了保護那些忘記了使用new的情況
return new Animal(name,age);
}
}
// 無new的情況
var animal1 = new Animal("cat",2);
var animal2 = Animal("dog",3);
console.log(animal1 instanceof Animal); // true
console.log(animal2 instanceof Animal); // true
console.log(Object.prototype.toString.call(animal1));//[object object]
console.log(Object.prototype.toString.call(animal2));//[object object]
console.log(animal1.name,animal1.age);
console.log(animal2.name,animal2.age);複製程式碼
控制檯輸出結果如下
為何內建建構函式無new也能工作
示例程式碼如下所示
var arr = Array; // 當沒有引數時,建構函式後面的圓括號可以省略
var obj = Object({
name:"隨筆川跡",
sex:"boy",
fun:function(){
return this.name+" "+this.sex+" "+Object.prototype.toString.call(this);
}});
console.log(obj.fun());複製程式碼
截圖如下所示
原因
:因為那些內建系統建構函式(Array,Object,RegExp,Date,Error,String等)都被設計為作用域安全的建構函式,也就是說在整個全域性範圍內都是可見的,一個作用域安全的建構函式無new也可以工作,並返回同樣型別的物件
原型物件
protype
:
- 作用1:去改寫物件下面公用的方法或者屬性,讓公用方法或者屬性在記憶體中存在一份(也就是更改構造器函式底下屬性和方法,解決了不會重複建立構造的過程,目的是提高效能),可以看作是物件的基類
- 作用二:在原有的物件基礎上上,通過prototype進行額外的,封裝,擴充(如後面示例程式碼)
- 原型是基於建構函式的(也就是說原型是掛載在建構函式下的,先有建構函式,才會有原型)
如下原型示例程式碼
控制檯輸出結果如下function ProtoFun(width,height){ this.width = width; this.height = height; this.method = function(){ return "我是建構函式下自定義的方法" } } // 建構函式.原型下新增屬性 ProtoFun.prototype.color = "red"; // 建構函式.原型下新增方法 ProtoFun.prototype.fun = function(){ return this.width+" "+this.height+" "+this.color; } // 上面兩個通常可以合併成下面一個 ProtoFun.prototype.init = { color:"red", fun:function(){ return this.width+" "+this.height+" "+this.color; } } var elemObj1 = new ProtoFun(100,100); var elemObj2 = new ProtoFun(200,200); console.log(elemObj1.width,elemObj1.height); // 100 100 console.log(elemObj2.width,elemObj2.height); // 200 200 console.log(elemObj1.color,elemObj1.fun(),elemObj1.init.color,elemObj1.init.fun()); console.log(elemObj2.color,elemObj2.fun(),elemObj2.init.color,elemObj2.init.fun()); console.log(elemObj1.method===elemObj2.method); // false console.log(elemObj1.fun === elemObj2.fun); // true複製程式碼
如下示例:
// 未用原型寫法,普通寫法求和
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = [2,4,6,8,10,12,14,16]
arr1.sum = function(){
var result = 0;
for(var i = 0;i<arr1.length;i++){ // 這裡也可以換成this.length
result += this[i];
}
return result; // 返回結果
}
arr2.sum = function(){
var result = 0;
for(var i = 0;i<arr2.length;i++){
result += this[i];
}
return result; // 返回結果
}
console.log("陣列arr1和為",arr1.sum()); // 55
console.log("陣列arr2和為",arr2.sum()); // 72複製程式碼
控制檯截圖如下:
原型寫法
// 原型寫法
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = [2,4,6,8,10,12,14,16]
Array.prototype.sum = function(){
var result = 0;
for(var i = 0;i<this.length;i++){
result += this[i];
}
return result;
}
console.log("陣列arr1的和為",arr1.sum()); // 陣列arr1的和為55
console.log("陣列arr2的和為",arr2.sum()); // 陣列arr2的和為72
console.log(arr1.sum === arr1.sum); // true複製程式碼
//普通函式封裝寫法,也就是閉包寫法
var arr1 = [1,2,3,4,5,6,7,8,9,10];
var arr2 = [2,4,6,8,10,12,14,16]
function AddResult(arr){
arr.sum = function(){
var result = 0;
for(var i = 0;i<this.length;i++){ // 這裡也可以換成this.length
result += this[i];
}
return result; // 返回結果
}
return arr.sum();
}
console.log("陣列arr1和為",AddResult(arr1)); // 陣列arr1和為55
console.log("陣列arr2和為",AddResult(arr2)); // 陣列arr2和為72複製程式碼
區分建構函式自定義屬性與原型屬性
如下示例程式碼所示:
function Person(name,publicNum){
this.name = name;
this.publicNum = publicNum;
this.userDefined = function(){
return "我是建構函式自定義方法unerDefined"
}
}
Person.prototype.age = 25;
Person.prototype.init = {
city:"beijing",
job:"coder",
method:function(){
return "我是原型下的方法輸出"
}
}
// 定義鑑別原型屬性方法
function hasPrototypeProperty(object,variable){
return !object.hasOwnProperty(variable) && (variable in object);
}
var person = new Person("隨筆川跡","itclancoder");
console.log(person.name,person.publicNum,person.userDefined(),person.init.city,person.init.job,person.init.method());
console.log(hasPrototypeProperty(person,"name"));
console.log(person.hasOwnProperty("name"));
console.log(person.hasOwnProperty("age")); // hasOwnProperty只能檢測自定義屬性,false
console.log(hasPrototypeProperty(person,"age"));
console.log("age" in person); // true,in操作符既能檢測自定義屬性也能檢測出原型下的屬性複製程式碼
控制檯輸出結果如下:
使用物件字面量形式改寫原型物件會改變建構函式的屬性,指向問題
function Person(name,job){
this.name = name;
this.job = job;
}
Person.prototype.init = {
name:"小川",
job:"碼男",
outName:function(){
return this.name;
},
outJob:function(){
return this.job;
}
}
var person = new Person("隨筆川跡","coder");
console.log(person.name,person.job);
console.log(person.init.outName(),person.init.outJob());
console.log(person.constructor === Person); // true
console.log(person instanceof Person); // true複製程式碼
控制檯輸出結果如下:
若將上面的程式碼更改如下:
function Person(name,job){
this.name = name;
this.job = job;
}
// 使用物件字面量形式改寫原型物件
Person.prototype ={
name:"小川",
job:"碼男",
outName:function(){
return this.name;
},
outJob:function(){
return this.job;
}
}
var person = new Person("隨筆川跡","coder");
console.log(person.name,person.job);
console.log(person.outName(),person.outJob());
console.log(person.constructor === Person); // false
console.log(person.constructor === Object); // true
console.log(person instanceof Person); // true複製程式碼
控制檯輸出結果如下正確寫法
:當一個函式被建立時,它的prototype屬性也被建立,且該原型物件的constructor屬性指向該函式,當使用物件字面量形式改寫原型物件Person.prototype時,則該constructor指向指向的是Object,為了避免這一點,需要手動的改寫原型物件手動設定constructor屬性,更改如下:
function Person(name,job){
this.name = name;
this.job = job;
}
// 使用物件字面量形式改寫原型獨享
Person.prototype ={
constructor:Person, // 手動指定這裡的指向該建構函式
outName:function(){
return this.name;
},
outJob:function(){
return this.job;
}
}
var person = new Person("隨筆川跡","coder");
console.log(person.name,person.job);
console.log(person.outName(),person.outJob());
console.log(person.constructor === Person); // true
console.log(person.constructor === Object); // false
console.log(person instanceof Person); // true複製程式碼
在原有的物件基礎上上,通過prototype進行額外的,封裝,擴充
例項程式碼如下:
// 通過原型prototype對現有的內容進行額外的擴充,給陣列Array新增方法
Array.prototype.sum = function(){
return this.reduce(function(m,n){
return m+n;
})
}
var arrNums = [1,2,3,4,5,6,7,8,9,10];
var result = arrNums.sum();
console.log("arrNums的和為",result); // arrNums的和為 55
// 給String新增額外的方法
String.prototype.capitalize = function(){
return this.charAt(0).toUpperCase()+this.substring(1);
}
var message = "suibichuanji hello";
console.log(message.capitalize()); // Suibichuanji hello複製程式碼
控制檯輸出如下:
以上例子中,我們是可以通過對系統提供的內建物件進行額外擴充的,也就是說系統物件(Date,String,Object,Array,RegExp等)是建構函式,當現有提供的功能沒法滿足時,就可以根據prototype進行擴充,因此都有原型物件給你去改變,在該新增的方法前面新增建構函式.prototype就可以了,上面的例子中是給Array.prototype新增了一個sum()求和的方法,該方法對陣列所有元素進行求和並返回,arrNums陣列通過原型物件自動就有了這個sum()方法,在sum()方法內部,this指向陣列物件例項arrNums,所以該方法也可以使用陣列的其他方法,什麼reduce(),substring(),等都可以
原型中的屬性優先順序
示例程式碼如下所示
var arr = [];
arr.name = "隨筆川跡";
Array.prototype.name = "川流不息";
console.log(arr.name); // 隨筆川跡複製程式碼
控制檯輸出如下
從上結果中可以得出:當建構函式自定義的屬性名與該建構函式下原型屬性名相同時,建構函式的自定義屬性優先於原型屬性
(可以把建構函式理解為內聯樣式),而原型屬性或者原型方法可以看做是class)
小結:建構函式就是用new關鍵字呼叫的普通函式,可以隨時定義自己的建構函式來建立多個具有同樣的屬性的物件,可以用instanceof操作符(建議用這個)者直接訪問constructor屬性來鑑別物件是被哪個建構函式建立的,每一個函式都具有prototype屬性,它定義了建構函式所有物件共享屬性
- 自定義的屬性和方法放在建構函式裡面
- 凡是共享的屬性和方法掛載在該建構函式原型下面
- javascript的查詢變數的機制,是沿著作用域鏈逐級向上查詢的,在原型裡,是原型鏈,建構函式與原型之間的連線就是原型鏈,當訪問物件的某個屬性時,js首先在自定義的屬性的作用域內查詢該變數是否存在,如果不存在,則會沿著原型鏈向原型下的查詢該屬性,直至頂層Object的原型物件,若有則返回,若無,則返回undefined
物件導向小例項
效果圖:css層疊樣式程式碼
*{
padding:0;
margin:0;
}
#wrap{
width:300px;
height:260px;
border:1px solid #ccc;
margin:0 auto;
}
#wrap:after{
content:"";
height:0;
display:block;
clear:both;
zoom:1;
}
#wrap div{
height:100%;
display:none;
text-indent:10px;
background:#2263A3;
color:#fff;
}
#wrap div:nth-of-type(1){
display:block;
}
#wrap input.active{
background:#2263A3;
color:#fff;
}
#wrap input{
width:100px;
height:30px;
background:#abcdef;
text-align:center;
line-height:30px;
outline:none;
border:none;
float:left;
cursor:pointer;
margin-bottom:30px;
}複製程式碼
html結構
<div id="wrap">
<input type="button" class="active" value="公告" name="">
<input type="button" value="規則" name="">
<input type="button" value="論壇" name="">
<div>歡迎關注微信itclancoder公眾號</div>
<div>點選右上方藍字即可關注</div>
<div>什麼都沒有留下</div>
</div>複製程式碼
js程式碼
// 普通寫法
// 獲取元素
var oWrap = document.querySelector("#wrap");
var aBtns = oWrap.getElementsByTagName("input");
var aDivs = oWrap.getElementsByTagName("div");
// 迴圈
for(var i = 0;i<aBtns.length;i++){
aBtns[i].index = i; //新增索引
aBtns[i].onclick = function(){ // 新增事件
for(var j = 0;j<aBtns.length;j++){
aBtns[j].className = ""; // 先去除掉所有的className
aDivs[j].style.display = "none"; // 先隱藏
}
// 新增class
this.className = "active";
aDivs[this.index].style.display = "block"; // 內容顯示
}
}複製程式碼
jquery寫法
$(function(){
$(" #wrap input").click(function(){
var $index = $(this).index(); // 獲取索引
$(this).addClass("active").siblings().removeClass("active");
$("#wrap div").eq($index).show().siblings("div").hide();
})
})複製程式碼
物件導向寫法
function 建構函式(){
this.屬性 // 物件.屬性
}
建構函式.原型.方法 = function(){}
var 物件1 = new 建構函式();
物件1.方法();複製程式碼
物件導向選項卡程式碼示例如下所示
window.onload = function(){
var t = new TabSelect(); // 例項化物件
t.init(); // 例項化物件呼叫方法
}
// 宣告建構函式
function TabSelect(){
// this == TabSelect,新增自定義屬性,獲取物件
this.oWrap = document.querySelector("#wrap");
this.aBtns = this.oWrap.getElementsByTagName("input");
this.aDivs = this.oWrap.getElementsByTagName("div");
}
// 建構函式的原型下新增方法(初始化)
TabSelect.prototype.init = function(){
var that = this; // 注意這裡的this指向,是TabSelect,用一個區域性變數將this給儲存起來,其實這種方式是根據詞法作用域,閉包的方式來解決的
for(var i = 0;i<this.aBtns.length;i++){
this.aBtns[i].index = i; //新增索引
this.aBtns[i].onclick = function(){
that.change(this);
//console.log(this);匿名函式裡面的this指向的是input按鈕元素
};
}
}
// 構造器函式原型物件下新增方法
TabSelect.prototype.change = function(obj){
//console.log(obj); // input點選按鈕元素
for(var j = 0;j<this.aBtns.length;j++){
this.aBtns[j].className = ""; // 先去除掉所有的className
this.aDivs[j].style.display = "none"; // 先隱藏
}
// 新增class
obj.className = "active";
this.aDivs[obj.index].style.display = "block"; // 內容顯示
}複製程式碼
小結
:
本例從普通寫法,jquery寫法,在到最後物件導向選項卡寫法,完成一簡易的選項卡,其中jquery寫法最為簡單,容易懂,但是這裡我只是為了嘗試用物件導向的思想去寫應用,實際開發中,無論哪種方式,只要能實現出來就行,從普通的寫法,也就是原生js寫法,到物件導向寫法,可以看出首先通過變形,把區域性的功能給拎出來,封裝成一個函式,這個過程中儘量不要出現函式巢狀函式,因為this是指向是個令人頭疼的問題,可以有全域性變數,window.onload裡面儘量是例項化物件,與物件的呼叫的方式,把不是賦值的語句單獨放到一個函式當中(比如上文中的獲取元素,給TabSelect新增自定義屬性),最後就是改變this指向問題,事件或者定時器,讓物件導向中的this指向該物件
總結
:
本篇主要是本人對構造器函式與原型物件的一點點理解,new操作符呼叫的函式為構造函,功能上與內建的函式並沒有多大的區別,建構函式首字母大寫用來區分普通函式還是建構函式,建構函式中的this指向該例項化的建構函式,主要是建立多個共享特定屬性和行為的物件,用於建立模板,作為餅乾工具,而原型物件主要是改寫建構函式(物件)下面的方法和屬性,讓公用方法或者屬性在記憶體中存在一份,解決了當建立多個例項化物件時,重複的建立建構函式的過程,目的是減少記憶體開銷,提高效能,還有就是原型在原有的物件基礎上,通過prototype進行額外的,封裝,擴充,原型是掛載在建構函式下面的,以及最後用物件導向寫法實現了一個小例項,其實設計模式中的原型模式就是物件導向的寫法,殺雞焉用牛刀,適合自己的才是好方法,物件導向的寫法,對於簡單的例項,程式導向就可以了,對於複雜的例項,什麼元件,外掛,我覺得都是物件導向的應用,關於物件導向,我也只是略知皮毛,在不斷的學習當中...
以下是本篇提點概要
- 什麼是函式:function關鍵字宣告,一獨立封閉功能的程式碼塊,也是物件
- 什麼是建構函式:new關鍵字建立物件時呼叫的函式,用於建立模板,生成餅乾工具
- 普通函式與建構函式的區別,有new無new區別,this的指向,普通函式,this指向全域性window,而構造器函式this,指向該new 構造器函式呼叫
- 為何內建建構函式無new也能工作,因為那些內建系統建構函式,都被設計為作用域安全的建構函式,一個作用域安全的建構函式無new也可以工作,並返回同樣型別的物件
- 原型物件,prototype,函式一旦宣告,就有該屬性,作用1:去改寫物件下面公用的方法或者屬性,讓公用方法或者屬性在記憶體中存在一份,可以看作是物件的基類
作用2:在原有的物件基礎上上,通過prototype進行額外的,封裝,擴充 - 區分建構函式自定義屬性與原型屬性,用in操作符,hasOwnProperty組合使用進行判斷(見上示例程式碼)
- 使用物件字面量形式改寫原型物件會改變建構函式的屬性,指向問題,需手動的改寫原型物件手動設定constructor屬性
- 在原有的物件基礎上,通過prototype進行額外的,封裝,擴充
- 原型中的屬性優先順序,建構函式自定義的屬性優先順序優先於原型屬性,查詢變數的方式是沿著原型鏈逐級查詢的,直至頂層Object,有則返回,無則返回undeinfed
- 物件導向小例項,普通寫法,JQuery寫法與物件導向選項卡寫法