1. 程式人生 > 實用技巧 >js 面試相關知識點彙總

js 面試相關知識點彙總

知識點彙總

js的基本型別

Number,String,Boolean,Null,undefined。

js的引用型別

Object,function

型別存放

基本型別的資料是存放在棧記憶體中的,而引用型別的資料是存放在堆記憶體中的

棧資料結構(stack)

先進後出,後進先出

堆資料結構(heap)

堆資料結構是一種樹狀結構。它的存取資料的方式,則與書架與書非常相似。

佇列(queue)

佇列是一種先進先出(FIFO)的資料結構。

Null和undefined的區別

null表示"沒有物件",即該處不應該有值。典型用法是:

  • 作為函式的引數,表示該函式的引數不是物件。
  • 作為物件原型鏈的終點。

undefined表示"缺少值",就是此處應該有一個值,但是還沒有定義。典型用法是:

  • 變數被聲明瞭,但沒有賦值時,就等於undefined。
  • 呼叫函式時,應該提供的引數沒有提供,該引數等於undefined。
  • 物件沒有賦值的屬性,該屬性的值為undefined。
  • 函式沒有返回值時,預設返回undefined。

如何判斷一個變數是Array型別 ? 如何判斷一個變數是Number型別 ?

typeof:null、物件、陣列返回的都是object型別;對於函式型別返回的則是function,再比如typeof(Date),typeof(eval)等。

instanceof:

var a=[];
console.log(a instanceof Array) //返回true

使用instaceof和construcor,被判斷的array必須是在當前頁面宣告的!比如,一個頁面(父頁面)有一個框架,框架中引用了一個頁面(子頁面),在子頁面中聲明瞭一個array,並將其賦值給父頁面的一個變數,這時判斷該變數,Array == object.constructor;會返回false;

特性判斷法:
以上方法均有一定的缺陷,但要相信人民大眾的智慧是無所不能及的,我們可根據陣列的一些特性來判斷其型別

function isArray(object){
 return object && typeof object==='object' && 
   typeof object.length==='number' && 
   typeof object.splice==='function' && 
    //判斷length屬性是否是可列舉的 對於陣列 將得到false 
   !(object.propertyIsEnumerable('length'));
}

有length和splice並不一定是陣列,因為可以為物件新增屬性,而不能列舉length屬性,才是最重要的判斷因子。

原型物件上有toString,轉碼成相應的型別

function isArray(o) {
 return Object.prototype.toString.call(o) === ‘[object Array]‘;
}

類陣列,即擁有 length 屬性並且 length 屬性值為 Number 型別的元素
包括陣列、arguments、HTML Collection 以及 NodeList 等等

判斷物件是否為空

是否是 {}、[] 或者 "" 或者 null、undefined

toString方法詳解

當呼叫toString方法時,下列步驟會被執行:

如果this未定義時,返回“[object Undefined]”

     如果this為null時,返回“[object Null]”

定義O,並且讓O=ToObject(this)

定義class,並且使class為O內建屬性[[class]]的值

返回三個字串的拼接字元:"[object",class,"]"

通過官方解釋,可以清晰的得出toString()是在以特殊的字串形式輸出this的型別

dom api

建立型api主要包括createElement,createTextNode,cloneNode和createDocumentFragment

修改頁面內容的api主要包括:appendChild,insertBefore,removeChild,replaceChild

節點查詢型API:document.getElementById;document.getElementByTagName; document.getElementsByName;document.getElementsByClassName;document.querySelector和document.querySelectorAll

節點關係型api:

  • 父關係型parentNode,parentElement
  • 兄弟關係型previousSibling,previousElementSibling,nextSibling,nextElementSibling
  • 子關係型childNodes,children,firstNode,lastNode,hasChildNodes
  • 元素屬性型setAttribute,getAttribute
  • 元素樣式型window.getComputedStyle:var style = window.getComputedStyle(element[, pseudoElt]);element是要獲取的元素,pseudoElt指定一個偽元素進行匹配。返回的style是一個CSSStyleDeclaration物件。通過style可以訪問到元素計算後的樣式;element.getBoundingClientRect:返回元素的大小以及相對於瀏覽器可視視窗的位置

attribute和property的區別

DOM 元素中的 attribute 與 property 並不相同。attribute 通常翻譯為“特性”,property 通常翻譯為“屬性”。其實它們是近義詞,並不能根據特性、屬性這兩個詞彙來區分 attribute 與 property。

特性:某事物所特有的性質;特殊的品性、品質。
屬性:事物所具有的不可缺少的性質。
所以,attribute 與 property 都可以叫“特性”,也都可以叫“屬性”。

從 HTML 到 DOM 元素,一種是宣告式的語言,一種是命令式語言。attribute 是直接收集 HTML 中的屬性轉為 js 物件,物件的 value 最接近原生態,也就是 HTML 標記裡面的樣子;
property 也是轉為 js 物件,但是轉化的過程中會對 value 做一些處理,將 value 轉為對 js 來說更有意義的值。

事件

事件部落格

閉包

什麼是閉包

簡單來說,閉包是指可以訪問另一個函式作用域變數的函式,一般是定義在外層函式中的內層函式。

為什麼需要閉包呢

區域性變數無法共享和長久的儲存,而全域性變數可能造成變數汙染,所以我們希望有一種機制既可以長久的儲存變數又不會造成全域性汙染。

特點

  • 佔用更多記憶體
  • 不容易被釋放

箭頭函式與普通函式的區別

1、沒有自己的this、super、arguments和new.target繫結。
2、不能使用new來呼叫。
3、沒有原型物件。
4、不可以改變this的繫結。
5、形參名稱不能重複。
箭頭函式中沒有this繫結,必須通過查詢作用域鏈來決定其值。
如果箭頭函式被非箭頭函式包含,則this繫結的是最近一層非箭頭函式的this,否則this的值則被設定為全域性物件。

javaScript函式引數傳值的方式

我們都知道,JavaScript的變數包括基本型別和引用型別,5種基本資料型別:

  • Undefined
  • Null
  • Boolean
  • Number
  • String

一般來說,引用型別的值是儲存在記憶體中的物件.而JavaScript不允許直接訪問記憶體中的位置,所以,操作物件時,實際上操作的是物件的引用而不是實際的物件,因此,如果使用下面的案例:

var obj1 = new Object();
var obj2 = obj1;

obj1.name = "test";
alert(obj2.name);// test

就是因為引用型別的值在使用時,實際上起到的是指標的作用,最終都會指到同一個地址上,所以,值會出現同步的情況.
不過可以通過下面的一個小例子函式複製
,說明了物件中的基本型別不受引用型別的影響,可以檢視之前對於jquery和zepto的擴充套件方法extend的解析來更好的瞭解值複製的內容.

而傳遞引數時,都是按值傳遞的.
我們知道,函式中的引數,傳遞時,實際上會當做arguments物件中的一個元素.

var obj = {};
function func1(obj){
    obj.name = "test"
}
func1(obj);
console.log(obj.name); // test

而如果

function func1(obj){
    obj = {
        name:"test"
    }
}

則obj.name就為Undefined了,是因為這裡的傳參,對於引用型別來說,實際上傳參時,傳遞的是儲存空間的地址的副本,如果修改了該副本的指向,那麼區域性的變數自然就不會影響外部的變量了.

http協議

首先複習下OSI七層協議,從上到下:

  1. 應用層,http,ftp
  2. 表示層,資料加密和定義
  3. 會話層,維持資料的傳輸,或者就是資料體
  4. 傳輸層,tcp
  5. 網路層,常見裝置,路由器,ip
  6. 資料鏈路層,又稱網路介面層,最常見裝置,網絡卡
  7. 物理層:物理層為裝置之間的資料通訊提供傳輸媒體及互連裝置,為資料傳輸提供可靠的環境。物理裝置

