JavaScript基礎(chǔ)知識(shí)(二)

1面向?qū)ο蟮某绦蛟O(shè)計(jì)

1.1 屬性類(lèi)型

ECMAScript 中有兩種屬性:數(shù)據(jù)屬性和訪問(wèn)器屬性。

1.1.1數(shù)據(jù)屬性

數(shù)據(jù)屬性包含一個(gè)數(shù)據(jù)值的位置。在這個(gè)位置可以讀取和寫(xiě)入值。數(shù)據(jù)屬性有 4 個(gè)描述其行為的特性。

  • [[Configurable]]:表示能否通過(guò) delete 刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問(wèn)器屬性。像前面例子中那樣直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為 true。

  • [[Enumerable]]:表示能否通過(guò) for-in 循環(huán)返回屬性。像前面例子中那樣直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為 true。

  • [[Writable]]:表示能否修改屬性的值。像前面例子中那樣直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為 true。

  • [[Value]]:包含這個(gè)屬性的數(shù)據(jù)值。讀取屬性值的時(shí)候,從這個(gè)位置讀;寫(xiě)入屬性值的時(shí)候,把新值保存在這個(gè)位置。這個(gè)特性的默認(rèn)值為 undefined。

var person = {}; Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas" });
alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas"

這個(gè)例子創(chuàng)建了一個(gè)名為 name 的屬性,它的值"Nicholas"是只讀的。這個(gè)屬性的值是不可修改 的,如果嘗試為它指定新值,則在非嚴(yán)格模式下,賦值操作將被忽略;在嚴(yán)格模式下,賦值操作將會(huì)導(dǎo) 致拋出錯(cuò)誤。
把 configurable 設(shè)置為 false,表示不能從對(duì)象中刪除屬性。如果對(duì)這個(gè)屬性調(diào)用 delete,則 在非嚴(yán)格模式下什么也不會(huì)發(fā)生,而在嚴(yán)格模式下會(huì)導(dǎo)致錯(cuò)誤。而且,一旦把屬性定義為不可配置的, 就不能再把它變回可配置了。此時(shí),再調(diào)用 Object.defineProperty()方法修改除 writable 之外 的特性,都會(huì)導(dǎo)致錯(cuò)誤。也就是說(shuō),可以多次調(diào)用 Object.defineProperty()方法修改同一個(gè)屬性,但在把configurable 特性設(shè)置為 false 之后就會(huì)有限制了。在調(diào)用 Object.defineProperty()方法時(shí),如果不指定,configurable、enumerable 和 writable 特性的默認(rèn)值都是 false。

1.1.2 訪問(wèn)器屬性

訪問(wèn)器屬性不包含數(shù)據(jù)值;它們包含一對(duì)兒 getter 和 setter 函數(shù)(不過(guò),這兩個(gè)函數(shù)都不是必需的)。 在讀取訪問(wèn)器屬性時(shí),會(huì)調(diào)用 getter 函數(shù),這個(gè)函數(shù)負(fù)責(zé)返回有效的值;在寫(xiě)入訪問(wèn)器屬性時(shí),會(huì)調(diào)用 setter 函數(shù)并傳入新值,這個(gè)函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù)。訪問(wèn)器屬性有如下 4 個(gè)特性。

  • [[Configurable]]:表示能否通過(guò) delete 刪除屬性從而重新定義屬性,能否修改屬性的特 性,或者能否把屬性修改為數(shù)據(jù)屬性。對(duì)于直接在對(duì)象上定義的屬性,這個(gè)特性的默認(rèn)值為 true。
  • [[Enumerable]]:表示能否通過(guò) for-in 循環(huán)返回屬性。對(duì)于直接在對(duì)象上定義的屬性,這 5 個(gè)特性的默認(rèn)值為 true。
  • [[Get]]:在讀取屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為 undefined。
  • [[Set]]:在寫(xiě)入屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為 undefined。
    訪問(wèn)器屬性不能直接定義,必須使用 Object.defineProperty()來(lái)定義。

1.2 創(chuàng)建對(duì)象

1.2.1工廠模式
function createPerson(name, age, job){ var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name); };
return o; }

函數(shù) createPerson()能夠根據(jù)接受的參數(shù)來(lái)構(gòu)建一個(gè)包含所有必要信息的 Person 對(duì)象??梢詿o(wú)數(shù)次地調(diào)用這個(gè)函數(shù),而每次它都會(huì)返回一個(gè)包含三個(gè)屬性一個(gè)方法的對(duì)象。工廠模式解決了創(chuàng)建 多個(gè)相似對(duì)象的問(wèn)題.

