JavaScript的發現與理解

1. 用var操作符聲明的變量與省略var操作符聲明的變量的區別

在JavaScript中,是通過var關鍵字來聲明變量的(稱這種聲明方式為正規聲明),但是,直接給沒有用var 聲明過的標識符賦值,也會創建變量(稱這種聲明方式為非正規聲明),但是,它們是有區別的,區別如下:

  1. 非正規變量只有在標識符被賦值的時候才會創建,而正規聲明的變量,在聲明時創建;
  2. 正規聲明的變量在其作用域內的任何地方都可用,哪怕是其聲明之前,只不過在其聲明之前,它的值是undefined,這是因為聲明提前的緣故。而非正規聲明的變量,只能在其創建之后的有效作用域內使用;
  3. 非正規聲明的變量總是全局變量,即使它在函數內創建也是如此;
  4. 在函數內,全局變量中有同名的變量,正規聲明的標識符會新創建變量并且使用優先級高于全局變量,而對于非正規聲明的變量不會重新創建變量,只是使用同名的全局變量,即并非正規聲明無效;
  5. 正規變量無法通過delete刪除,而非正規變量可以;

2. 省略var操作符聲明的變量是全局變量的原因

我認為ECMAScript這樣規定的原因是:
假設某個地方對全局變量variable進行賦值;
當因為某種失誤variable沒有被聲明或創建時,解析器有兩種方案:

  1. 作為錯誤處理;
  2. 創建variable變量;

由于JavaScritpt是作為瀏覽器的腳本語言,且瀏覽器的JavaScritpt代碼是可以分散的,且瀏覽器并不能保證所有的JavaScritpt代碼全部執行,也不能保證所有JavaScript代碼按順序執行,所以經常存在某個聲明變量的語句未在合適的時機執行的情況,所以把這種示聲明的變量的引用當作錯誤處理是不合適的,所以第一種方案不選,只能選擇第2種方案:創建變量;

創建變量也有兩種方案:

  1. 創建局部變量;
  2. 創建全局變量;

但考慮以下2個原因,應該選擇:創建全局變量;

  1. 如果創建局部變量,則當其它JavaScritpt代碼片斷執行時仍存在上述問題;但如果創建全局變量,則當其它JavaScritpt代碼片斷執行時便不存在該變量沒有聲明的情況了;
  2. 上述問題發生在引用其它JavaScript代碼片斷中聲明的全局變量的情況下,所以,如果創建局部變量的話,就違背的了代碼的意圖(把該變量作為全局變量用);

所以,綜合以上就有:省略var操作符聲明的變量是全局變量;

3. 作用域鏈的理解

  1. 可以把作用域鏈理解成是一個棧結構;
  2. 每個作用域都有一個作用域對象用于保存在該作用域內創建的變量(包括函數),其保存的方式是:在作用域內創建的變量會成為作用域對象的屬性;
  3. 作用鏈鏈保存的是各級作用域對象的引用,其中最近的作用域的作用域對象在最前端,越遠的作用域的作用域對象越靠后;
  4. 全局作用域的作用域對象是全局對象本身;所以,每個作用域鏈的最后端都是全局對象的引用;
  5. 在全局作用域內創建的變量會成為全局對象的屬性的原因:由于2(在作用域內創建的變量會成為作用域對象的屬性)和4(全局作用域的作用域對象是全局對象本身),所以在全局作用域創建的變量會成為全局對象的屬性;
  6. 函數的作用域鏈是在函數對象被創建時(被定義時)創建的;
  7. 每當函數被執行時,都會新創建一個函數的作用域對象,并把該作用域對象推到作用域鏈的最前端;
  8. 每當函數執行結束時,都會把函數的作用域對象從該函數作用鏈中推出;

4. 標識符查找

  • 訪問屬性,查找的是原型鏈;
  • 訪問變量,查找的是作用域鏈;

作用鏈是一個棧,棧里保存的是作用域對象,棧的最頂端是當前作用域對象,棧的最底端是全局作用域對象;
作用域對象只有2種:

  • 全局作用域對象;
  • 函數作用域對象;

所以當通過屬性調用的方式調用對象的方法時,該對象只會成this的值;不會成為它的方法(即:對象中屬性值是函數的屬性)的作用域鏈中的作用域對象,不過可以通過with操作符使對象成為它的方法的作用域鏈中的作用域對象;