而http作為應用層中的其中一種協議,即超文字傳輸協議(Hypertext transfer protocol)。是一種詳細規定了瀏覽器和全球資訊網(WWW = World Wide Web)伺服器之間互相通訊的規則,通過因特網傳送全球資訊網文件的資料傳送協議。
HTTP是一個應用層協議,由請求和響應構成,是一個標準的客戶端伺服器模型。HTTP是一個無狀態的協議。

在Internet中所有的傳輸都是通過TCP/IP進行的。HTTP協議作為TCP/IP模型中應用層的協議也不例外。HTTP協議通常承載於TCP協議之上,有時也承載於TLS或SSL協議層之上,這個時候,就成了我們常說的HTTPS。

http預設的埠號為80,https預設的埠號為443

特點

HTTP協議永遠都是客戶端發起請求,伺服器回送響應。這樣就限制了使用HTTP協議,無法實現在客戶端沒有發起請求的時候,伺服器將訊息推送給客戶端。

無狀態協議:

協議的狀態是指下一次傳輸可以“記住”這次傳輸資訊的能力。
http是不會為了下一次連線而維護這次連線所傳輸的資訊,為了保證伺服器記憶體。
比如客戶獲得一張網頁之後關閉瀏覽器,然後再一次啟動瀏覽器,再登陸該網站,但是伺服器並不知道客戶關閉了一次瀏覽器。

HTTP是一個無狀態的面向連線的協議,無狀態不代表HTTP不能保持TCP連線
從HTTP/1.1起,預設都開啟了Keep-Alive,保持連線特性,簡單地說,當一個網頁開啟完成後,客戶端和伺服器之間用於傳輸HTTP資料的TCP連線不會關閉,如果客戶端再次訪問這個伺服器上的網頁,會繼續使用這一條已經建立的連線。
Keep-Alive不會永久保持連線,它有一個保持時間,可以在不同的伺服器軟體(如Apache)中設定這個時間。

解決無狀態協議:通過cookie,通過session

Cookie和Session有以下明顯的不同點:
1)Cookie將狀態儲存在客戶端,Session將狀態儲存在伺服器端;
2)Cookies是伺服器在本地機器上儲存的小段文字並隨每一個請求傳送至同一個伺服器。Cookie最早在RFC2109中實現,後續RFC2965做了增強。網路伺服器用HTTP頭向客戶端傳送cookies,在客戶終端,瀏覽器解析這些cookies並將它們儲存為一個本地檔案,它會自動將同一伺服器的任何請求縛上這些cookies。Session並沒有在HTTP的協議中定義;
3)Session是針對每一個使用者的,變數的值儲存在伺服器上,用一個sessionID來區分是哪個使用者session變數,這個值是通過使用者的瀏覽器在訪問的時候返回給伺服器,當客戶禁用cookie時,這個值也可能設定為由get來返回給伺服器;
4)就安全性來說:當你訪問一個使用session 的站點,同時在自己機子上建立一個cookie,建議在伺服器端的SESSION機制更安全些。因為它不會任意讀取客戶儲存的資訊。

工作流程

一次HTTP操作稱為一個事務,其工作過程可分為四步:
1)首先客戶機與伺服器需要建立連線。只要單擊某個超級連結,HTTP的工作開始。
2)建立連線後,客戶機發送一個請求給伺服器,請求方式的格式為:統一資源識別符號(URL)、協議版本號,後邊是MIME資訊包括請求修飾符、客戶機資訊和可能的內容。
3)伺服器接到請求後,給予相應的響應資訊,其格式為一個狀態行,包括資訊的協議版本號、一個成功或錯誤的程式碼,後邊是MIME資訊包括伺服器資訊、實體資訊和可能的內容。
4)客戶端接收伺服器所返回的資訊通過瀏覽器顯示在使用者的顯示屏上,然後客戶機與伺服器斷開連線。
如果在以上過程中的某一步出現錯誤,那麼產生錯誤的資訊將返回到客戶端,有顯示屏輸出。對於使用者來說,這些過程是由HTTP自己完成的,使用者只要用滑鼠點選,等待資訊顯示就可以了。

https

簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL.

javaScript的全域性函式

通常意義上來說,全域性物件在JavaScript中,屬於不存在的物件:所有在全域性作用域中定義的屬性和方法,最終都是Global物件的屬性;

uri(Uniform Rescource Identifiers)編碼

有encodeURI(),encodeURIComponent()方法對於URI進行編碼,它們用特殊的UTF-8編碼替換所有無效的字元,從而讓瀏覽器能夠接受和理解.

  • encodeURI()不會對本身屬於URI的特殊字元進行編碼,例如冒號,正斜槓,問號和井字號進行編碼.
  • encodeURIComponent()則對整個url進行編碼,任何非標準字元都會產生新的符號.

與之相對的是decodeURI()和decodeURIComponent()

eval()

用來解析傳入的JavaScript字串,相當於一個解析器,可以用來程式碼注入.

Global的所有屬性

屬性說明屬性說明
undefined特殊值undefinedDate建構函式Date
NaN特殊值NaNRegExp建構函式RegExp
Infinity特殊值InfinityError建構函式Error
Object建構函式ObjectEvalError建構函式EvalError
Array建構函式ArrayRangeError建構函式RangeError
Function建構函式FunctionReferenceError建構函式ReferenceError
Boolean建構函式BooleanSyntaxError建構函式SyntaxError
String建構函式StringTypeError建構函式TypeError
Number建構函式NumberURIError建構函式URIError

Math

還包括一個公共物件Math,用來存放數學公式和資訊.

Window

因為Global都是被當做window物件的一部分加以實現的,所以,在全域性作用域中宣告的所有變數和函式,就都成為了window物件的屬性.

惰性載入函式

針對於每一次載入頁面時,可能存在的大量巢狀或者重複判斷,並且該函式使用次數不高,使用惰性載入方法,避免記憶體的不必要消耗.

在函式被呼叫時再處理函式

function demo(){
    if(typeof str != undefined){
        if(typeof str == "string"){
            demo = function(){
                return 1;
            }
        }else if(typeof str == "num"){
            demo = function(){
                return 2;
            }
        }
    }else {
        demo = function(){
            throw new Error("沒有引數");
        }
    };
    return demo;
}

這樣,只要該函式,通過判斷之後,下次再使用該函式,就直接不用再通過判斷了.

在宣告函式時就指定適當的函式

其實相當於,在開始載入程式碼時,就自執行一遍函式,直接賦值給函式,實際上,建立個匿名自執行函式就好了.

var demo = (function(){
    return function(){};
})();

for in 與 for of 區別

簡單總結就是,for in遍歷的是陣列的索引(即鍵名),而for of遍歷的是陣列元素值。

for-in總是得到物件的key或陣列、字串的下標。

for-of總是得到物件的value或陣列、字串的值,另外還可以用於遍歷Map和Set。

call,apply,bind

唯一區別是apply接受的是陣列引數,call接受的是連續引數。
bind是以call的形式傳參的,但是bind與call、apply最大的區別就是bind繫結this指向並傳參後仍然為一個函式,並沒有去呼叫,而call與apply是直接呼叫函式
不要忽略其最基本的呼叫函式的作用

手動實現一個bind