但卻沒(méi)有解決對(duì)象識(shí)別的問(wèn)題(即怎樣知道一個(gè)對(duì)象的類(lèi)型)。

1.2.2構(gòu)造函數(shù)模式
function Person(name, age, job){ this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){ alert(this.name);
}; }

要?jiǎng)?chuàng)建 Person 的新實(shí)例,必須使用 new 操作符。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷以下 4 個(gè)步驟:
(1) 創(chuàng)建一個(gè)新對(duì)象;
(2) 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此 this 就指向了這個(gè)新對(duì)象);
(3) 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性);
(4) 返回新對(duì)象。
構(gòu)造函數(shù)模式雖然好用,但也并非沒(méi)有缺點(diǎn)。使用構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè) 實(shí)例上重新創(chuàng)建一遍。

1.2.3原型模式
 function Person(){
    }
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){
alert(this.name); };
1.2.4組合使用構(gòu)造函數(shù)和原型模式
function Person(name, age, job){
this.name = name; 3 this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];

}

Person.prototype = { constructor : Person, sayName : function(){

alert(this.name); }

}
1.2.5寄生構(gòu)造函數(shù)模式
function Person(name, age, job){ var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name); };
return o; }
1.2.6穩(wěn)妥的構(gòu)造函數(shù)模式
function Person(name, age, job){
//創(chuàng)建要返回的對(duì)象
var o = new Object();
//可以在這里定義私有變量和函數(shù)

//添加方法
 o.sayName = function(){
alert(name);
}; 
//返回對(duì)象
return o; 
}

1.2.7繼承

可以通過(guò)兩種方式來(lái)確定原型和實(shí)例之間的關(guān)系。第一種方式是使用 instanceof 操作符,只要用 這個(gè)操作符來(lái)測(cè)試實(shí)例與原型鏈中出現(xiàn)過(guò)的構(gòu)造函數(shù),結(jié)果就會(huì)返回 true。

alert(instance instanceof Object);   //true

第二種方式是使用 isPrototypeOf()方法。同樣,只要是原型鏈中出現(xiàn)過(guò)的原型,都可以說(shuō)是該 原型鏈所派生的實(shí)例的原型,因此 isPrototypeOf()方法也會(huì)返回 true.

alert(Object.prototype.isPrototypeOf(instance));  //true
1.2.8 class類(lèi)
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

上面代碼定義了一個(gè)“類(lèi)”,可以看到里面有一個(gè)constructor方法,這就是構(gòu)造方法,而this關(guān)鍵字則代表實(shí)例對(duì)象。也就是說(shuō),ES5 的構(gòu)造函數(shù)Point,對(duì)應(yīng) ES6 的Point類(lèi)的構(gòu)造方法。

Point類(lèi)除了構(gòu)造方法,還定義了一個(gè)toString方法。注意,定義“類(lèi)”的方法的時(shí)候,前面不需要加上function這個(gè)關(guān)鍵字,直接把函數(shù)定義放進(jìn)去了就可以了。另外,方法之間不需要逗號(hào)分隔,加了會(huì)報(bào)錯(cuò)。

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

上面代碼表明,類(lèi)的數(shù)據(jù)類(lèi)型就是函數(shù),類(lèi)本身就指向構(gòu)造函數(shù)。
constructor方法是類(lèi)的默認(rèn)方法,通過(guò)new命令生成對(duì)象實(shí)例時(shí),自動(dòng)調(diào)用該方法。一個(gè)類(lèi)必須有constructor方法,如果沒(méi)有顯式定義,一個(gè)空的constructor方法會(huì)被默認(rèn)添加。
類(lèi)相當(dāng)于實(shí)例的原型,所有在類(lèi)中定義的方法,都會(huì)被實(shí)例繼承。如果在一個(gè)方法前,加上static關(guān)鍵字,就表示該方法不會(huì)被實(shí)例繼承,而是直接通過(guò)類(lèi)來(lái)調(diào)用,這就稱(chēng)為“靜態(tài)方法”。

class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

注意,如果靜態(tài)方法包含this關(guān)鍵字,這個(gè)this指的是類(lèi),而不是實(shí)例。

class Foo {
  static bar() {
    this.baz();
  }
  static baz() {
    console.log('hello');
  }
  baz() {
    console.log('world');
  }
}

Foo.bar() // hello