5. 數組的本質

數組的本質還是對象,數組是通過對象實現的,它并沒有為索引加入新的機制,數組中的索引本質是作為數組對象的屬性存在的,因為打點調用的屬性不能是數字,所以數組的索引不通過打點調用,只能通過方括號的方式調用;

6. 函數this的取值機制

經過測試,總結得出以下結論:

結論1:容易理解的this取值機制

函數的this的值取決于函數的調用方式;
函數有2種調用方式,如下:

  • 直接調用;如:fn();
  • 屬性調用;如:obj.fn();
  • 當函數以直接調用的方式調用時,this的值為全局對象,如:winodw;
  • 當函數以屬性調用的方式調用時,this的值為調用函數的對象;

對象屬性調用有2種方式:

  • 打點調用,如:obj.attr
  • 方括號調用,如:obj["attr"]

所有通過這2種屬性調用方式調用的函數的this的值都為調用函數的對象;

又因為數組中的索引本質是作為數組對象的屬性存在的,所以調用數組中的函數(如:array[index]();)時,被調用的函數的this的值為該數組對象;

結論2:this取值的本質機制

我認為,本質上,函數的this的值取決于函數對象的查找方式:

  • 如果函數對象在被調用時是在作用域鏈上查找到的,則該函數中this的值為全局對象;
  • 如果函數對象在被調用時是在原型鏈上查找到的,則該函數中this的值為調用函數的對象;

因為:

  • 當函數以 直接調用 的方式調用時,它是在作用域鏈上被查找到的,所以該函數中this的值為全局對象;
  • 當函數以 屬性調用 的方式調用時,它是在原型鏈上被查找到的,所以該函數中this的值為調用函數的對象;

所以:

結論1是正確的,并且結論1只是結論2的一個特例,即:結論2包含結論1;

注意

  • 計算屬性的get和set函數中的this也符合上述規則;
  • ES6引用了模塊機制,對模塊中頂層的this做了處理,使得在ES6的模塊中,頂層的this的值是undefined,所以,不應該在頂層代碼使用this; 所以,如果在模塊中直接訪問全局對象(如 window 對象)的計算屬性(即:不是通過調用 window 屬性的方式來訪問,如 window.計算屬性名,而是直接訪問,如 全局對象的計算屬性名),則該計算屬性的 set 和 get 函數的 this 也是 undefined ;

7. 函數調用表達的理解

假設在全局環境中有以下代碼:

var inWhere = "The Window";
var aObject = {
        inWhere:"The aObject",
        getWhere:function(){
              return this.inWhere
           }
  };

則在全局環境下,以下三句代碼及其執行結果如下:
代碼1:

aObject.getWhere();     //結果:"The aObject"

代碼2:

(aObject.getWhere)();       //結果:"The aObject"

代碼3:

(aObject.getWhere=aObject.getWhere)();  //結果:"The Window" 

我認為造成以上執行結果的原因是:

  • 因為對于代碼1和代碼2中的函數在被調用時,都是在原型鏈上查找到的,所以它們的執行結果均為The aObject
  • 因為賦值語句=在內部本質上是作為函數來實現的,并且這個函數有個返回值,會返回等號=右邊表達式的值,在代碼3中該返回值就是getWhere函數,所以 (aObject.getWhere=aObject.getWhere) 結果是getWhere函數,并且這個函數是從等號=函數的返回值中取得的;又因為返回值是在作用域鏈中查找到的,所以在getWhere被執行時,this的值為全局對象,所以代碼3執行結果是The Window

8. 未定義的變量與未定義的屬性的訪問

假設以下代碼在全局作用域下執行,且gby和dyx都從未定義過,則執行結果如下:

gby;        //結果:報錯:ReferenceError: Can't find variable: gby;
window.gby; //結果:undefined
window.dyx; //結果:undefined

所以:訪問未定義的變量會報錯,但訪問未定義的屬性不會報錯,只是結果為undefined;
如果gby是全局變量,則gby 和 window.gby 訪問的都是同一個變量,那為什么這兩種訪問方式的結果不一樣呢?我認為原因如下:
訪問變量時是通過搜索作用域鏈來查找變量的,而訪問屬性是通過搜索原型鏈來查找屬性的,因為這兩種訪問方式的搜索方式不一樣,所以導致了結果不一樣;