// 第三版 實現new呼叫
Function.prototype.bindFn = function bind(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' must be a function');
    }
    // 儲存呼叫bind的函式本身
    var self = this;
    // 去除thisArg的其他引數 轉成陣列
    var args = [].slice.call(arguments, 1);
    var bound = function(){
        // bind返回的函式 的引數轉成陣列
        var boundArgs = [].slice.call(arguments);
        var finalArgs = args.concat(boundArgs);
        // new 呼叫時,其實this instanceof bound判斷也不是很準確。es6 new.target就是解決這一問題的。
        if(this instanceof bound){
            // 這裡是實現上文描述的 new 的第 1, 2, 4 步
            // 1.建立一個全新的物件
            // 2.並且執行[[Prototype]]連結
            // 4.通過`new`建立的每個物件將最終被`[[Prototype]]`連結到這個函式的`prototype`物件上。
            // self可能是ES6的箭頭函式,沒有prototype,所以就沒必要再指向做prototype操作。
            if(self.prototype){
                // ES5 提供的方案 Object.create()
                // bound.prototype = Object.create(self.prototype);
                // 但 既然是模擬ES5的bind,那瀏覽器也基本沒有實現Object.create()
                // 所以採用 MDN ployfill方案 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
                function Empty(){}
                Empty.prototype = self.prototype;
                bound.prototype = new Empty();
            }
            // 這裡是實現上文描述的 new 的第 3 步
            // 3.生成的新物件會繫結到函式呼叫的`this`。
            var result = self.apply(this, finalArgs);
            // 這裡是實現上文描述的 new 的第 5 步
            // 5.如果函式沒有返回物件型別`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),
            // 那麼`new`表示式中的函式呼叫會自動返回這個新的物件。
            var isObject = typeof result === 'object' && result !== null;
            var isFunction = typeof result === 'function';
            if(isObject || isFunction){
                return result;
            }
            return this;
        }
        else{
            // apply修改this指向,把兩個函式的引數合併傳給self函式,並執行self函式,返回執行結果
            return self.apply(thisArg, finalArgs);
        }
    };
    return bound;
}

手動實現一個call和apply

// 最終版版 刪除註釋版,詳細註釋看文章
// 瀏覽器環境 非嚴格模式
function getGlobalObject(){
    return this;
}
function generateFunctionCode(argsArrayLength){
    var code = 'return arguments[0][arguments[1]](';
    for(var i = 0; i < argsArrayLength; i++){
        if(i > 0){
            code += ',';
        }
        code += 'arguments[2][' + i + ']';
    }
    code += ')';
    return code;
}
Function.prototype.applyFn = function apply(thisArg, argsArray){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' is not a function');
    }
    if(typeof argsArray === 'undefined' || argsArray === null){
        argsArray = [];
    }
    if(argsArray !== new Object(argsArray)){
        throw new TypeError('CreateListFromArrayLike called on non-object');
    }
    if(typeof thisArg === 'undefined' || thisArg === null){
        thisArg = getGlobalObject();
    }
    thisArg = new Object(thisArg);
    var __fn = '__' + new Date().getTime();
    var originalVal = thisArg[__fn];
    var hasOriginalVal = thisArg.hasOwnProperty(__fn);
    thisArg[__fn] = this;
    var code = generateFunctionCode(argsArray.length);
    var result = (new Function(code))(thisArg, __fn, argsArray);
    delete thisArg[__fn];
    if(hasOriginalVal){
        thisArg[__fn] = originalVal;
    }
    return result;
};
Function.prototype.callFn = function call(thisArg){
    var argsArray = [];
    var argumentsLength = arguments.length;
    for(var i = 0; i < argumentsLength - 1; i++){
        argsArray[i] = arguments[i + 1];
    }
    return this.applyFn(thisArg, argsArray);
}

undefined和void 0

js裡所有的void + 表示式,都會返回undefined,而undefined是可以被當成物件來重寫的.所以使用void + 表示式就可以避免這個問題

web狀態碼

  • 2xx-成功,一切都很好
  • 4xx-客戶端錯誤-如果客戶端發生錯誤(例如客戶端傳送無效請求或者未被授權)
  • 5xx-伺服器錯誤-如果伺服器發生錯誤(例如,嘗試處理請求時錯誤)
2xx-成功 3xx-重定向 4xx-客戶端錯誤 5xx-伺服器錯誤
200成功 301永久重定向 400錯誤請求 500內部伺服器錯誤
201建立 304資源未修改 401未授權 503由於超載或系統維護,伺服器暫時的無法處理客戶端的請求
202已接受,但未處理完成 403禁止(伺服器理解請求客戶端的請求,但是拒絕執行此請求)
 404未找到 505伺服器不支援請求的HTTP協議的版本,無法完成處理

原型及原型鏈

顯示原型和隱式原型

顯示原型是__proto__:
每一個函式在建立之後都會擁有一個名為prototype的屬性,這個屬性指向函式的原型物件。
通過Function.prototype.bind方法構造出來的函式是個例外,它沒有prototype屬性。

隱式原型是prototype:
JavaScript中任意物件都有一個內建屬性[[prototype]],在ES5之前沒有標準的方法訪問這個內建屬性,但是大多數瀏覽器都支援通過__proto__來訪問。ES5中有了對於這個內建屬性標準的Get方法Object.getPrototypeOf().
Object.prototype 這個物件是個例外,它的__proto__值為null

字面量原型及原型鏈

JS 可通過字面量構造物件。為了實現繼承,物件裡面有個__proto__屬性可以指向該物件的父物件。這個父物件就是所謂的“原型”。

建構函式原型及原型鏈

但是為了遷就 C++、Java、C# 程式設計師,讓 JavaScript 可以像 Java 那樣 new (構造)出一個物件出來,於是這裡做了一個變通,也提供了建構函式。

但是這裡面存在兩個問題:在物件裡面定義方法,這樣每建立一個物件都會一個sayHello()函式,這樣來說顯得物件臃腫,浪費資源;同時每個物件各自保有自己的屬性和函式的副本,無法做到屬性和方法共享。因此,這裡有一個更加高效的辦法就是把物件共享的屬性和方法可以放到 Student.prototype 這個物件當中
我們應該還知道上面的建構函式Student()物件(JS 中函式也是物件)會建立一個 prototype 物件(Student.prototype),而 new 出來的例項物件例如 andy 和 lisa 是沒有這個 prototype 物件,但是他會有個 proto 屬性(__proto__)指向這個建構函式物件的 prototype 物件,從而構成原型鏈。例項物件其實是通過原型物件與建構函式取得聯絡的

總結