父類(lèi)的靜態(tài)方法,可以被子類(lèi)繼承。
實(shí)例屬性除了定義在constructor()方法里面的this上面,也可以定義在類(lèi)的最頂層。

class foo {
  bar = 'hello';
  baz = 'world';

  constructor() {
    // ...
  }
}

上面的代碼,一眼就能看出,foo類(lèi)有兩個(gè)實(shí)例屬性,一目了然。另外,寫(xiě)起來(lái)也比較簡(jiǎn)潔。

1.2.9 class繼承

Class 可以通過(guò)extends關(guān)鍵字實(shí)現(xiàn)繼承,這比 ES5 的通過(guò)修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 調(diào)用父類(lèi)的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString(); // 調(diào)用父類(lèi)的toString()
  }
}

上面代碼中,constructor方法和toString方法之中,都出現(xiàn)了super關(guān)鍵字,它在這里表示父類(lèi)的構(gòu)造函數(shù),用來(lái)新建父類(lèi)的this對(duì)象。

子類(lèi)必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇?lèi)自己的this對(duì)象,必須先通過(guò)父類(lèi)的構(gòu)造函數(shù)完成塑造,得到與父類(lèi)同樣的實(shí)例屬性和方法,然后再對(duì)其進(jìn)行加工,加上子類(lèi)自己的實(shí)例屬性和方法。如果不調(diào)用super方法,子類(lèi)就得不到this對(duì)象。
ES5 的繼承,實(shí)質(zhì)是先創(chuàng)造子類(lèi)的實(shí)例對(duì)象this,然后再將父類(lèi)的方法添加到this上面(Parent.apply(this))。ES6 的繼承機(jī)制完全不同,實(shí)質(zhì)是先將父類(lèi)實(shí)例對(duì)象的屬性和方法,加到this上面(所以必須先調(diào)用super方法),然后再用子類(lèi)的構(gòu)造函數(shù)修改this。
最后,父類(lèi)的靜態(tài)方法,也會(huì)被子類(lèi)繼承。

2.函數(shù)表達(dá)式

2.1 閉包

閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見(jiàn)方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)。
當(dāng)某個(gè)函數(shù)被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境(execution context)及相應(yīng)的作用域鏈。 然后,使用 arguments 和其他命名參數(shù)的值來(lái)初始化函數(shù)的活動(dòng)對(duì)象(activation object)。但在作用域鏈中,外部函數(shù)的活動(dòng)對(duì)象始終處于第二位,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位,......直至作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境。 在函數(shù)執(zhí)行過(guò)程中,為讀取和寫(xiě)入變量的值,就需要在作用域鏈中查找變量。
作用域鏈的這種配置機(jī)制引出了一個(gè)值得注意的副作用,即閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值。

function createFunctions(){ 
var result = new Array();
for (var i=0; i < 10; i++){ 
result[i] = function(){
return i; };
}
    return result;
}

這個(gè)函數(shù)會(huì)返回一個(gè)函數(shù)數(shù)組。表面上看,似乎每個(gè)函數(shù)都應(yīng)該返自己的索引值,即位置 0 的函數(shù) 返回 0,位置 1 的函數(shù)返回 1,以此類(lèi)推。但實(shí)際上,每個(gè)函數(shù)都返回 10。因?yàn)槊總€(gè)函數(shù)的作用域鏈中 都保存著 createFunctions()函數(shù)的活動(dòng)對(duì)象,所以它們引用的都是同一個(gè)變量 i。當(dāng) createFunctions()函數(shù)返回后,變量 i 的值是 10,此時(shí)每個(gè)函數(shù)都引用著保存變量 i 的同一個(gè)變量 對(duì)象,所以在每個(gè)函數(shù)內(nèi)部 i 的值都是 10。但是,我們可以通過(guò)創(chuàng)建另一個(gè)匿名函數(shù)強(qiáng)制讓閉包的行為 9 符合預(yù)期,如下所示。

function createFunctions(){ 
var result = new Array();
for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                 return num;
                       }
           }(i)
       }
                return result;
}

2.2 this

在閉包中使用 this 對(duì)象也可能會(huì)導(dǎo)致一些問(wèn)題。我們知道,this 對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定,在全局函數(shù)中,this 等于 window,而當(dāng)函數(shù)被作為某個(gè)對(duì)象的方法調(diào)用時(shí),this 等 于那個(gè)對(duì)象。不過(guò),匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其 this 對(duì)象通常指向 window。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容