9. JavaStript的特點

  1. JavaStript解析器會忽略后面對已存的變量的聲明操作,但會執行對已存的變量的賦值操作(包括初始化操作);
  2. 當function關鍵字用作函數的聲明時,不能省略函數名字,當function關鍵字用在表達式中創建函數時,就可以省略函數名;可以通過圓括號將“()”將函數聲明轉成函數表達式;所以直接執行代碼:function(){}(); 會報錯,而代碼:(function(){})();可以正常執行;
  3. 聲明的全局變量會成為全局對象(window對象)的屬性,但定義全局變量 和 直接在全局對象上定義屬性 是有差別的,差別如下:
    3.1. 定義全局變量生成的屬性的configurable特性配置 false
    3.2直接在全局對象上定義屬性的屬性的configurable特性配置 true
    所以,通過定義變量生成的屬性不能用delete操作符刪除,通過直接定義屬性的屬性可能用delete操作符刪除;

10. new操作符的特性

使用new操作符的格式如下:

new 構造函數;

經過探究,new操作符有如下特性:

  1. 每次調用都會創建一個新的對象;
  2. 會把構造函數的原型屬性設置成新創建的對象的原型;
  3. 會把構造函數的this指向新創建的對象;
  4. 如果構造函數沒有返回值,則會默認返回new新創建的對象;
  5. 如果構造函數有返回值,則會返回構造函數返回的值;

11. new.target

new.target屬性是一個可以被所有函數訪問的元屬性
new.target屬性允許你檢測函數或構造方法是否通過是通過new運算符被調用的。
new.target的具體取值如下:

  1. 在通過new運算符被初始化的函數或構造方法中,new.target返回一個指向構造方法或函數的引用;
  2. 在普通的函數調用中,new.target 的值是undefined。
  3. 在箭頭函數中,new.target指向外圍函數的 new.target。

new.target語法由一個關鍵字"new",一個點,和一個屬性名"target"組成。通常標識符.的作用是提供屬性訪問的上下文,但這里new.其實不是一個真正的對象。不過在構造方法調用中,new.target指向被new調用的構造函數,所以new.成為了一個虛擬上下文。

12. undefined和null

在JavaScript的原始類型中包含了undefined和null;其中undefined是全局對象的一個屬性,null是一個字面量;

13. 正則表達式

在JavaScript中創建正則表達式對象有兩種方式:

  1. 正則表達式字面量;
  2. RegExp構造函數,格式如下:new RegExp(模式:string,標志:string);

通過這2種方式創建的正則表達式是有區別的,如下:
對于相同的正則表達式字面量,共享峽一個RegExp類型的實例,而使用構造函數創建的每一個RegExp類型的實例都是一個新實例;

注意:
RegExp構造函數接收2個參數,第1個參數是要匹配的字符串模式,第2個參數是可選的標志字符串;在字符串格式的模式中,需要對元字符進行雙重轉義;

14. 提升

JavaScript在解析JavaScript代碼之前,會先進行一個聲明提升的預處理,聲明提升的規則如下:

  1. 會把聲明提升到單個<script>標簽內的每個作用域內的最頂端 或者 單個文件內的每個作用域內的最頂端;
  2. 提升后,變量聲明總是放在所有函數聲明的前面;
  3. 對于同一作用域內重復聲明的變量或者函數,有如下規則:
    3.1 對于變量,解析器會忽略第1次聲明之后的同名變量的聲明;
    3.2 對于函數,后面聲明的函數會覆蓋前面聲明的同名函數;
  4. 變量聲明的提升只是提升變量名字的聲明,不會提升變量的賦值操作;對于函數聲明的提升,不僅會提升函數名字的導明,也會提升函數的代碼定義;
  5. FireFox瀏覽器不能對代碼塊內的聲明進行提升;

注意:
由于上述的規則2,所以在同一聲明提升的范圍內,如果同一名字即有變量的聲明又有函數的聲明,則函數的聲明總是會覆蓋掉變量的聲明;

15. 閉包

我對閉包的定義是:
閉包的標準定義:攜帶外部變量的函數稱為閉包;