JS 在建立物件(不論是普通物件還是函式物件)的時候,都有一個叫做 ``proto_` 的內建屬性,用於指向建立它的函式物件的原型物件 prototype
原型和原型鏈是 JS 實現繼承的一種模型
原型鏈是靠 proto 形成的,而不是 prototype
所有的原型物件都有 constructor 屬性,該屬性對應建立所有指向該原型的例項建構函式
函式物件和原型物件通過 prototype 和 constructor 屬性進行相互關聯

建立物件的多種方式

物件字面量

var o = {};

通過工廠函式建立相同型別的物件

function thing() {
  return {
    x: 42,
    y: 3.14,
    f: function() {},
    g: function() {}
  };
}

var o = thing();

object.create()建立物件

實現繼承的多種方式和優缺點

借用建構函式繼承

function Parent0(){
    this.name = "parent0";
    this.colors = ["red","blue","yellow"];
}
function Child0(){
    Parent0.call( this ); // 或apply
    this.type = "child0";
}

但是通過這種方式,父類原型上的東西是沒法繼承的
缺點:Child1無法繼承Parent1的原型物件,並沒有真正的實現繼承(部分繼承)

原型鏈式繼承(借用原型鏈實現繼承)

function Parent1(){
    this.name = "parent1";
    this.colors = ["red","blue","yellow"];
}
function Child1(){
    this.name = "child1";
}
Child1.prototype = new Parent1();

但是,這種方式仍有缺點,原型鏈上中的原型物件是共用的

組合式繼承

function Parent2(){
    this.name = "parent2";
    this.colors = ["red","blue","yellow"];
}
function Child2(){
    Parent2.call(this);
    this.type = "child2";
}
Child2.prototype = new Parent2()

但這種方式仍有缺點。父類的建構函式被執行了兩次,第一次是Child2.prototype = new Parent2(),第二次是在例項化的時候,這是沒有必要的。

組合式繼承優化

function Parent4(){
    this.name = "parent4";
    this.colors = ["red","blue","yellow"];
}
Parent4.prototype.sex = "男";
Parent4.prototype.say = function(){console.log("Oh, My God!")}
function Child4(){
    Parent4.call(this);
    this.type = "child4";
}
Child4.prototype = Object.create(Parent4.prototype);
Child4.prototype.constructor = Child4;

寄生式繼承

function _inherits(Child, Parent){
// Object.create
Child.prototype = Object.create(Parent.prototype);
// proto
// Child.prototype.proto = Parent.prototype;
Child.prototype.constructor = Child;
// ES6
// Object.setPrototypeOf(Child, Parent);
// proto
Child.proto = Parent;
}

ES6中繼承

Class 可以通過extends關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。

class Parent {
}
class Child1 extends Parent {
    constructor(x, y, colors) {
         super(x, y); // 呼叫父類的constructor(x, y)
         this.colors = colors;
    }
    toString() {
         return this.colors + ' ' + super.toString(); // 呼叫父類的toString()
    }
}

new 一個物件具體做了什麼

var cat = new Animal("cat");
var obj = {};
var args = Array.prototype.slice.call(arguments,1);
 obj.__proto__ = Animal.prototype;
 obj.__proto__.constructor = Animal;
 var result = Animal.apply(obj,args);
 return typeof result === 'obj'? result : obj;

(1)建立一個空物件obj;
(2)把obj的__proto__ 指向Animal的原型物件prototype,此時便建立了obj物件的原型鏈:obj->Animal.prototype->Object.prototype->null;
(3)在obj物件的執行空間呼叫Animal函式並傳遞引數“cat”。 相當於var result = obj.Animal("cat")。
當這句執行完之後,obj便產生了屬性name並賦值為"cat"。【關於JS中call的用法請閱讀:JS的call和apply】
(4)考察第3步返回的返回值,如果無返回值或者返回一個非物件值,則將obj返回作為新物件;否則會將返回值作為新物件返回。

XMLHttpRequest

ajax原生物件

function ajax(options){
    var default = {
        "type": "GET",
        "dataType": "string",
        "data":{},
        "url": '',
        "async": true,
        "success":function(){},
        "error":function(){}
    };

    this.opts = extend(true,default,options);

    if(_this.opts.dataType == "jsonp"){
        ajaxJSONP(opts);
    }

    var xhr = new XMLHttpRequest();

    xhr.open(this.opts.type,this.opts.url,this.opts.async);

    xhr.send(null);

    xhr.onreadystatechange=function(e){
        if(xhr.readystate === 4){
            if(xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                success.call(this,e.response);
            }else{
                error.call(this,e.response);
            }
        }
    };
}

//模擬form表單提交:

xhr.open("post","example.json",true);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
xhr.send("key=0&key1=1}");

匿名函式

(function(){})();

JS的宿主物件和原生物件

所有非本地物件都是宿主物件(host object),即由 ECMAScript 實現的宿主環境提供的物件.非內建物件,即為宿主物件.

ECMA-262 只定義了兩個內建物件,即 Global 和 Math

document load和document DOMContentLoaded兩個事件的區別

他們的區別是,觸發的時機不一樣,先觸發DOMContentLoaded事件,後觸發load事件。

DOM文件載入的步驟為

  • 解析HTML結構。
  • 載入外部指令碼和樣式表文件。
  • 解析並執行指令碼程式碼。
  • DOM樹構建完成。//DOMContentLoaded
  • 載入圖片等外部檔案。
  • 頁面載入完畢。//load

在第4步,會觸發DOMContentLoaded事件。在第6步,觸發load事件。

js的過載和多型

多型: 同一操作作用於不同的物件,可以有不同的解釋,產生不同的執行結果。
過載: 一組具有相同名字、不同引數列表的函式(方法)。

dom0級和dom2級

dom0級事件:
實際上,DOM0級標準是不存在的,所謂的DOM0級是DOM歷史座標中的一個參照點而已,具體說呢,DOM0級指的是IE4和Netscape 4.0這些瀏覽器最初支援的DHTML..大概2000年的時候爭論過DOM0的問題,最後結論大概是,沒有官方形成此標準.。

DOM1級(DOM Level 1)於1998年10月成為W3C的推薦標準。DOM1級由兩個模組組成:DOM核心(DOM Core)和DOM HTML。其中,DOM核心規定的是如何對映基於XML的文件結構,以便簡化對文件中任意部分的訪問和操作。DOM HTML模組則在DOM核心的基礎上加以擴充套件,添加了針對HTML的物件和方法。

dom2級:
新增了更多的節點操作以及互動模組

DOM3級則進一步擴充套件了DOM,引入了以統一方式載入和儲存文件的方法–在DOM載入和儲存(DOM Load and Save)模組中定義;新增了驗證文件的方法–在DOM驗證(DOM Validation)模組中定義。DOM3級也對DOM核心進行了擴充套件,開始支援XML 1.0規範,涉及XML Infoset、XPath和XML Base。

給定一個元素獲取它相對於檢視視窗的座標

element.getBoundingClientRect():返回元素的大小以及相對於瀏覽器可視視窗的位置

或者通過offsetWidth;offsetHeight;一層層向上計算與瀏覽器視窗的偏移值.

圖片懶載入

將頁面裡所有img屬性src屬性用data-xx代替,當頁面滾動直至此圖片出現在可視區域時,用js取到該圖片的data-xx的值賦給src。

呈現形式

【1】延時載入,使用setTimeout或setInterval進行載入延遲,如果使用者在載入前就離開,自然就不會進行載入。
【2】條件載入,符合某些條件或者觸發了某些條件才開始非同步載入。
【3】可視區域載入,僅僅載入使用者可以看到的區域,這個主要監控滾動條來實現,一般距離使用者看到的底邊很近的時候開始載入,這樣能保證使用者下拉時圖片正好接上,不會有太長時間的停頓。

window.onscroll = function(){
    loadImg(aImages);
};
function loadImg(arr){
    for( var i = 0,len = arr.length; i < len; i++){
        if(arr[i].getBoundingClientRect().top < document.documentElement.clientHeight && !arr[i].isLoad){
            arr[i].isLoad = true;
            arr[i].style.cssText = "transition: ''; opacity: 0;"
            if(arr[i].dataset){
                aftLoadImg(arr[i],arr[i].dataset.original);    
            }else{
                aftLoadImg(arr[i],arr[i].getAttribute("data-original"));
            }
            (function(i){
                setTimeout(function(){
                    arr[i].style.cssText = "transition: 1s; opacity: 1;"
                },16)
            })(i);
        }
    }
}

字串的型別有哪些方法

  • charCodeAt方法返回一個整數,代表指定位置字元的Unicode編碼。
  • fromCharCode方法從一些Unicode字串中返回一個字串。
  • slice方法返回字串的片段(start,[end])
  • substring方法返回位於String物件中指定位置的子字串.
  • substr方法返回一個從指定位置開始的指定長度的子字串
  • indexOf方法放回String物件內第一次出現子字串位置。如果沒有找到子字串,則返回-1
  • 將一個字串分割為子字串,然後將結果作為字串陣列返回,split

js的正則表示式

var reg=/hello/    或者  var reg=new RegExp("hello")

深拷貝

extend

通用的事件監聽

var EventUtil = {
    addHandler: function(element,type,handler){
        if(element.addEventListener){
            element.addEventListener(type,handler,false);
        } else if(element.attachEvent){
            element.attachEvent("on"+type,handler);
        } else {
            element["on"+type] = handler;
        }
    },
    removeHandler: function(element,type,handler){
        if(element.removeEventListener){
            element.removeEventListener(type,handler,false);
        }else if(element.detachEvent){
            element.detachEvent("on"+type,handler);
        }else {
            element["on"+type] = null
        }
    }
}

web端cookie的設定和獲取

獲取:document.cookie

設定:document.cookie="name="+username;

刪除 cookie 非常簡單。您只需要設定 expires 引數為以前的時間即可,如下所示,設定為 Thu, 01 Jan 1970 00:00:00 GMT:
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT";

promise和setTimeout執行順序的疑惑

從規範上來講,setTimeout有一個4ms的最短時間,也就是說不管你設定多少,反正最少都要間隔4ms才執行裡面的回撥(當然,瀏覽器有沒有遵守這個規範是另外一回事兒)。而Promise的非同步沒有這個問題。

從具體實現上來說,這倆的非同步佇列不一樣,Promise所在的那個非同步佇列優先順序要高一些。

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function executor(resolve) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()

//1,2,3,5,4

Promise.then 是非同步執行的,而建立Promise例項( executor )是同步執行的。
setTimeout 的非同步和 Promise.then 的非同步看起來 “不太一樣” ——至少是不在同一個佇列中。

promise

簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理

Promise物件有以下兩個特點。

(1)物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件添加回調函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

resolved 的 Promise 是在本輪事件迴圈的末尾執行,總是晚於本輪迴圈的同步任務

JavaScript 的事件流模型都有什麼 ???

DOM0級模型:又稱為原始事件模型,在該模型中,事件不會傳播,即沒有事件流的概念。

<input type="button" onclick="fun()">

var btn = document.getElementById('.btn');
btn.onclick = fun;

IE事件模型:

  • 事件處理階段(target phase)。事件到達目標元素, 觸發目標元素的監聽函式。
  • 事件冒泡階段(bubbling phase)。事件從目標元素冒泡到document, 依次檢查經過的節點是否綁定了事件監聽函式,如果有則執行。

DOM2級模型:屬於W3C標準模型,現代瀏覽器(除IE6-8之外的瀏覽器)都支援該模型。在該事件模型中,一次事件共有三個過程:

  • 事件捕獲階段(capturing phase)。事件從document一直向下傳播到目標元素, 依次檢查經過的節點是否綁定了事件監聽函式,如果有則執行。
  • 事件處理階段(target phase)。事件到達目標元素, 觸發目標元素的監聽函式。
  • 事件冒泡階段(bubbling phase)。事件從目標元素冒泡到document, 依次檢查經過的節點是否綁定了事件監聽函式,如果有則執行。

儲存了關於瀏覽器的相關資訊

屬性 說明
appCodeName 返回瀏覽器的程式碼名
appName 返回瀏覽器的名稱
appVersion 返回瀏覽器的平臺和版本資訊
cookieEnabled 返回指明瀏覽器中是否啟用 cookie 的布林值
platform 返回執行瀏覽器的作業系統平臺
userAgent 返回由客戶機發送伺服器的user-agent 頭部的值

等相關資訊

location物件

提供了與當前視窗中載入的文件有關的資訊,還提供了導航的功能,location既是window物件的屬性,又是document物件的屬性,而且location還將URL解析成了獨立的片段,從而直接使用.

屬性 描述
hash 設定或返回從井號 (#) 開始的 URL(錨)。
host 設定或返回主機名和當前 URL 的埠號。
hostname 設定或返回當前 URL 的主機名。
href 設定或返回完整的 URL。
pathname 設定或返回當前 URL 的路徑部分。
port 設定或返回當前 URL 的埠號。
protocol 設定或返回當前 URL 的協議。
search 設定或返回從問號 (?) 開始的 URL(查詢部分)。

history物件

儲存著歷史記錄

方法(屬性) 描述
length 返回瀏覽器歷史列表中的 URL 數量。
back() 載入 history 列表中的前一個 URL。
forward() 載入 history 列表中的下一個 URL。
go() 載入 history 列表中的某個具體頁面。

js的垃圾回收機制

主要方法有標記清除和引用計數,引用計數會導致迴圈引用的問題

一旦資料不再有用,最好將其值設定為null來釋放引用--解除引用

js的記憶體洩漏

  1. 全域性變數

  2. 被遺忘的計時器或回撥

  3. 閉包

  4. 超出DOM引用

DOM事件中target和currentTarget的區別

target在事件流的目標階段;currentTarget在事件流的捕獲,目標及冒泡階段。只有當事件流處在目標階段的時候,兩個的指向才是一樣的, 而當處於捕獲和冒泡階段的時候,target指向被單擊的物件而currentTarget指向當前事件活動的物件(註冊該事件的物件)(一般為父級)。this指向永遠和currentTarget指向一致(只考慮this的普通函式呼叫)。

typeof 和 instanceof 區別

typeof
typeof 是一個一元運算,放在一個運算數之前,運算數可以是任意型別。

instanceof
用於判斷一個變數是否是某個物件的例項

instanceof原理

檢視物件B的prototype指向的物件是否在物件A的[[prototype]]鏈上。如果在,則返回true,如果不在則返回false。不過有一個特殊的情況,當物件B的prototype為null將會報錯(類似於空指標異常)

function instance_of(L, R) {//L 表示左表示式,R 表示右表示式
 var O = R.prototype;// 取 R 的顯示原型
 L = L.__proto__;// 取 L 的隱式原型
 while (true) {
   if (L === null)
     return false;
   if (O === L)// 這裡重點:當 O 嚴格等於 L 時,返回 true 
     return true;
   L = L.__proto__;
 }

js動畫和css動畫的區別

Chromium專案裡,渲染執行緒分為main thread和compositor thread。
如果CSS動畫只是改變transforms和opacity,這時整個CSS動畫得以在compositor thread完成(而JS動畫則會在main thread執行,然後觸發compositor進行下一步操作)
在JS執行一些昂貴的任務時,main thread繁忙,CSS動畫由於使用了compositor thread可以保持流暢

現今CSS動畫和JS動畫主要的不同點是

  • 功能涵蓋面,JS比CSS3大
    • 定義動畫過程的@keyframes不支援遞迴定義,如果有多種類似的動畫過程,需要調節多個引數來生成的話,將會有很大的冗餘(比如jQuery Mobile的動畫方案),而JS則天然可以以一套函式實現多個不同的動畫過程
    • 時間尺度上,@keyframes的動畫粒度粗,而JS的動畫粒度控制可以很細
    • CSS3動畫裡被支援的時間函式非常少,不夠靈活
    • 以現有的介面,CSS3動畫無法做到支援兩個以上的狀態轉化
  • 實現/重構難度不一,CSS3比JS更簡單,效能調優方向固定
  • 對於幀速表現不好的低版本瀏覽器,CSS3可以做到自然降級,而JS則需要撰寫額外程式碼
  • CSS動畫有天然事件支援(TransitionEnd、AnimationEnd,但是它們都需要針對瀏覽器加字首),JS則需要自己寫事件
  • CSS3有相容性問題,而JS大多時候沒有相容性問題

js處理異常

try/catch

Throw

js設計模式

  • 單例模式
  • 建構函式模式
  • 建造者模式:表相即是回撥,也就是說獲取資料以後如何顯示和處理取決於回撥函式
  • 工廠模式:需要依賴具體環境建立不同例項;處理大量具有相同屬性的小物件
  • 裝飾者模式:裝飾者是一種實現繼承的替代方案,動態給函式新增功能,且功能使用單獨的function來實現
  • 外觀模式:通過它封裝一些介面用於相容多瀏覽器,可以讓我們間接呼叫子系統,從而避免因直接訪問子系統而產生不必要的錯誤
  • 代理模式:為其他物件提供一種代理以控制對這個物件的訪問
  • 觀察者模式(釋出訂閱模式)
  • 策略模式
  • 命令模式

js實現輪播

首先確認需求,這是一個什麼樣的輪播器,如果只涉及到圖片的輪播,則可以預設處理圖片類模板,同時,如果是懶載入的,則需要事先考慮好載入方式,設定data資料來源,以及是否自動輪播,延時多少,是否有前進後退,是否有指代器(小白條等).

這裡的動畫效果,可以使用transition,也可以使用opacity顯示隱藏就好.

websocket的工作原理和機制

WebSocket 協議是一個獨立的基於TCP的協議。和HTTP唯一的關係是它的握手被HTTP伺服器識別為Upgrade 請求。WebSocket協議讓瀏覽器和web伺服器之間實時資料傳輸成為可能。也為這種標準方式提供了可能,就是服務端在未經客戶端請求時給瀏覽器傳送內容,並且當連線沒有關閉,資訊可以來回傳遞。用這種方式,在瀏覽器和伺服器之間可以發生一種雙向的會話

ajax輪詢或者長輪詢

手指點選可以觸控的螢幕時,是什麼事件

touch,touchstart,touchend

什麼是函式柯里化

在電腦科學中,柯里化(Currying)是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數且返回結果的新函式的技術

var currying = function (fn) {
    var _args = [];
    return function () {
        if (arguments.length === 0) {
            return fn.apply(this, _args);
        }
        Array.prototype.push.apply(_args, [].slice.call(arguments));
        return arguments.callee;
    }
};

Function.prototype.bind 方法也是柯里化應用

Object.prototype.bind = function(context) {
    var _this = this;
    var args = [].slice.call(arguments, 1);

    return function() {
        return _this.apply(context, args)
    }
}

接收單一引數,將更多的引數通過回撥函式來搞定?
返回一個新函式,用於處理所有的想要傳入的引數;
需要利用call/apply與arguments物件收集引數;
返回的這個函式正是用來處理收集起來的引數。

實現一個add方法,使計算結果能夠滿足如下預期:

add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
    // 第一次執行時,定義一個數組專門用來儲存所有的引數
    var _args = [].slice.call(arguments);

    // 在內部宣告一個函式,利用閉包的特性儲存_args並收集所有的引數值
    var adder = function () {
        var _adder = function() {
            // [].push.apply(_args, [].slice.call(arguments));
            _args.push(...arguments);
            return _adder;
        };

        // 利用隱式轉換的特性,當最後執行時隱式轉換,並計算最終的值返回
        _adder.toString = function () {
            return _args.reduce(function (a, b) {
                return a + b;
            });
        }

        return _adder;
    }
    // return adder.apply(null, _args);
    return adder(..._args);
}

var a = add(1)(2)(3)(4);   // f 10
var b = add(1, 2, 3, 4);   // f 10
var c = add(1, 2)(3, 4);   // f 10
var d = add(1, 2, 3)(4);   // f 10

// 可以利用隱式轉換的特性參與計算
console.log(a + 10); // 20
console.log(b + 20); // 30
console.log(c + 30); // 40
console.log(d + 40); // 50

// 也可以繼續傳入引數,得到的結果再次利用隱式轉換參與計算
console.log(a(10) + 100);  // 120
console.log(b(10) + 100);  // 120
console.log(c(10) + 100);  // 120
console.log(d(10) + 100);  // 120

當我們沒有重新定義toString與valueOf時,函式的隱式轉換會呼叫預設的toString方法,它會將函式的定義內容作為字串返回。而當我們主動定義了toString/vauleOf方法時,那麼隱式轉換的返回結果則由我們自己控制了。其中valueOf會比toString後執行

利用call/apply封陣列的map方法

map(): 對陣列中的每一項執行給定函式,返回每次函式呼叫的結果組成的陣列。

Array.prototype._map = function(fn, context) {
    var temp = [];
    if(typeof fn == 'function') {
        var k = 0;
        var len = this.length;
        // 封裝for迴圈過程
        for(; k < len; k++) {
            // 將每一項的運算操作丟進fn裡,利用call方法指定fn的this指向與具體引數
            temp.push(fn.call(context, this[k], k, this))
        }
    } else {
        console.error('TypeError: '+ fn +' is not a function.');
    }

    // 返回每一項運算結果組成的新陣列
    return temp;
}

var newArr = [1, 2, 3, 4]._map(function(item) {
    return item + 1;
})
// [2, 3, 4, 5]

JS程式碼除錯

pc:firebug,chrome控制檯
跨平臺:fiddler,weinre,Wireshark
手機:chrome和safari自帶的開發者工具

函式上下文

每次當控制器轉到可執行程式碼的時候,就會進入一個執行上下文。執行上下文可以理解為當前程式碼的執行環境

JavaScript引擎會以棧的方式來處理它們,這個棧,我們稱其為函式呼叫棧(call stack)。棧底永遠都是全域性上下文,而棧頂就是當前正在執行的上下文。

比如

var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}

changeColor();

這段程式碼,可以發現,雖然是順序執行的,但是執行時,每個區域性作用域內會執行完所有程式碼,再繼續執行上個作用域的可執行程式碼

一個執行上下文的生命週期可以分為兩個階段。

  1. 建立階段
    在這個階段中,執行上下文會分別建立變數物件,建立作用域鏈,以及確定this的指向。

  2. 程式碼執行階段
    建立完成之後,就會開始執行程式碼,這個時候,會完成變數賦值,函式引用,以及執行其他程式碼。

  3. 執行完畢,等待被回收

作用域

在JavaScript中,我們可以將作用域定義為一套規則,這套規則用來管理引擎如何在當前作用域以及巢狀的子作用域中根據識別符號名稱進行變數查詢。

JavaScript程式碼的整個執行過程,分為兩個階段,程式碼編譯階段與程式碼執行階段。編譯階段由編譯器完成,將程式碼翻譯成可執行程式碼,這個階段作用域規則會確定。執行階段由引擎完成,主要任務是執行可執行程式碼,執行上下文在這個階段建立。

因此作用域和執行上下文不是一種東西

作用域鏈

當函式呼叫棧成型時,那麼作用域鏈實際上已經建立,其可以看做是規定了每個作用域內如何訪問變數,是一種邏輯上的鏈關係,可以看做上層預設引用下層的所有變數的地址和定義

作用域鏈,是由當前環境與上層環境的一系列變數物件組成,它保證了當前執行環境對符合訪問許可權的變數和函式的有序訪問。

我們可以直接用一個數組來表示作用域鏈,陣列的第一項scopeChain[0]為作用域鏈的最前端,而陣列的最後一項,為作用域鏈的最末端,所有的最末端都為全域性變數物件。

再次理解閉包

閉包是一種特殊的物件。

閉包是一個函式和函式所宣告的詞法環境的結合.

它由兩部分組成。執行上下文(代號A),以及在該執行上下文中建立的函式(代號B)。

當B執行時,如果訪問了A中變數物件中的值,那麼閉包就會產生。

var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() { 
        console.log(a);
    }
    fn = innnerFoo; // 將 innnerFoo的引用,賦值給全域性變數中的fn
}

function bar() {
    fn(); // 此處的保留的innerFoo的引用
}

foo();
bar(); // 2

這裡將內部引用型別變數地址傳遞給全域性變數fn,因此執行完foo()之後,foo不會被回收,因為它的屬性正在被其他值所引用.

閉包是指這樣的作用域(foo),它包含有一個函式(fn1),這個函式(fn1)可以呼叫被這個作用域所封閉的變數(a)、函式、或者閉包等內容。通常我們通過閉包所對應的函式來獲得對閉包的訪問。

所以,通過閉包,我們可以在其他的執行上下文中,訪問到函式的內部變數。

for (var i = 0; i < 5; i++) {
setTimeout(console.log.bind(console,i), 1000 * i)
}

變數物件和活動物件

function foo() { console.log('function foo') }
var foo = 20;

console.log(foo); // 20

// 上慄的執行順序為

// 首先將所有函式宣告放入變數物件中
function foo() { console.log('function foo') }

// 其次將所有變數宣告放入變數物件中,但是因為foo已經存在同名函式,因此此時會跳過undefined的賦值
// 不再執行 var foo = undefined;

// 然後開始執行階段程式碼的執行
console.log(foo); // function foo
foo = 20;
  • 建立arguments物件。檢查當前上下文中的引數,建立該物件下的屬性與屬性值。
  • 檢查當前上下文的函式宣告,也就是使用function關鍵字宣告的函式。在變數物件中以函式名建立一個屬性,屬性值為指向該函式所在記憶體地址的引用。如果函式名的屬性已經存在,那麼該屬性將會被新的引用所覆蓋。
  • 檢查當前上下文中的變數宣告,每找到一個變數宣告,就在變數物件中以變數名建立一個屬性,屬性值為undefined。如果該變數名的屬性已經存在,為了防止同名的函式被修改為undefined,則會直接跳過,原屬性值不會被修改。

變數物件和活動物件只是執行上下文的不同生命週期的叫法而已.

同時,this的指向也是在不同的執行上下文中,各不相同

this的指向

this的指向,是在函式被呼叫的時候確定的,在函式執行過程中,this一旦被確定,就不可更改了

在一個函式上下文中,this由呼叫者提供,由呼叫函式的方式來決定。如果呼叫者函式,被某一個物件所擁有,那麼該函式在呼叫時,內部的this指向該物件。如果函式獨立呼叫,那麼該函式內部的this,則指向undefined。但是在非嚴格模式中,當this指向undefined時,它會被自動指向全域性物件。

將類陣列物件轉換為陣列

function exam(a, b, c, d, e) {

    // 先看看函式的自帶屬性 arguments 什麼是樣子的
    console.log(arguments);

    // 使用call/apply將arguments轉換為陣列, 返回結果為陣列,arguments自身不會改變
    var arg = [].slice.call(arguments);

    console.log(arg);
}

exam(2, 8, 9, 10, 3);

TypeScript

TypeScript中的interface 與 type 有何異同

相同點

  1. 都可以描述一個物件或者函式

    // interface
    interface User {
        name: string
        age: number
    }
    
    interface SetUser {
        (name: string, age: number): void;
    }
    
    // type
    type User = {
        name: string
        age: number
    };
    
    type SetUser = (name: string, age: number)=> void;
    
  2. 都允許拓展(extends):

    interface 和 type 都可以拓展,並且兩者並不是相互獨立的,也就是說 interface 可以 extends type, type 也可以 extends interface 。 雖然效果差不多,但是兩者語法不同。

    // interface extends interface
    interface Name {
        name: string;
    }
    interface User extends Name {
        age: number;
    }
    
    // type extends type
    type Name = {
        name: string;
    }
    type User = Name & { age: number  };
    
    // interface extends type
    type Name = {
        name: string;
    }
    interface User extends Name {
        age: number;
    }
    
    // type extends interface
    interface Name {
        name: string;
    }
    type User = Name & {
        age: number;
    }
    
    

不同點

  1. type 可以而 interface 不行

    type 可以宣告基本類型別名,聯合型別,元組等型別

    // 基本類型別名
    type Name = string
    
    // 聯合型別
    interface Dog {
        wong();
    }
    interface Cat {
        miao();
    }
    
    type Pet = Dog | Cat
    
    // 具體定義陣列每個位置的型別
    type PetList = [Dog, Pet]
    
    

    type 語句中還可以使用 typeof 獲取例項的 型別進行賦值

    // 當你想獲取一個變數的型別時,使用 typeof
    let div = document.createElement('div');
    type B = typeof div
    

    其他操作

    type StringOrNumber = string | number;  
    type Text = string | { text: string };  
    type NameLookup = Dictionary<string, Person>;  
    type Callback<T> = (data: T) => void;  
    type Pair<T> = [T, T];  
    type Coordinates = Pair<number>;  
    type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
    
  2. interface 可以而 type 不行

    interface 能夠宣告合併

    interface User {
        name: string
        age: number
    }
    
    interface User {
        sex: string
    }
    
    /*
    User 介面為 {
        name: string
        age: number
        sex: string
    }
    */
    

如果不清楚什麼時候用interface/type,能用 interface 實現,就用 interface , 如果不能就用 type

React的setState的更新模式

  • setState在生命週期函式和合成函式中都是非同步更新。
  • setState在steTimeout、原生事件和async函式中都是同步更新。每次更新不代表都會觸發render,如果render內容與newState有關聯,則會觸發,否則即便setState多次也不會render
  • 如果newState內容與render有依賴關係,就不建議同步更新,因為每次render都會完整的執行一次批量更新流程(只是dirtyComponets長度為1,stateQueue也只有該元件的newState),呼叫一次diff演算法,這樣會影響React效能。
  • 如果沒有必須同步渲染的理由,不建議使用同步,會影響react渲染效能

連結:https://zhuanlan.zhihu.com/p/82089614

React Hook 和 Vue Hook 對比

其實 React Hook 的限制非常多,比如官方文件中就專門有一個章節介紹它的限制:

不要在迴圈,條件或巢狀函式中呼叫

  1. Hook確保總是在你的 React 函式的最頂層呼叫他們。
  2. 遵守這條規則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被呼叫。這讓 React 能夠在多次的 useState 和 useEffect 呼叫之間保持 hook 狀態的正確。

而 Vue 帶來的不同在於:

  1. 與 React Hooks 相同級別的邏輯組合功能,但有一些重要的區別。 與 React Hook 不同,setup 函式僅被呼叫一次,這在效能上比較佔優。
  2. 對呼叫順序沒什麼要求,每次渲染中不會反覆呼叫 Hook 函式,產生的的 GC 壓力較小。 不必考慮幾乎總是需要 useCallback 的問題,以防止傳遞函式prop給子元件的引用變化,導致無必要的重新渲染。
  3. React Hook 有臭名昭著的閉包陷阱問題(甚至成了一道熱門面試題,omg),如果使用者忘記傳遞正確的依賴項陣列,useEffect 和 useMemo 可能會捕獲過時的變數,這不受此問題的影響。Vue 的自動依賴關係跟蹤確保觀察者和計算值始終正確無誤。
  4. 不得不提一句,React Hook 裡的「依賴」是需要你去手動宣告的,而且官方提供了一個 eslint 外掛,這個外掛雖然大部分時候挺有用的,但是有時候也特別煩人,需要你手動加一行醜陋的註釋去關閉它。

Vue2.0和Vue3.0的差異

Vue2.0

  1. 基於Object.defineProperty,監聽陣列效能有問題,需要重新定義陣列的原型來達到響應式。
  2. Object.defineProperty 無法檢測到物件屬性的新增和刪除。
  3. 由於Vue會在初始化例項時對屬性執行getter/setter轉化,所有屬性必須在data物件上存在才能讓Vue將它轉換為響應式。
  4. 深度監聽需要一次性遞迴,對效能影響比較大。
function defineReactive(target, key, value) {
   //深度監聽
   observer(value);

   Object.defineProperty(target, key, {
     get() {
       return value;
     },
     set(newValue) {
       //深度監聽
       observer(value);
       if (newValue !== value) {
         value = newValue;

         updateView();
       }
     }
   });
 }

 function observer(target) {
   if (typeof target !== "object" || target === null) {
     return target;
   }

   if (Array.isArray(target)) {
     target.__proto__ = arrProto;
   }

   for (let key in target) {
     defineReactive(target, key, target[key]);
   }
 }

 // 重新定義陣列原型
 const oldAddrayProperty = Array.prototype;
 const arrProto = Object.create(oldAddrayProperty);
 ["push", "pop", "shift", "unshift", "spluce"].forEach(
   methodName =>
     (arrProto[methodName] = function() {
       updateView();
       oldAddrayProperty[methodName].call(this, ...arguments);
     })
 );

 // 檢視更新
  function updateView() {
   console.log("檢視更新");
 }

 // 宣告要響應式的物件
 const data = {
   name: "zhangsan",
   age: 20,
   info: {
     address: "北京" // 需要深度監聽
   },
   nums: [10, 20, 30]
 };

 // 執行響應式
 observer(data);

Vue3.0

基於Proxy和Reflect,可以原生監聽陣列,可以監聽物件屬性的新增和刪除。
不需要一次性遍歷data的屬性,可以顯著提高效能。
因為Proxy是ES6新增的屬性,有些瀏覽器還不支援,只能相容到IE11。

const proxyData = new Proxy(data, {
   get(target,key,receive){
     // 只處理本身(非原型)的屬性
     const ownKeys = Reflect.ownKeys(target)
     if(ownKeys.includes(key)){
       console.log('get',key) // 監聽
     }
     const result = Reflect.get(target,key,receive)
     return result
   },
   set(target, key, val, reveive){
     // 重複的資料,不處理
     const oldVal = target[key]
     if(val == oldVal){
       return true
     }
     const result = Reflect.set(target, key, val,reveive)
     console.log('set', key, val)
     return result
   },
   deleteProperty(target, key){
     const result = Reflect.deleteProperty(target,key)
     console.log('delete property', key)
     console.log('result',result)
     return result
   }
 })

  // 宣告要響應式的物件,Proxy會自動代理
 const data = {
   name: "zhangsan",
   age: 20,
   info: {
     address: "北京" // 需要深度監聽
   },
   nums: [10, 20, 30]
 };

amd,cmd,es6 module之間的區別

AMD

AMD一開始是CommonJS規範中的一個草案,全稱是Asynchronous Module Definition,即非同步模組載入機制。後來由該草案的作者以RequireJS實現了AMD規範,所以一般說AMD也是指RequireJS。

RequireJS的基本用法
通過define來定義一個模組,使用require可以匯入定義的模組。

//a.js
//define可以傳入三個引數,分別是字串-模組名、陣列-依賴模組、函式-回撥函式
define(function(){
    return 1;
})

// b.js
//陣列中宣告需要載入的模組,可以是模組名、js檔案路徑
require(['a'], function(a){
    console.log(a);// 1
});

RequireJS的特點

對於依賴的模組,AMD推崇依賴前置,提前執行。也就是說,在define方法裡傳入的依賴模組(陣列),會在一開始就下載並執行。

CMD

CMD是SeaJS在推廣過程中生產的對模組定義的規範,在Web瀏覽器端的模組載入器中,SeaJS與RequireJS並稱,SeaJS作者為阿里的玉伯。

SeaJS的基本用法

//a.js
/*
* define 接受 factory 引數,factory 可以是一個函式,也可以是一個物件或字串,
* factory 為物件、字串時,表示模組的介面就是該物件、字串。
* define 也可以接受兩個以上引數。字串 id 表示模組標識,陣列 deps 是模組依賴.
*/
define(function(require, exports, module) {
  var $ = require('jquery');

  exports.setColor = function() {
    $('body').css('color','#333');
  };
});

//b.js
//陣列中宣告需要載入的模組,可以是模組名、js檔案路徑
seajs.use(['a'], function(a) {
  $('#el').click(a.setColor);
});

SeaJS的特點

對於依賴的模組,CMD推崇依賴就近,延遲執行。也就是說,只有到require時依賴模組才執行。

CommonJS

CommonJS規範為CommonJS小組所提出,目的是彌補JavaScript在伺服器端缺少模組化機制,NodeJS、webpack都是基於該規範來實現的。

CommonJS的基本用法

//a.js
module.exports = function () {
  console.log("hello world")
}

//b.js
var a = require('./a');

a();//"hello world"

//或者

//a2.js
exports.num = 1;
exports.obj = {xx: 2};

//b2.js
var a2 = require('./a2');

console.log(a2);//{ num: 1, obj: { xx: 2 } }

CommonJS的特點

  • 所有程式碼都執行在模組作用域,不會汙染全域性作用域;
  • 模組是同步載入的,即只有載入完成,才能執行後面的操作;
  • 模組在首次執行後就會快取,再次載入只返回快取結果,如果想要再次執行,可清除快取;
  • CommonJS輸出是值的拷貝(即,require返回的值是被輸出的值的拷貝,模組內部的變化也不會影響這個值)。

ES6 Module

ES6 Module是ES6中規定的模組體系,相比上面提到的規範, ES6 Module有更多的優勢,有望成為瀏覽器和伺服器通用的模組解決方案。

//a.js
var name = 'lin';
var age = 13;
var job = 'ninja';

export { name, age, job};

//b.js
import { name, age, job} from './a.js';

console.log(name, age, job);// lin 13 ninja

//或者

//a2.js
export default function () {
  console.log('default ');
}

//b2.js
import customName from './a2.js';
customName(); // 'default'

ES6 Module的特點(對比CommonJS)

  • CommonJS模組是執行時載入,ES6 Module是編譯時輸出介面;
  • CommonJS載入的是整個模組,將所有的介面全部載入進來,ES6 Module可以單獨載入其中的某個介面;
  • CommonJS輸出是值的拷貝,ES6 Module輸出的是值的引用,被輸出模組的內部的改變會影響引用的改變;
  • CommonJS this指向當前模組,ES6 Module this指向undefined;

目前瀏覽器對ES6 Module相容還不太好,我們平時在webpack中使用的export/import,會被打包為exports/require。

必須瞭解 ES6 模組與 CommonJS 模組完全不同。

它們有兩個重大差異。

CommonJS 模組輸出的是一個值的拷貝,ES6 模組輸出的是值的引用。
CommonJS 模組是執行時載入,ES6 模組是編譯時輸出介面。
第二個差異是因為 CommonJS 載入的是一個物件(即module.exports屬性),該物件只有在指令碼執行完才會生成。而 ES6 模組不是物件,它的對外介面只是一種靜態定義,在程式碼靜態解析階段就會生成。

尾語

相關知識點僅供參考,具有一定的時效性,博主比較懶,部落格僅做一個記錄,更新的內容會在github裡https://github.com/leomYili/my-learning-notes/tree/master/interview