JavaScript之ECMAScript筆記(基礎)
ECMAScript,DOM,BOM
javascript分為三部分: ECMAScript,DOM,BOM
Brendan Eich完全建立了ECMAScript;
DOM,BOM也可以算他建立的,不過他只是給出了規則
- DOM(document object model) 通過js操控HTML;
- BOM(browser object model) 通過js操控瀏覽器;
- (注意:CSS是不能被操作的)
Brendan Eich給出了DOM,BOM的標準,由各個廠商補充方法,所以DOM,BOM是各個廠商結合相同的ECMAScript,結合出的產物 因此,每個瀏覽器在DOM,BOM上可能有細微的差別
es5嚴格模式
JavaScript一直在發展, 通用的版本是:es3.0,es5.0,es6.0
且其中es3.0,es5.0特別通用
補充:一個版本的升級和革新,意味著:
- 會擯棄一些老語法(可能新版本的有些語法和老版本的語法產生了衝突,因此產生衝突的東西要適當的擯棄掉)
- 還產生一些新的語法(原來一些老的語法裡面沒有的)
- 注意:在es3.0,es5.0中可能有些語法重複了,但是在不同的版本中體現的方法和得出的結果不一樣。
es5嚴格模式的源由:
現在的瀏覽器是基於es3.0和es5.0的那些新增的方法使用的。然而,在es3.0和es5.0必然會有衝突的部分,這些衝突的部分,瀏覽器就是使用es3.0的
es5嚴格模式的啟用:
es5.0要啟動,要遵循一個語法規則(嚴格模式):在邏輯最前面寫"use strict";
這是規定
"use strict";//可以寫在全域性-邏輯最頂端
//也可以寫在區域性-邏輯最頂端(區域性函式中邏輯最前面,推薦)
//為什麼這個語法規則要寫成字串形式?
//不會對不相容嚴格模式的瀏覽器產生影響。
es5嚴格模式報錯: 在es3中可以使用的方法,但是es5中不能使用
- 不能使用arguments.callee和function.caller
- 變數賦值前必須宣告
- 區域性的this必須賦值(傳啥就是啥),預編譯過程this不再預設指向window(注意:在全域性中this依舊預設指向window)
- 拒絕重複屬性或重複引數
- 注意: eval(“”) 在es3.0不能使用,通用的規定
- 使用with(){}會報錯(with會把傳進來的物件引數,變成with程式碼段的作用域鏈的最頂端)
//with還可以用來簡化程式碼
with(document) {
write('a'); //document中有很多的方法
}
//但是with太強大了,可以改變作用域鏈,系統核心會消耗大量的效率,程式會變得很慢
//所以es5.0中,把with方法給禁止了
es5嚴格模式,縮減了程式碼的靈活度,但減少了你的犯錯誤的機率
注意:以下知識點都是es3.0包含es5.0的東西,這沒什麼好區分的
變數
- 變數名必須以 英文字母、_、$ 開頭
- 變數名可以包括英文字母、_、$、數字
- 不可以用系統的關鍵字、保留字作為變數名
- 區分大小寫
- 型別不需要定義,由值決定型別
資料型別概要:
javascript是動態語言 解釋一行,編譯一行,所以變數由值確定型別 並且,解釋性語言(弱資料語言)永遠不可能輸出地址
值型別有:原始值,引用值(他們的賦值有區別)
原始值:
- Number(=右邊為數字,數字是浮點型)
- String(=右邊的內容用單引號或雙引號包含起來)
- Boolean(=右邊的值有true或false)
- undefined(=右邊的值只有undefined,意為未定義)
- null(=右邊值為null,意為空,一般用作覆蓋某些變數的值)
引用值:
- function(函式)
- array(陣列)
- object(對像)
原始值和引用值的區別:
例一:
var a = 10;
var b = a;
a = 20;
(a的值改為20,但b的值仍舊是10)
原始值存在stack(棧)裡面
棧的一個規則:先進後出 棧記憶體和棧記憶體之間的賦值是copy,互不影響
例二:
var arr = [1];
var arr1 = arr;
arr.push(2);
(arr和arr1都改為1,2)
引用值存在heap(堆)裡面
堆heap記憶體是引用值(值放入堆heap記憶體中,值對應的地址放進棧stack中)
例三:
var arr = [1,2];
var arr1 = arr;
arr = [1,3];
(arr的值改變,但是arr1的值不變,因為 arr = [1,3]; 是arr重新在堆heap裡申請了新的空間,且值對應的存在棧stack裡的地址得到改變,而arr1的堆heap的值和棧stack的地址都沒變)
typeof
typeof()用來返回括號中的值的型別
六種資料型別:
- number(infinity,NaN屬於number型別)
- string
- boolean
- undefined
- object(注意:null,陣列,物件的資料型別是object)
- function
typeof呼叫方法:
- typeof(變數)
- typeof 變數
一般,如果訪問沒定義的變數,會報錯 但有一極特殊的情況, 當且僅當:未定義的變數放入typeof()中不會報錯 result: undefined(是字串型別的)
型別轉轉
考慮原始值的型別轉換即可,引用值的型別轉換沒意義
顯示型別轉換
Number() : 例一:
var num = Number('123');
//等價於 var num = 123;
例二: 一般明顯是非數的轉換為Number,為NaN
var demo = undefined;
var num = Number(demo);
console.log(typeof(num) + ":" + num);
result: Number : NaN
例三:
var demo = "123abc";
var num = Number(demo);
console.log(typeof(num) + ":" + num);
result: Number : NaN
例四:
var demo = "-123";
var num = Number(demo);
console.log(typeof(num) + ":" + num);
result: Number : -123
例五: Number(true)的結果是 1 Number(null)的結果是 0
parseInt() : 一:括號內的資料型別轉換為整型,將字串和數字轉換為整型,而其餘的為NaN 二:parseInt(),括號內可以寫兩個引數,右邊的是基底radix 三:parseInt(),括號內的資料,從第一位數字位看起,看到非數字位停止(若第一位不是數字,則返回NaN)
例一:
var demo = "123";
var num = parseInt(demo);
console.log(typeof(num) + ":" + num);
result: Number : 123
例二:
var demo = true;
var num = parseInt(demo);
console.log(typeof(num) + ":" + num);
result: Number : NaN
例三:
var demo = "123.9";
var num = parseInt(demo);
console.log(typeof(num) + ":" + num);
result: Number : 123
二:parseInt(),括號內可以寫兩個引數,右邊的是基底radix 例一:
var demo = "10";
var num = parseInt(demo,16);
console.log(typeof(num) + ":" + num);
result: Number : 16 基底是16,所以左邊引數認為為16進位制的,然後轉換為十進位制
例二: 注意:radix的範圍一般2-36,代表的是進位制,對應的不合理的數結果為NaN
var demo = "3";
var num = parseInt(demo,2);
console.log(typeof(num) + ":" + num);
result: Number : NaN(例二中radix為2,二進位制數不存在3)
parseFloat() : 與parseInt()有許多相似之處 區別:parseFloat是轉換為浮點型,而且只能有一個引數
String() : 無論是什麼,都轉換為字串
toString() :
- 寫法有一些不同
var demo = 123; var num = demo.toString();
- 也可以轉換為字串(注意,undefined和null不能toString,不然會報錯)
- toString()括號中也可以有引數,toString(radix),不過這個radix是目標進位制(和parseInt()中的第二個引數是基底不同),若radix為8,即將十進位制的數轉換為八進位制的數
Boolean() : 轉換為布林值
只有六種值對應的boolean值是false;
- undefined
- null
- NaN
- “” (空字串“”,和空格字串” “不一樣)
- false
- 0
隱式型別轉換
一元正負,加減乘除取餘;大於小於等於……(有數字); 都先隱式的呼叫Number()
與或非,先隱式的呼叫Boolean()
isNaN() : 自己寫一個isNaN()的功能:
function isNaN(num) {
var ret = Number(num);
ret += "";
if (ret == "NaN") {
return true;
}
else return false;
}
isNaN()會先隱式的呼叫Number(),再判斷是不是NaN,是則為true,否則為false
幾個特殊知識點(undefined,null,NaN):
- undefined和null 與數字0比較的結果都是false(無論是大於小於,還是等於)
- undefined == null;的結果是true
- NaN == NaN; 的結果是false(NaN不等於任何數,包括它自己)
避免發生隱式型別轉換 : 絕對等於 === 絕對不等與 !==
基本語法
- 語句後面要用分號結束“;”(英文字元的分號)
- 書寫格式要規範,運算子兩邊都應該有空格
編譯錯誤
一個html檔案可以引入多個js程式碼塊,不同程式碼塊的錯誤相互不影響,但是不同程式碼塊的變數可以相互使用,在執行之前會通篇檢查是否有低階錯誤,若沒有,則從上至下依句執行。
- 低階錯誤(語法解析錯誤)——該程式碼塊一行都不會執行
- 邏輯錯誤(標準錯誤,情有可原)——該程式碼塊中,執行到錯誤處終止執行
try{}catch(e){}
前提準備(Error.name的六種植對應的資訊): - EvalError:eval()的使用與定義不一致,eval()是不准我們用的,用了會報錯,(很少見) - RangeError:陣列越界,(少見) - ReferenceError:非法或不能識別的引用數值,(常見) - SyntaxError:發生語法解析錯誤,(常見) - TypeError:運算元型別錯誤 - URIError:URI處理函式使用不當 ,(多見於引用地址)
try {
}catch (e) {
}
作用:防止我們報錯的 假如你寫的程式碼中有一百行程式碼不確定會不會出錯,但是無論其出不出錯,都不希望這一百行程式碼中的出錯程式碼影響到後續程式碼的執行
有些時候錯誤不是你能把控的,比如:假如設定一個變數data,賦值為null,當後端給data傳遞資料進來,傳完後,我們那這個資料來用,但是你不知道什麼時候傳,傳的成不成功,這個時候網速通不通暢
程式設計,是前端,後端拼到一起,後端的問題影響前端,前端的問題影響後端,都是有可能的
把程式碼寫到 try 裡面,程式碼會正常執行,但是它有一個特點:
try{
console.log("a");
console.log(b);
console.log("c");
}catch(e){
console.log(e.name + ":" + e.message);
}
console.log("d");
result: a ReferenceError : b is not defined d
執行到 console.log(b); 這一句,按正常邏輯,會報錯且後面的程式碼不會執行(看都不看),但是 try 裡面的程式碼邏輯是:報錯的程式碼依然報錯,但不丟擲錯誤( console.log(b); 這一行程式碼報錯後,try裡面的程式碼停止執行,但是 try 外面的程式碼依然繼續執行)
catch 的作用是:當 try 裡面的程式碼出錯,就會執行 catch 裡面的程式碼(實質上是捕捉 try 裡面程式碼的錯誤資訊) 錯誤有一堆錯誤資訊,系統會把這些錯誤資訊(error.name,error.message)封裝到一個物件裡面(error物件),然後傳到形參e裡面
運算子
+,/,比較運算子,&&,|| 的知識點較為值得書寫
+
任何資料型別加字串都等於字串
var a = 1 + 1 + "a" + 1 + 1;
result: 2a11 (字串型別)
/
例一:
var num = 1 / 0;
result: infinity(屬於number型別)
例二:
var num = -1 / 0;
result: -infinity(屬於number型別)
例三:
var num = 0 / 0;
result: NaN(Not a Number,也屬於number型別)
比較運算子
例一:
var a = "a" > "b";
result: false(字串比較的是ASCII碼)
例二:
var a = 1 == 2;
result: false
例三;
var a = NaN == NaN;
result: false(這個是特例,因為NaN不等於任何數,包括它自己)
例四:
注意:引用值和引用值之間的比較,比較的是地址
var a = {} == {};
result: false
例五:
var obj = {};
var obj1 = obj;
var a = obj1 == obj;
result: true
邏輯運算子
只有六種值對應的boolean值是false;
- undefined
- null
- NaN
- “” (空字串“”,和空格字串” “不一樣)
- false
- 0
&&
例一:
var a = 1 && 2 + 2;
result: 4
從左到右依次判斷真假,是真就往後走,遇到假就返回該值
注意: &&有中斷作用 例二:
2 < 1 && document.write("zgh666");
前面的為假,後面的語句不執行 例三:
2 > 1 && document.write("zgh666");
前面的為真,後面的語句執行,結果是zgh666
||
||和&&的規則大致相同,但一些地方相反 &&是尋找假,不找到假不停 而||是尋找真,不找到真不停
注意:||可以用於寫相容 例一:
div.onclick = function(e) {
var event = e || window.event;
}
三目運算子
條件判斷?真:否 並且會返回該值
例一:
var name = 1 > 0 ? 2 +2 : 1+1;
result: 4
例二:
var name = 1 < 0 ? 2 +2 : 1+1;
result: 2
逗號操作符
使用逗號操作符時,先要加上(),防止出現優先順序混亂
var a = (2, 3);
result: 2 逗號是一個計算符,有括號的加持,先計算逗號操作符,再把值賦給a
先:看前面的表示式,如果前面的表示式需要計算就先計算 然後再:如果後面表示式需要計算的話就計算後面的的表示式 都計算完後,把後面的表示式的結果返回回去
函式
高內聚,弱耦合 函式是變數,函式是引用值
函式的宣告
關鍵字:function
固定格式
function test(){}
test是函式名,(引數),{函式體,即程式碼段}
起名: 函式名與變數名的起名方式差不多 開發規範:小駝峰原則(複合單詞時),例如: theFirstName
函式表示式
函式表示式可以忽略它的名字 有兩種函式表示式:
var test = function abc(){ /*命名函式表示式,adc僅僅只是函式名而已,不能通過
adc來呼叫這個函式,即寫的這個名adc沒什麼用*/
......
}
var demo= function (){ /*匿名函式表示式(一般函式表示式
指的都是匿名函式表示式)*/
......
}
函式的引數
作用:聚合程式碼塊;抽象規則(這一點使函式變得神奇)
在JavaScript中:形參可以任意多個,天生不定參,可以形參比實參多,反之亦可
arguments類陣列
無論形參有沒有把實參表示出來,實參都有地方去放 在每個函式裡,都有一個隱式的東西,arguments類陣列
function sum(a) {
/*arguments -- [11,55,66,88,9981] 實參列表,依次從左到右放入實參中,
函式中實參有多少,實參列表就有多少,不因為形參的某些變化而改變*/
......
}
sum(11,55,66,88,9981);
- 計算形參的個數用: arguments.length
- 計算實參的個數用: sum.length
對映規則
function sum(a,b) {
a = 2;
arguments[0] = 3;
console.log(a); //3
}
sum(1,2);
形參和實參不是同一個變數,但是有繫結規則,一個變了,另一個要跟著變(和c語言不同)
特殊: 當實參比形參少,給多的形參(b)賦值,但是arguments[1]的輸出結果是undefined,此時的形參b與實參不對映
function sum(a,b) {
b = 2;
console.log(arguments[1]); //undefined
}
sum(1);
預編譯(較難)
準備知識:
- imply global 暗示全域性變數:即任何變數,如果未經宣告就賦值,此變數就為全域性物件所有
- 全域性物件是window
a = 10; <=> window.a = 10;
- window就是全域性的域
- 預編譯發生在函式執行前的一刻
函式預編譯四部曲:
- 建立AO物件
- 找形參和變數宣告,將變數和形參名作為AO屬性名,值為undefined
- 將實參與形參統一
- 在函式體裡面找到函式宣告,值賦予函式體
全域性預編譯
- 與函式預編譯的規則基本一致
- 不過生成的物件是GO(物件GO就是window)
- 所以全域性變數都是window上的屬性
注意:for迴圈語句,if條件語句…不影響預編譯,裡面的內容該預編譯就預編譯
作用域(較難)
作用域精解:
- [[scope]]:每個js函式都是一個物件,物件中有些屬性我們可以訪問,例如:name;但有些不可以,這些屬性僅供js引擎存取,[[scope]]就是其中一個。[[scope]]指的就是我們所說的作用域,其中儲存了執行期上下文的集合。
- 作用域鏈:[[scope]]所儲存的執行期上下文的物件的集合,這個集合呈鏈式連結,我們把這種鏈式連結叫做作用域鏈。
- 執行期上下文:例如:AO,GO,當函式執行的時候,會建立一個稱為執行期上下文的內部物件。一個執行期上下文定義了一個函式執行時的環境,函式每次執行對應的執行期上下文都是獨一無二的,所以多次呼叫一個函式會導致建立多個執行期上下文,當函式(每次)執行完畢,他所產生的執行上下文被銷燬。
- 查詢變數:從作用域鏈的頂端依次向下查詢(在哪個函式查詢變數,就在那個函式的作用域鏈的頂端依次向下查詢)
以下面程式碼例一來說明作用域 例一:
function a() {
}
var glob = 100;
a();
//s.[[scope]] --> 0:GO {}
函式剛剛被定義,它的[[scope]]就存了東西,此時只儲存了一位,第0位存了GO,此時還沒形成鏈 函式a被定義後,開始執行a();a函式產生執行期上下文AO,AO放到作用域鏈的頂端,佔據第0位,而GO被放進第1位
特殊:函式裡面定義函式的情況(與函式和全域性的關係差不多),以例二來說明 例二:
function a(){
function b(){
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
a函式執行時:b函式定義,直接把函式a(上一級)的執行期上下文拿來 b函式執行時:生成自己的AO,放入作用域鏈[[scope]]的頂端 注意:
- 函式b訪問的第1位的AO就是a函式產生的AO
- 函式b執行完後,它的第0位的AO被幹掉,即第0位房間被清空,等待下一次函式b的執行,再建立一個自己的AO
- 如果函式a執行完,它的第0位的AO被幹掉的同時,這個AO裡面的函式b也永遠沒了,函式b的執行上下文也永遠沒了;然後函式a回到被定義的狀態,等待再次被執行
- a函式再次被執行,又產生全新的執行上下文,然後放入作用域鏈[[scope]]的頂端,同時,又產生函式b的定義(全新的函式b),然後……
所以:
- 在哪個函式查詢變數,就在那個函式的作用域鏈的頂端依次向下查詢
- 所以外面的不能訪問裡面的變數,而裡面的可以訪問外面的變數
- 注意:只有函式被執行,裡面的語句才會一條條被讀,a函式執行後才有b函式的定義
閉包(較難,內部的函式被儲存在外部時觸發)
凡是內部的函式被儲存在外部必定生成閉包 例一(粗略解釋閉包):
function a() {
function b() {
var bbb = 234;
document.write(aaa);
}
var aaa = 123;
return b;
}
var glob = 100;
var demo = a();
demo();
a函式執行時,b定義(函式b的執行上下文繼承了函式a的執行上下文) 所以,函式a執行完後,它的AO沒有被銷燬,只是與其的聯絡斷掉了,還保留著與函式b的聯絡(也因為函式b定義被儲存到外面,沒有被幹掉)
例二(閉包功能:累加器):
function a() {
var num = 100;
function b() {
num++;
document.write(num);
}
return b;
}
var demo = a();
demo(); //101
demo(); //102
例三(閉包功能:快取):
function eater() {
var food = "";
var obj = {
eat: function(){
console.log("i am eating" + food);
food = "";
},
push: function(myFood){
food = myFood;
}
}
return obj;
}
var eater1 = eater();
eater1.push("apple");
eater.eat(); //i am eating apple
function test() {
var food = "apple";
var obj = {
eatFood: function(){
if (food != ""){
console.log("i am eating" + food);
food = "";
}
else{
console.log("there is nothing ! empty!");
}
},
pushFood: function(myFood){
food = myFood;
}
}
return obj;
}
var person = test();
person.eatFood(); //i am eating apple
person.eatFood(); //there is nothing ! empty!
person.pushFood("banana");
person.eatFood(); //i am eating banana
例四(可以實現封裝,屬性私有化):
function Deng (name,wife) {
var prepareWife = "xiao er";
this.name = name;
this.wife = wife;
this.divorce = function () {
this.wife = prepareWife;
}
this.changePrepareWife = function () {
prepareWife = target;
}
this.sayPrepareWife = function () {
console.log(prepareWife);
}
}
var deng = new Deng('deng', 'xiao yi');
deng.divorce();
deng.wife; //xiao er
deng.prepareWife; //undefined
經過閉包後,prepareWife這個變數對於這個物件變得很微妙 deng.prepareWife訪問不到這個變數,但是它可以操作這個變數;表面上看prepareWife這個變數不是它自己的,但閉包永遠跟隨它,閉包相當於一個隱藏的區域;prepareWife這個變數變得像它的私有化變數似的(只有它自己能看到,別人看都看不到)
例五(模組化開發,防止汙染全域性變數,用來解決變數名衝突):
var name = "zzz";
var init = (function () {
var name = "ggg";
function callName() {
console.log(name);
}
return function() {
callName();
}
}())
init();
將全域性要實現的功能放到了一個區域性裡面,使它們互相不汙染
例六:
function test() {
var arr =[];
for (var i = 0; i < 10; i++){
arr[i] = function() {
document.write(i);
}
}
return arr;
}
var myArr = test();
for (var i = 0; i < 10; i++){
myArr[i]();
}
result: 打印出10個10(在函式被執行時才會讀裡面的語句,等函式被儲存到外部後,開始執行函式裡面的語句時,i的值已經是10)
function test() {
var arr =[];
for (var i = 0; i < 10; i++){
(function (j) { /*上面程式碼的基礎上使用立即執行函式*/
arr[j] = function() {
document.write(j);
}
})(i)
}
return arr;
}
var myArr = test();
for (var i = 0; i < 10; i++){
myArr[i]();
}
result: 打印出0到9
例七(除了用return,還可以用全域性變數實現閉包的效果):
var demo;
function test () {
var abc = 100;
function a () {
console.log(abc);
}
demo = a;
}
test();
demo();
閉包的危害: 當閉包函式被儲存在外部時,將會生成閉包。閉包會導致原有作用域鏈不被釋放,造成記憶體洩漏(即記憶體被佔用)
立即執行函式(很有意思)
- 此類函式沒有宣告,在一次執行後即釋放,適合做初始化工作
- 除啦寫法,和執行完就銷燬,其餘特性和一般函式是一樣的
立即執行函式很有意思的一點:不是特意規定的語法,是後來人們發現的,利用()即執行符號的特點 立即執行函式,官方給出兩種寫法: - (function () {……}()) W3C 建議第一種 - (function () {……})() - 不用函式名,左邊的(寫形參),右邊的(寫實參)
注意:只有表示式才能被執行符號()執行; 函式宣告不是表示式,函式表示式是表示式
例一:
var test = function () {
console.log('...');
}();
這也是立即執行函式的一種寫法 可以把函式宣告變成表示式,加執行符號(),變成立即執行函式
例二:
-function () {
console.log('...');
}();
立即執行函式的寫法還可以通過在函式宣告前加正號或負號,使函式宣告通過隱式轉換變成表示式,再在後面加執行符號()
例三;
function test (a, b, c, d) {
console.log(a + b + c + d);
}(2, 3, 5, 9);
這樣不會報錯,但是也不會執行。系統把函式宣告和 (2, 3, 5, 9) 看作是分開的, (2, 3, 5, 9) 不被看作執行符號
物件
物件的定義:
var deng = {
lastName : "Deng",
//屬性名 用冒號連線 屬性值 用逗號隔開
age : 40,
handsome : false //結尾處不用寫逗號了
}
console.log(deng.lastName); //取值舉例
deng.lastName = "old deng"; //賦值舉例
補充:物件裡面的函式叫做方法
var haha = {
name : "xixi",
age : 66,
health : 100,
smoke : function () {
console.log("i am smoking!cool!");
this.health--; /*在物件裡面,this.health <==> haha.health */
},
drink : function () {
console.log("i am drink");
this.health++;
}
}
物件的定義方法有四種:物件自變數,建構函式(系統,自定義),Object.creat()
物件的增、刪、改、查:
var haha = {
name : "xixi",
age : 66,
}
haha.wife = "xiaosan"; //增,注意要給屬性值
haha.name = "gaga"; //改
delete haha.age; //刪,要藉助delete操作符
//注意中間有空格
console.log(haha.age); //undefined
/*當一個變數沒定義就使用(除了typeof)會報錯,
但是一個物件的屬性沒定義就列印,為undefined*/
delete
一旦經歷了 var 的操作,所得出的屬性 window 這種屬性叫做不可配置的屬性
不可配置的屬性 delete不掉
屬性的表示方法
- obj.prop (最常用的,內部原理:當你呼叫obj.prop時,會在內部隱式轉換成obj[“prop”])
- obj[“prop”] (更加靈活)
法二的一個應用舉例(把字串和 + 的特點結合起來,實現屬性的拼接):
var deng = {
wife1 : {name : "xiao yi"}, //物件的屬性名是String型別的
wife2 : {name : "xiao er"},
wife3 : {name : "xiao san"},
wife4 : {name : "xiao si"},
wife5 : {name : "xiao wu"},
sayWife : function (num) {
return this['wife' + num];
}
}
物件的建立方法:
- var obj = {} plainObject 物件字面量/物件直接量
- 建構函式:系統自帶的建構函式和自定義
建構函式
var obj = new Object();
Object()是系統自帶的建構函式,這個建構函式可以批量生產物件,每個物件都一樣,但是彼此獨立
new Object() 能產生一個物件
var obj = new Object();
var obj = {};
這兩種寫法沒有任何區別 系統自帶建構函式建立的物件屬性的新增可以通過外面新增
JavaScript語言中的物件,相比其他語言是非常靈活的
自定義建構函式
函式形式和一般函式完全一樣,加new就可以生成物件
function Student (name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.grade = 2018;
}
var student1 = new Student("zgh", "18", "male");
注意:建構函式的命名規則:大駝峰(每個首字母都大寫),使其與一般函式易於區別
建構函式內部原理(較難,隱式this物件)
- 在函式體最前面隱式的加上 this = {}
- 執行 this.xxx = xxx;
- 隱式的返回this
不過,函式必須加new才能成為建構函式,有了new,上面三個隱式步驟才能開始;沒有new就是正常函式執行
一:邏輯最前面加上 var this = {} ,即隱式建立this物件,而this物件並不是空物件,誕生時應該是這樣的 :
function Student (name, age, sex) {
/*var this = {_proto_:Student.prototype},如果在Student物件中
找不到他要的屬性,就會通過_proto_屬性去它的原型中找,所以_proto_的
作用是連線物件和它的原型的*/
this.name = name;
this.age = age;
this.sex = sex;
this.grade = 2018;
}
var student1 = new Student("zgh", "18", "male");
二:執行this ,即給this物件裡面的屬性傳參賦值:
function Student (name, age, sex) {
/* var this = {
_proto_ : Student.prototype,
name : "zgh",
age : "18" ,
sex : "male"
}
this.name = name;
this.age = age;
this.sex = sex;
this.grade = 2018;
//return this;(三:隱式返回this物件)
}
var student1 = new Student("zgh", "18", "male");
注意,如果函式沒有加new,this預設指向window 在物件裡面的this指向物件名
做一個小改動,在建構函式最後顯式的返回一個空物件(或原始值):
function Student (name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.grade = 2018;
//return {}; 使這個建構函式建立的物件是一個空物件,因為顯式優於隱式
/*return 原始值; 那麼這個搗亂是不成功的,因為
函式只要加了new,返回的必須是物件,即使寫了 返回 原始值 ,也會被忽略掉,
強制返回this物件;
有new了就不可能返回原始值*/
}
var student1 = new Student("zgh", "18", "male");
原型(較難,描述的就是繼承關係)
- 定義:原型(prototype)是function物件的一個屬性,它定義了建構函式製造出的物件的公有祖先。通過該建構函式產生的物件,可以繼承該原型的屬性和方法。原型也是物件
- 利用原型特點和概念,可以提取共有屬性
- 物件如何檢視原型——隱式屬性
_proto_
- 物件如何檢視物件的建構函式——
constructor
原型的定義: 形象理解:若人是物件,則原型是人的祖先
prototype(原型)是系統自帶的屬性
以下面程式碼來說明:
//Person.prototype 原型
//Person.prototype {} 祖先
function Person () {
}
var person = new Person();
Person.prototype 是函式剛剛出生,就已經被系統定義好了 Person.prototype是一個物件 Person.prototype = {} 可以看作一個’空’物件 Person.prototype = {} 可以理解為 Person 的建構函式,構造出的物件的爸爸
本來Person.prototype = {} 是’空’物件,若在這個’空’物件裡面加屬性:
Person.prototype.name = "hehe";
function Person () {
}
var person = new Person();
console.log(person); // Person {}
console.log(person.name); //"hehe"
name屬性不是Person物件的屬性,但是是它的原型的屬性,所以可以直接呼叫, 原型描述的就是繼承關係
注意:若物件及其原型有相同屬性,呼叫這個屬性時,用物件自己的屬性
Person.prototype.name = "hehe";
function Person () {
this.name = "haha";
}
var person = new Person();
console.log(person.name); //"haha"
用於提取公有屬性: 原型的應用(把公有的程式碼提取出來放在原型裡)
一構造一個工廠建構函式為例:
function Car (color, owner) {
this.owner = owner;
this.color = color;
this.carName = "BMw";
this.height = 0.61818;
this.lang = 1;
}
var car = new Car('red', 'zgh');
這樣寫看似沒有什麼問題 但是這種流程化的東西,每一個用這個建構函式生成物件時,都要執行像this.height = 0.61818……這類建構函式中的不變屬性,都要執行一遍 這就是問題所在,這是程式碼的冗餘(每次建立Car物件都要執行許多一模一樣的程式碼) 解決辦法:用原型,繼承
Car.prototype.carName = "BMw";
Car.prototype.height = 0.61818;
Car.prototype.lang = 1;
function Car (color, owner) {
this.owner = owner;
this.color = color;
}
var car = new Car('red', 'zgh');
這樣簡化程式碼
原型的增、刪、改、查:
原型的增、刪、改(除了查)只能通過原型本身來進行操作,其子孫無法操作
刪: 用delete (在控制檯中刪除一個本來就沒有的屬性,會返回true)
原型的另一種設定方法:
Car.prototype.carName = "BMw";
Car.prototype.height = 0.61818;
Car.prototype.lang = 1;
function Car () {
}
var car = new Car();
Car.prototype = {
carName = "BMw";
height = 0.61818;
lang = 1;
}
function Car () {
}
var car = new Car();
第二種方法中的程式碼要放在有new的語句之前(理解這句話,請看下面的“幾個小例子來理解Person.prototype.name = “haha” 和 Person.prototype {name : “haha”} 的不同”) 這兩種設定方法一樣 後者的寫法更加簡便
注意:剛出生的原型理論上好像是’空’物件
function Car () {
}
var car = new Car();
console.log(Car.prototype); //Object {}
若在控制檯中 可以把 Object {} 展開
Object {
constructor : Car (),
_proto_ : Object //而且這兩行程式碼在控制檯中(是淺粉色的字型,代表隱式的)
//還可以繼續展開
}
_proto_
:
小知識:有一種命名規則,在你開發的時候,不希望你的同事用方法訪問某個屬性時,一般這個屬性以下劃線_ 開頭,通過名字告訴同事,你不要用
_proto_
的作用是連線物件和它的原型的(具體講解請看:建構函式的內部原理)
_proto_
的屬性值也可以手動修改:
Person.prototype.name = "hehe";
function Person () {
}
var obj = () {
name : "haha"
}
var person = new Person();
console.log(person._prot