我之所以這樣對閉包下定義,是因為這個定義幾乎適用所有語言的閉包,如:Object-C、Swift、JavaScript等等;所以我認為這是較標準的定義;

對于JavaScript中的閉包雖然符合標準定義,但是由于JavaScript語言的一些特性,使得JavaScript中的閉包的實現與其它語言(如:Object-C、Swift)的實現并不一樣;

很多人都認為JavaScript中的閉包只會攜帶它內部引用的外部變量,并不會攜帶沒有引用的外部變量,其實這是錯誤的;可以通過下面的代碼證明:

function outFun() {
    var outArg1 = "外部參數1";
    var outArg2 = "外部參數2";
    function outArg3() {
        console.log("外部參數3");
    }


    /*定義閉包
    * codeStr:字符串類型的參數,該參數的值將被當作代碼執行
    * return : 返回將codeStr作為代碼執行的結果;
    * */
    function closureFun(codeStr) {
        console.log("閉包引用的變量的值:",outArg1);
        return eval(codeStr);   //返回將codeStr作為代碼執行的結果;
    }

    return closureFun;
}

var getValueOf  = outFun();     //獲取閉包




var arg2Value = getValueOf("outArg2");      //嘗試獲取閉包內沒有引用的變量outArg2的值;
console.log(arg2Value);     //輸出結果為:外部參數2


var arg3Value = getValueOf("outArg3");      //嘗試獲取閉包內沒有引用的函數outArg3;
arg3Value();     //輸出結果為:外部參數3

從示例代碼中的運行結果中可以看出,對于閉包引用到的外部變量outArg1 和 閉包沒有引用到的變量outArg2和函數outArg3,在閉包執行時都能被正確地訪問到,所以閉包會攜帶所有的外部變量(函數也是變量);

其實閉包攜帶外部變量的機制并非閉包的特有機制,它是函數的作用域鏈的一個效應;在JavaScript中,閉包和普通函數沒有任何本質的區別,閉包只是函數在某種使用場景下的一個名字,就好比兇器只是刀在用于行兇時的名字;

JavaScript中的閉包能攜帶外部變量的原因是:
JavaScript的函數在被創建時(被定義時)會生成自己的作用域鏈;該作用域鏈會保存各級作用域對象的引用,所以JavaScript的函數能夠訪問其外部的所有變量;
說見上文的《作用域鏈的理解》

所以,本質上,JavaScript中的閉包攜帶的不是外部變量,而是外部的作用域對象;

使用閉包的建議:
由于JavaScript中的函數(包括閉包)會外部的作用域鏈;所以,建議:

  1. 閉包的嵌套不要太深;
    閉包嵌套越深,占用的內存空間就越大;
  2. 不要使用過多的閉包;因為閉包較占內存;

16. 數組的splice()方法的參數說明

splice()方法可以接收任意數量的參數,參數的含意如下:
第1個參數:指定操作的起始項的索引;
第2個參數:指定要刪除的項數;
第3個及以后的參數:指定要插入的項目;

17. 各種添加事件處理程序的特點

事件處理程序的名字以on開頭,緊接個事件的名字,如:click事件的事件處理程序名字為onclick;
為事件指定處理程序的方式有好幾種:

方式1. 對象的事件屬性

格式為:

對象.事件屬性 = 事件處理函數;

示例:

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    //處理事件
}

這種添加方式有以下特點:

  1. 事件處理程序是作為對象的方法來運行的,所以事件處理程序中this的值是相應的對象;
  2. 事件處理程序會在事件流的冒泡階段被處理;
  3. 通過給對象的事件屬性設置null來移除事件;
  4. 只能同時指定一個事件處理程序;
  5. 只能把事件處理程序添加到冒泡階段;
  6. 使用 對象的事件屬性(方式1) 和 HTML的事件標簽屬性(方式3)這兩種方式對 同一個元素 添加的事件處理器,只會最后一次設置的有效;因為 HTML的事件標簽屬性的方式(方式3) 最終也是通過 對象的事件屬性的方式(方式1) 來添加事件處理器的;
  7. 如果事件處理程序返回假值 return false; ,則會阻止此事件的相關默認操作,即:相當于調用事件對象event的preventDefault()方法;

方式2. 元素的addEventListener方法

