1. 程式人生 > >JavaScript之ECMAScript筆記(基礎)

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的)

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的東西,這沒什麼好區分的

變數

  1. 變數名必須以 英文字母_$ 開頭
  2. 變數名可以包括英文字母_$數字
  3. 不可以用系統的關鍵字保留字作為變數名
  4. 區分大小寫
  5. 型別不需要定義,由值決定型別

資料型別概要:

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不等於任何數,包括它自己)

避免發生隱式型別轉換 : 絕對等於 === 絕對不等與 !==

基本語法

  1. 語句後面要用分號結束“”(英文字元的分號)
  2. 書寫格式要規範,運算子兩邊都應該有空格

編譯錯誤

一個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 暗示全域性變數:即任何變數,如果未經宣告就賦值,此變數就為全域性物件所有
  • 全域性物件是windowa = 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