這種方式是通過元素的addEventListener方法添加事件處理程序,通過元素的removeEventListener方法移除事件;
所有DOM節點中都包含addEventListener和removeEventListener這2個實例方法,這2個方法的描述如下:

addEventListener方法:

語法:

element.addEventListener(event, function, useCapture)

參數值:

event:必須。字符串,指定事件名。
注意: 不要使用 "on" 前綴。 例如,使用 "click" ,而不是使用 "onclick"。 

function:必須。指定要事件觸發時執行的函數。 
當事件對象會作為第一個參數傳入函數。 事件對象的類型取決于特定的事件。例如, "click" 事件屬于 MouseEvent(鼠標事件) 對象。

useCapture:可選。布爾值,指定事件是否在捕獲或冒泡階段執行。true - 事件句柄在捕獲階段執行
        false- false- 默認。事件句柄在冒泡階段執行;

示例:

var btn = document.getElementById(“myBtn”);
btn.addEventListener("click", function(){
    document.getElementById("demo").innerHTML = "Hello World";
});

removeEventListener方法:

語法:

element.removeEventListener(event, function, useCapture)

參數值:

event:必須。要移除的事件名稱。
注意: 不要使用 "on" 前綴。 例如,使用 "click" ,而不是使用 "onclick"。 

function:必須。指定要移除的函數。 

useCapture:可選。布爾值,可選。布爾值,指定移除事件句柄的階段。
true - 在捕獲階段移除事件句柄;
false- 默認。在冒泡階段移除事件句柄;
注意: 如果添加兩次事件句柄,一次在捕獲階段,一次在冒泡階段,你必須單獨移除該事件。

示例:

// 向 <div> 元素添加事件句柄
document.getElementById("myDIV").addEventListener("mousemove", myFunction);

// 移除 <div> 元素的事件句柄
document.getElementById("myDIV").removeEventListener("mousemove", myFunction);

這種添加方式有以下特點:

  1. 可以添加多個處理事件;
  2. 通過addEventListener添加的事件處理程序,必須通過removeEventListener才能移除;
  3. 即可以把事件處理程序添加到捕獲階段,又可以添加到冒泡階段;
  4. 事件處理程序中this的值是被添加的元素對象;
  5. 不能通過事件處理程序返回假值 return false; 來會阻止此事件的相關默認操作;

方式3. HTML的事件標簽屬性

格式為:

<標簽名  事件處理程序名="JavaScript代碼">

通過這種方式指定的事件處理代碼(相應事件處理程序的標簽屬性(簡稱:事件標簽屬性)的值,如:“JavaScript代碼”)會被放在一個函數環境中去執行;具體實現機制如下:

通過事件的標簽屬性指定事件的實現要機制:

  1. 瀏覽器會為相應事件動態地創建一個函數作為相應事件的事件處理函數,并把該函數通過對象的事件屬性的方式設置給了當前元素dom對象上對應的事件處理器屬性上,該函數如下:
    dom對象.事件屬性 = function (event) {
        with (document){
            with (this){
                //元素的事件標簽屬性的值會在這里被當作JavaScript代碼來執行;
                eval(事件標簽屬性的值);
             }
         }
    }
    
  2. 這個動態創建的事件處理函數中有一個入參,名收 event,用來接收事件對象;
  3. 事件標簽屬性的值會被當作JavaScript代碼在這個動態創建的事件處理程序中運行:
  4. 這個動態創建的事件處理函數內部使用 withdocumentthis 對象擴展了成當前的作用域對象,所以 documentthis 的所有屬性都可以在當前作用域里直接通過屬性名字作為變量來直接訪問;

通過這個實現機制,可知:

通過事件的標簽屬性指定事件的方式有以下特點:

  1. 事件標簽屬性的值會被當作JavaScript代碼在這個動態創建的事件處理程序中運行;
  2. 使用 對象的事件屬性(方式1) 和 HTML的事件標簽屬性(方式3)這兩種方式對 同一個元素 添加的事件處理器,只會最后一次設置的有效;因為 HTML的事件標簽屬性的方式(方式3) 最終也是通過 對象的事件屬性的方式(方式1) 來添加事件處理器的;
  3. 這個動態創建的事件處理函數中的tihs值是事件的目標元素;事件標簽屬性的JavaScript代碼中被調用的函數的this值是 window 對象;
  4. 在事件標簽屬性的值的JavaScript代碼中,可以直接訪問 保存事件對象的變量event、綁定當前事件的dom元素this,也可以通過 document對象 和 綁定當前事件的dom對象 的任意屬性名字作為變量名來直接訪問;如:<input onclick="clickHandler(event,this,cookie,value)">,其中 cookiedocument對象的屬性,value 是 input元素的Dom對象的屬性;
  5. 只能把事件處理程序添加到冒泡階段;
  6. 在事件標簽屬性的值中可以通過返回假值 return false; 來阻止此事件的相關默認操作,即:相當于調用事件對象event的preventDefault()方法;

18. change、propertychange 和 input 事件的區別

onchange 事件與 onpropertychange 事件的區別:

onchange 事件在內容改變(兩次內容有可能還是相等的)且失去焦點時觸發;
onpropertychange 事件卻是實時觸發,即每增加或刪除一個字符就會觸發,通過 js 改變也會觸發該事件,但是該事件 IE 專有;

oninput 事件與 onpropertychange 事件的區別:

oninput 事件是 IE 之外的大多數瀏覽器支持的事件,在 value 改變時觸發,實時的,即每增加或刪除一個字符就會觸發,然而通過 js 改變 value 時,卻不會觸發。
onpropertychange 事件是任何屬性改變都會觸發的,而 oninput 卻只在 value 改變時觸發,oninput 要通過 addEventListener() 來注冊,onpropertychange 注冊方式跟一般事件一樣。(此處都是指在js中動態綁定事件,以實現內容與行為分離)

oninput 與 onpropertychange 失效的情況:

oninput 事件:

  • 當腳本中改變 value 時,不會觸發;
  • 從瀏覽器的自動下拉提示中選取時,不會觸發;

onpropertychange 事件:

  • 當 input 設置為 disable=tru e后,onpropertychange 不會觸發;

19. void運算符

void是一元運算符,它用在操作數的前面,操作數可以是任意類型;操作數會被正常計算,void會忽略操作數的計算結果,并返回undefined;

20. textContent 與 innerText、innerHTML 的區別

Node.textContent 屬性表示一個節點及其后代的文本內容。

語法

let text = element.textContent;
element.textContent = "this is some sample text";

描述

  • 如果 element 是 Document,DocumentType 或者 Notation 類型節點,則 textContent 返回 null。如果你要獲取整個文檔的文本以及CDATA數據,可以使用 document.documentElement.textContent。
  • 如果節點是個CDATA片段,注釋,ProcessingInstruction節點或一個文本節點,textContent 返回節點內部的文本內容(即 nodeValue)。
  • 對于其他節點類型,textContent 將所有子節點的 textContent 合并后返回,除了注釋、ProcessingInstruction節點。如果該節點沒有子節點的話,返回一個空字符串。
  • 在節點上設置 textContent 屬性的話,會刪除它的所有子節點,并替換為一個具有給定值的文本節點。

與innerText的區別

Internet Explorer 引入了 node.innerText。意圖類似,但有以下區別:

  • textContent 會獲取所有元素的內容,包括 <script><style> 元素,然而 innerText 不會。
  • innerText意識到樣式,并且不會返回隱藏元素的文本,而textContent會。
  • 由于 innerText 受 CSS 樣式的影響,它會觸發重排(reflow),但textContent 不會。
  • 與 textContent 不同的是, 在 Internet Explorer (對于小于等于 IE11 的版本) 中對 innerText 進行修改, 不僅會移除當前元素的子節點,而且還會永久性地破壞所有后代文本節點(所以不可能再次將節點再次插入到任何其他元素或同一元素中)。

與innerHTML的區別

正如其名稱,innerHTML 返回 HTML 文本。通常,為了在元素中檢索或寫入文本,人們使用innerHTML。但是,textContent通常具有更好的性能,因為文本不會被解析為HTML。此外,使用textContent可以防止 XSS 攻擊。

歸屬區別

  • textContent 是 Node 對象的屬性;
  • innerHTML 是 Element 對象的屬性;
  • innerText 是 HTMLElement 對象的屬性;

21. 類型檢查與驗證

typeof

語法:
typeof運算符后跟操作數:

typeof operand

或者

typeof (operand)

參數:

  • operand 是一個表達式,表示對象或原始值,其類型將被返回;
  • 括號是可選的。

說明:
typeof 操作符返回一個字符串,表示操作數的基本類型。

下表總結了typeof可能的返回值:

類型 結果
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Symbol "symbol"
宿主對象(由JS環境提供) Implementation-dependent
函數對象 "function"
任何其他對象 "object"

instanceof

語法:

object instanceof constructor

參數:

  • object : 要檢測的對象
  • constructor : 某個構造函數

說明:
instanceof 運算符用來檢測 constructor.prototype 是否存在于參數 object 的原型鏈上。

機制:
instanceof 的等效函數如下:

function instanceof(object,constructor){
    
    let nextProto = object.__proto__;
    
    while (constructor !== null) {
        if (nextProto === constructor.prototype) {
            return true;
        }
        nextProto = nextProto.__proto__;
    }

    return false;
}

或者

function instanceof(object,constructor){
    
    let nextProto = Object.getPrototypeOf(object);
    
    while (constructor !== null) {
        if (nextProto === constructor.prototype) {
            return true;
        }
        nextProto = Object.getPrototypeOf(nextProto);
    }

    return false;
}

typeof 與 instanceof 區別

  • typeof 是用來檢查類型的,而 instanceof 是用來驗證類型的;
  • typeof 返回的是字符串,而 instanceof 返回的是布爾值;
  • typeof 檢查的是基本類型,而 instanceof 驗證的是對象類型;

獲取對象類型的具體類型信息

typeof 操作符只能返回操作數的基本類型,對于任何自定義的對象類型, typeof 操作符只會返回 "object" ,而 instanceof 是用來驗證對象類型的,不是用來獲取對象類型的;若想獲取對象的具體類型信息,可通過訪問對象的 constructor 屬性,該屬性會返回該對象的構造函數;若只想獲取對象的具體類型的名字,則可通過訪問對象的構造函數 constructor 的 name 屬性;如下:

獲取對象的類型:

object.constructor

獲取對象的類型的名字:

object.constructor.name

22. Attribute和Property的區別

1. Attribute和Property的概念

  • attribute : 特性,XML 元素中的概念,用于描述 XML 標簽的附加信息,即: XML 標簽的特性; 如:<input type="text" value="初始值" /> 中的 typevalue 均是input元素的特性;
  • property : 屬性,JavaScript 對象的概念,用于描述 JavaScript 對象的成員,即:JavaScript 對象的屬性;如: JavaScript 對象 var person = {name:"郭斌勇",age:28} 中的 nameage 均是對象 person 的屬性;

2. Attribute和Property的關系

為了能夠在 JavaScript 中操作 HTML 瀏覽器會為 HTML 中的元素創建相應的 Dom 對象,Dom 對象就是普通的 JavaScript 對象,它是 HTMLElement 類型的對象,所以 dom 對象的所有成員都稱為 property (屬性);

JavaScript 中的 HTMLElement 類型的對象有個 attributes 屬性(property),它是 NamedNodeMap 類型的類數組對象 ,它里面保存的都是 HTML 中元素的 特性(attribute) 相對應的 JavaScript 對象;在 JavaScript 中,HTML 中元素的 attribute (特性) 用 Attr 類型的對象表示;

在 XML 中,元素的特性(attribute)值是沒有類型之分的,所以,無論是數字、字符中、布爾值,在 XML 眼中,它們都是同一類東西; JavaScript 中的 Attr 類型把 HTML 中的元素的特性值都映射為字符串類型;為了更好地使用 Dom , 瀏覽器也在 Dom 對象中為標準的 HTML 元素的特性(attribute)添加了相應的屬性 property ,如:dom 對象的 idtypecheckedvaluedefaultValue 等等;為了更好的使用,瀏覽器為這些屬性的值也做了相應的處理,所以,這些屬性(property)的值與 HTML 元素中相應的特性值并非完全一致;如:對于 Dom 對象,dom.checked 的值始終是布爾類型的,dom.value 的值始終是相應 input 元素的當前值,并非是 HTML 中 input 元素的 value 特性的值,dom.defaultValue 則是 HTML 中 input 元素的 value 特性的值;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容