2018 – 2019 年前端 JavaScript 面試題


JavaScript 基礎問題

1.使以下代碼正常運行:

JavaScript 代碼:
    const a = [1, 2, 3, 4, 5];
     
    // Implement this
    a.multiply();
     
    console.log(a); // [1, 2, 3, 4, 5, 1, 4, 9, 16, 25]

2.以下代碼在 JavaScript 中返回 false 。 解釋一下為什么會這樣:

JavaScript 代碼:
    // false
    0.2 + 0.1 === 0.3

3.JavaScript 中有哪些不同的數據類型?

提示:只有兩種類型 – 主要數據類型和引用類型(對象)。 有 6 種主要類型。

4.解決以下異步代碼問題。

檢索並計算屬於同一教室中每個學生的平均分數,其中一些ID為75。每個學生可以在一年內參加一門或多門課程。 以下 API 可用於檢索所需數據。

JavaScript 代碼:
    // GET LIST OF ALL THE STUDENTS
    GET /api/students
    Response:
    [{
    "id": 1,
    "name": "John",
    "classroomId": 75
    }]
    // GET COURSES FOR GIVEN A STUDENT
    GET /api/courses?filter=studentId eq 1
    Response:
    [{
    "id": "history",
    "studentId": 1
    }, {
    "id": "algebra",
    "studentId": 1
    },]
    // GET EVALUATION FOR EACH COURSE
    GET /api/evaluation/history?filter=studentId eq 1
    Response:
    {
    "id": 200,
    "score": 50,
    "totalScore": 100
    }

 

編寫一個接受教室 ID 的函數,您將根據該函數計算該教室中每個學生的平均值。該函數的最終輸出應該是具有平均分數的學生列表:

JavaScript 代碼:
    [
    { "id": 1, "name": "John", "average": 70.5 },
    { "id": 3, "name": "Lois", "average": 67 },
    ]

使用普通的 callbacks ,promises ,observablesgenerator 或 async-wait 編寫所需的函數。嘗試使用至少 3 種不同的技術解決這個問題。

5.使用 JavaScript Proxy 實現簡單的數據綁定

提示:ES Proxy 允許您攔截對任何對象屬性或方法的調用。首先,每當更改底層綁定對象時,都應更新 DOM 。

6.解釋 JavaScript 並發模型

您是否熟悉 Elixir,Clojure,Java 等其他編程語言中使用的任何其他並發模型?

提示:查找事件循環,任務隊列,調用棧,堆等。

7.new 關鍵字在 JavaScript 中有什么作用?

提示:在 JavaScript 中,new 是用於實例化對象的運算符。 這里的目的是了解知識廣度和記憶情況。

另外,請注意 [[Construct]] 和 [[Call]]

8.JavaScript 中有哪些不同的函數調用模式? 詳細解釋。

提示:有四種模式,函數調用,方法調用,.call() 和 .apply()

9.解釋任一即將發布新的 ECMAScript 提案。

提示:比如 2018 的 BigInt,partial 函數,pipeline 操作符 等。

10.JavaScript 中的迭代器(iterators)和迭代(iterables)是什么? 你知道什么是內置迭代器嗎?

11.為什么 JavaScript classes(類)被認為是壞的或反模式?

這是一個神話嗎?它是否遭受了誤傳?是否有一些有用的用例?

12.如何在 JSON 中序列化以下對象?

如果我們將以下對象轉換為 JSON 字符串,會發生什么?

JavaScript 代碼:
    const a = {
    key1: Symbol(),
    key2: 10
    }
    // What will happen?
    console.log(JSON.stringify(a));

13.你熟悉 Typed Arrays 嗎? 如果熟悉,請解釋他們與 JavaScript 中的傳統數組相比的異同?

Arrays

ES6對數組添加了一些新的方法,另外還添加了TypedArray類型,這種類型支持對內存的操作,ArrayBuffer和C語言內存分配一樣,分配一塊內存塊。下面從以下幾個方面來看看ES6數組的變化:

  1. 2個靜態方法 Array.of(), Array.from();
  2. 數組原型上新添加的方法 find(), findIndex(), fill(), copyWithin();
  3. 新類型 ArrayBuffer;
  4. Typed Arrays, 以及和Array的相同性,差異性

一.Array.of() & Array.from()

1.Array.of()

ES6新添加了這個靜態方法,用於數組構造器實例化數組。我們知道數組實例化一般可以通過構造器或者數組字面量的形式來聲明。ES5通過構造器聲明數組會出現一個比較模糊的地方,比如:

var arr = new Array(1, 2, 3);
// 表示聲明一個數組元素為[1, 2, 3]的數組
arr.length; // 3
arr[0]; // 1
arr[1]; // 2
arr[2]; // 3

var arr = new Array(2); // 2表示長度
// 而這樣則表示一個數組長度為2的數組,而數組元素未聲明
arr.length; // 2
arr; // [undefined, undefined]

Array.of()消除了這種模糊,凡是向方法中添加數字,都表示數組元素,而不是長度

var arr = Array.of(1, 2);
arr; // [1, 2]

var arr = Array.of(2); // 2表示元素
arr; // [2]

2.Array.from()

ES6之前要將一個array-like對象轉換成數組,我們一般是利用slice方法,比如

function doSomething() {

    // 將arguments類數組對象轉換成數組
    var args = Array.prototype.slice.call(arguments);
    // 或者  [].slice.call(arguments)
    // ...

}

ES6通過靜態方法 Array.from() 可以將 類數組對象 或者 可迭代的對象 轉換成一個數組,其語法為:

Array.from(arraylike[, callback] [, thisArg])

上面的例子可以寫為:

function doSomething() {
    var args = Array.from(arguments);
    // ...
}

將可迭代的對象轉變為數組:

var set = new Set([1, 2, 2, 4, 5]);
// Set {1, 2, 4, 5}
var arr = Array.from(set); // [1, 2, 4, 5]

后面添加回調函數, 如果回調函數屬於一個對象, 則可以添加第3個可選參數,指出this上下文:

let helper = {
    diff: 1,
    add(value) {
        return value + this.diff;
    }
}
 
function translate() {
    // 第2個參數為callback, 第3個參數為上下文
    return Array.from(arguments, helper.add, helper);
}

translate(1, 2, 3); // [2, 3, 4]

二.新添加的方法

1.find(),findIndex()

以前我們查看數組中是否存在某個值或者某個值的位置時,一般使用indexOf(), lastIndexOf(),ES6添加了find(), findIndex()來添加條件查找。這兩個方法和map(),forEach()一樣添加一個回調函數,有選擇性的添加thisArg指定上下文。

find找到了就返回第一個滿足條件的,未找到返回undefined, findIndex返回索引位置,未找到返回 -1:

var arr = [1, 2, 19, 16];
arr.find(v => v > 10 );  // 返回 19
arr.findIndex(v => v > 10); // 返回 2

find(), findIndex()用於查找一個數組元素滿足某個條件而不是值,要根據值查找建議使用indexOf(), lastIndexOf().

2.fill(), copyWithin()

這兩個方法其實為了操作Typed Array對象使用的,但是為了保持一致性,也添加給普通數組了。看下語法:

    fill(value[,startFillPostion = 0 ] [, endFillPostion = arr.length])
    copyWithin(StartToBeCopiedPos[,StartCopyPos = 0] [,EndCopyPos = arr.length])

先看fill:

var arr = [1, 2, 3, 4];

// 不指定開始和結束填充的位置
arr.fill(5); // arr: [5, 5, 5, 5]

// 指定開始
arr.fill(5, 2); // arr: [1, 2, 5, 5]

// 都指定,不包含結束位置
arr.fill(5, 0, 2)// arr: [5, 5, 3, 4]

// 當然起始和結尾也可以為負數,相當於加上數組長度
arr.fill(5, -3); // arr: [1, 5, 5, 5]
// 相當於 arr.fill(5, -3+4)

copyWith: 比較繞, 它是指復制自身內容到指定的位置:

var arr = [1, 10, 15, 29, 18];

// 只有一個參數表示被復制的索引,另外2個參數則默認從0開始到結束
arr.copyWithin(2); // arr [1, 10, 1, 10, 15]

// 2個參數,指定自身復制的位置
arr.copyWithin(3, 1); // arr [1, 10, 15, 10, 15]

// 3個參數都指定
arr.copyWithin(2, 0, 1); // arr [1, 10, 1, 29, 18]
// 0-1只有一個數 "1", 所有索引位置為2的 "15" 被替換成 "1"

上面例子我們可以發現,是有這些方法都會改變數組自身


三.ArrayBuffer

AarryBuffer是指分配內存中的一塊位置用於操作,相當於C語言中的malloc(),對內存塊進行二進制操作,會極大的提升性能,滿足一些特別的接口要求。
先了解一下內存分配的基本語法:

var buffer = new ArrayBuffer(bytes);

比如:分配10個字節(byte)

var buffer = new ArrayBuffer(10);

內存的大小確定之后是不能修改的,可以改變內部內容

屬性: byteLength , slice()
slice方法是從已經存在的內存塊中復制一段,添加都新的內存塊中

var buffer = new ArrayBuffer(10);
var buffer2 = buffer.slice(3, 5);
// 將buffer中的3, 4字節內容拷貝到新的內存塊中
 console.log(buffer2.byteLength); // 2

四.TypedArray, Views視圖

光有內存塊,而不進行操作也是沒有用的,javascript通過視圖的方式對內存塊進行讀寫,存在兩種視圖:

  1. TypedArray: 特定類型的數據類型,特定類型的一種視圖,對特定類型操作性能更高;
  2. DataView: 各種數據類型都可以,還可以指定大端序(BIG_ENDIAN),小端序(LITTLE_ENDIAN),功能更強大的一種視圖

1.共同屬性

這兩種視圖擁有一些共同的屬性:

  • buffer: 表示指向的內存塊;

2.DataView

DataView構造器能夠添加三個參數:new DataView(buffer[, byteOffset][, byteLength])

var buffer = new ArrayBuffer(10);
// 指向整個內存塊
var dataView1 = new DataView(buffer);    
dataView1.buffer === buffer; // true
dataView1.byteOffset; // 0
dataView1.byteLength; // 10

// 表示 字節 5, 6上的視圖
var dataView2 = new DataView(buffer, 5, 2);
dataView2.buffer === buffer; // true
dataView2.byteOffset; // 5
dataView2.byteLength; // 2

3.TypedArray

TypedArray本質上是一個抽象類,他表示9中特定類型: Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array,
Float64Array,還有一種只針對Canvas顏色值的 Uint8ClampedArray

14. 默認參數是如何工作?

如果我們在調用 makeAPIRequest 函數時必須使用 timeout 的默認值,那么正確的語法是什么?

JavaScript 代碼:
    function makeAPIRequest(url, timeout = 2000, headers) {
    // Some code to fetch data
    }

15.解釋 TCO – 尾調用優化(Tail Call Optimization)。 有沒有支持尾調用優化的 JavaScript 引擎?

  • 尾調用指“函數尾調用”。
  • 尾調用是函數式編程的一個重要概念。
  • 尾調用就是某個函數最后一步是調用另一個函數

提示:截至 2018 年,沒有。


JavaScript 前端應用設計問題

1.解釋單向數據流和雙向數據綁定。

Angular 1.x 基於雙向數據綁定,而 React,Vue,Elm 等基於單向數據流架構。

單向數據綁定

單向數據綁定,帶來單向數據流。。
指的是我們先把模板寫好,然后把模板和數據(數據可能來自后台)整合到一起形成HTML代碼,然后把這段HTML代碼插入到文檔流里面。適用於整體項目,並於追溯。

 

 


 

優點:
1. 所有狀態的改變可記錄、可跟蹤,源頭易追溯;
2. 所有數據只有一份,組件數據只有唯一的入口和出口,使得程序更直觀更容易理解,有利於應用的可維護性;
3. 一旦數據變化,就去更新頁面(data-頁面),但是沒有(頁面-data);
4. 如果用戶在頁面上做了變動,那么就手動收集起來(雙向是自動),合並到原有的數據中。
方神:雙向綁定 = 單向綁定 + UI事件監聽,可了解vuex
缺點:
1. HTML代碼渲染完成,無法改變,有新數據,就須把舊HTML代碼去掉,整合新數據和模板重新渲染;
2. 代碼量上升,數據流轉過程變長,出現很多類似的樣板代碼;
3. 同時由於對應用狀態獨立管理的嚴格要求(單一的全局store),在處理局部狀態較多的場景時(如用戶輸入交互較多的“富表單型”應用),會顯得啰嗦及繁瑣。

雙向數據綁定

雙向數據綁定,帶來雙向數據流。AngularJS2添加了單向數據綁定
數據模型(Module)和視圖(View)之間的雙向綁定。無論數據改變,或是用戶操作,都能帶來互相的變動,自動更新。適用於項目細節,如:UI控件中(通常是類表單操作)。

 


優點:
1. 用戶在視圖上的修改會自動同步到數據模型中去,數據模型中值的變化也會立刻同步到視圖中去;
2. 無需進行和單向數據綁定的那些CRUD(Create,Retrieve,Update,Delete)操作;
3. 在表單交互較多的場景下,會簡化大量業務無關的代碼。
缺點:
1. 無法追蹤局部狀態的變化;
2. “暗箱操作”,增加了出錯時 debug 的難度;
3. 由於組件數據變化來源入口變得可能不止一個,數據流轉方向易紊亂,若再缺乏“管制”手段,血崩。
雙向數據綁定,Angular使用 臟檢查“digest” - “dirty checking”
(在angular中,他沒有辦法判斷你的數據是否做了更改, 所以它設置了一些條件,當你觸發了這些條件之后,它就執行一個檢測來遍歷所有的數據,對比你更改了地方,然后執行變化。這個檢查很不科學。而且效率不高,有很多多余的地方,所以官方稱為臟檢查)

2.單向數據流架構在哪些方面適合 MVC?

MVC 擁有大約 50 年的悠久歷史,並已演變為 MVP,MVVM 和 MV *。兩者之間的相互關系是什么?如果 MVC 是架構模式,那么單向數據流是什么?這些競爭模式是否能解決同樣的問題?

3.客戶端 MVC 與服務器端或經典 MVC 有何不同?

提示:經典 MVC 是適用於桌面應用程序的 Smalltalk MVC。在 Web 應用中,至少有兩個不同的數據 MVC 周期。

4.使函數式編程與面向對象或命令式編程不同的關鍵因素是什么?

提示:Currying(柯里化),point-free 函數,partial 函數應用,高階函數,純函數,獨立副作用,record 類型(聯合,代數數據類型)等。

5.在 JavaScript 和前端的上下文中,函數式編程與響應式編程有什么關系?

提示:沒有正確答案。但粗略地說,函數式編程是關於小型編碼,編寫純函數和響應式編程是大型編碼,即模塊之間的數據流,連接以 FP 風格編寫的組件。 FRP – 功能響應式編程( Functional Reactive Programming)是另一個不同但相關的概念。

6.不可變數據結構(immutable data structures)解決了哪些問題?

不可變結構是否有任何性能影響? JS 生態系統中哪些庫提供了不可變的數據結構?這些庫的優點和缺點是什么?

提示:線程安全(我們真的需要在瀏覽器 JavaScript 中擔心嗎?),無副作用的函數,更好的狀態管理等。

7.大型應用程序是否應使用靜態類型?

  1. 如何比較 TypeScript/Flow 與 Elm/ReasonML/PureScript 等 JS 轉換語言?這些方法的優缺點是什么?
  2. 選擇特定類型系統的主要標准應該是什么?
  3. 什么是類型推斷(type inference)?
  4. 靜態類型語言和強類型語言有什么區別?在這方面 JavaScript 的本質是什么?
  5. 有你知道的弱類型但靜態類型的語言嗎?有你知道的動態類型但強類型的語言嗎?舉例一二。

提示:Structural 與 Nominal 類型系統,類型穩健性,工具/生態系統支持,正確性超過方便。

8.JavaScript 中有哪些傑出的模塊系統(module systems )?如何評價 ES 模塊系統。

列出在實現不同模塊系統之間互操作所涉及的一些復雜性問題(主要對 ES 模塊和 CommonJS 互操作感興趣)

9.HTTP/2 將如何影響 JavaScript 應用程序打包?

列出 HTTP/2 與其上一個版本的基本區別。

10.Fetch API 相對於傳統的 Ajax 有哪些改進?

  1. 使用 Fetch API 有那些缺點/難點嗎?
  2. 哪些是Ajax 可以做的,而 fetch 不能做的?

11.如何評論 pull-based 和 push-based 的反應系統。

討論概念,含義,用途等。

  1. 在這個討論中加入惰性和及早求值。
  2. 然后在討論中添加單數和復數值維度。
  3. 最后,討論值解析的同步和異步性質。
  4. 為JavaScript中可用的每個組合提供示例。

提示:Observable 是惰性的,基於推送的復數值構造,同時具有 async/sync 調度程序。

12.討論與 Promise 相關的問題。

提示:及早求值(eager evaluation),尷尬的取消機制,用 then() 方法偽裝 map() 和 flatMap() 等。


前端基礎和理論問題

1.HTML 中 Doctype 的用途是什么?

具體談談,以下每種情況下會發生什么:

    1. Doctype 不存在。
    2. 使用了 HTML4 Doctype,但 HTML 頁面使用了 HTML5 的標簽,如 <audio> 或 <video> 。它會導致任何錯誤嗎?
    3. 使用了無效的 Doctype。

1、告訴瀏覽器使用什么樣的html或xhtml規范來解析html文檔

2、對瀏覽器的渲染模式產生影響;不同的渲染模式會影響到瀏覽器對於 CSS 代碼甚至 JavaScript 腳本的解析,所以Doctype是非常關鍵的,尤其是在 IE 系列瀏覽器中,由DOCTYPE 所決定的 HTML 頁面的渲染模式至關重要。 

兩種渲染模式:

     BackCompat:標准兼容模式未開啟(或叫怪異模式[Quirks mode]、混雜模式)

     CSS1Compat:標准兼容模式已開啟(或叫嚴格模式[Standards mode/Strict mode])

選擇什么樣的DOCTYPE

如上例所示,XHTML 1.0中有3種DTD(文檔類型定義)聲明可以選擇:過渡的(Transitional)、嚴格的(Strict)和框架的(Frameset)。這里分別介紹如下。

1.過渡的

一種要求不很嚴格的DTD,允許在頁面中使用HTML4.01的標識(符合xhtml語法標准)。過渡的DTD的寫法如下:

代碼如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

2.嚴格的

一種要求嚴格的DTD,不允許使用任何表現層的標識和屬性,例如<br/>等。嚴格的DTD的寫法如下:

代碼如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

3.框架的

一種專門針對框架頁面所使用的DTD,當頁面中含有框架元素時,就要采用這種DTD。框架的DTD的寫法如下:

代碼如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

使用嚴格的DTD來制作頁面,當然是最理想的方式。但是,對於沒有深入了解Web標准的網頁設計者,比較合適的是使用過渡的DTD。因為這種DTD還允許使用表現層的標識、元素和屬性,比較適合大多數網頁制作人員。

HTML 4.01 中的 doctype 需要對 DTD 進行引用,因為 HTML 4.01 基於 SGML。
HTML 5 不基於 SGML,因此不需要對 DTD 進行引用,但是需要 doctype 來規范瀏覽器的行為(html 5簡化了這種聲明,意在告訴瀏覽器使用統一的標准即可)

2. DOM 和 BOM 的區別是什么?

提示:BOM,DOM,ECMAScript 和 JavaScript 都是不同的東西。

BOM和瀏覽器關系密切,DOM和HTML文檔有關。BOM是Browser Object Mode的縮寫,及對瀏覽器對象模型,用來獲取或設置瀏覽器的屬性、行為。DOM是Document Object Model 的縮寫,即文檔對象模型,用來獲取或設置文檔中標簽的屬性。BOM沒有相關標准。DOM是W3C的標准。BOM的最根本對象是window。DOM最根本對象是document(實際上是window.document)。由於DOM的操作對象是文檔(Document),所以dom和瀏覽器沒有直接關系。 
HTML DOM 模型被構造為對象的樹。
通過可編程的對象模型,JavaScript 獲得了足夠的能力來創建動態的 HTML。
JavaScript 能夠改變頁面中的所有 HTML 元素
JavaScript 能夠改變頁面中的所有 HTML 屬性
JavaScript 能夠改變頁面中的所有 CSS 樣式
JavaScript 能夠對頁面中的所有事件做出反應

3.JavaScript 中的事件處理如何運行?

如下圖所示,我們有三個 div 元素。每個 div 都有一個與之關聯的點擊處理程序。處理程序執行以下任務:

  • Outer div click 處理程序將 hello outer 打印到控制台。
  • Inner div click 處理程序將 hello inner 打印到控制台。
  • Innermost div click 處理程序將 hello innermost 打印到控制台。

編寫一段代碼來分配這些任務,以便在單擊 innermost div 時始終打印以下序列?

hello inner → hello innermost → hello outer

事件冒泡和捕獲

提示:事件捕獲和事件冒泡

探究JavaScript中的五種事件處理程序

  我們知道JavaScript與HTML之間的交互是通過事件實現的。事件最早是在IE3和Netscape Navigator 2中出現的,當時是作為分擔服務器運算負載的一種手段。  通俗地理解,事件就是用戶或瀏覽器自身執行的某種操作。而事件處理程序即為響應某個事件的函數。抽出主干,即事件處理程序為函數。  我們又把事件處理程序稱為事件偵聽器。  事件處理程序是以"on"開頭的,因此對於事件on的時間處理程序即為onclick。時間處理程序在JavaScript中大致有五種,下面會根據這五種不同的時間處理程序分為5部分來介紹。

  1. HTML事件處理程序
  2. DOM0級事件處理程序
  3. DOM2級事件處理程序
  4. IE事件處理程序
  5. 跨瀏覽器的事件處理程序

第一部分:HTML事件處理程序

  什么使HTML事件處理程序呢?顯然,通過名字就可以猜到,它是卸載HTML中的函數(事件處理程序)。初學者大多用到的事件處理程序即為HTML事件處理程序。下面舉例:

例1:

    <button onclick="alert('success')">點我</button>

這條代碼即為事件處理程序,點擊button后,會彈出彈框,顯示success。

特點:HTML事件處理程序中Javascript代碼作為了onclick特性的值,因此,我們不能在JavaScript代碼中使用未經轉義的HTML語法字符,如&(和號)、""(雙引號)、<(小於號)、>(大於號)等等。所以這個例子中字符串我使用了單引號而沒有使用雙引號。看下面在JavaScript代碼中使用了未經轉義的HTML語法字符。

例2:

    <button onclick="alert("success")">點我</button>

這時,我在success外使用了HTML語法字符""(雙引號),這時不會彈出窗口,而是報錯語法錯誤。但是我如果還是希望使用雙引號呢? 這時就要用&quot;實體來代替HTML中的語法字符。如下例所示:

例3:

<button onclick="alert(&quot;success&quot;)">點我</button>
    <!--  正常彈出窗口-->

這個例子中我們在JavaScript代碼中使用了HTML實體而沒有使用HTML語法字符,這時就不會報錯了。

例4:

1
2
3
4
5
6
7
8
<button onclick="show()">點我</button>
<!--  正常彈出窗口-->
 
<script>
    function show(){
        alert("success");
    }
</script>

這個例子中我們調用函數,而把函數定義放在了script中,這樣也是可以的。因為:事件處理程序中的代碼在執行時,有權訪問到全局作用域中的任何代碼。這句話怎么理解呢?  實際上,我們可以在chrome中觀察button標簽的作用域鏈。如下所示:

  接下來我們再看看script所在的作用域,如下圖所示:

可以看到script標簽就在全局作用域。

  也就是說目前button中的HTML事件處理函數在作用域鏈的最前端,而Script在全局作用域,所以“事件處理程序中的代碼在執行時,有權訪問到全局作用域中的任何代碼。”這句話就不難理解了。

例5:

    <button onclick="alert(event.type)">點我</button>

這時瀏覽器彈出窗口顯示:click。這個例子是什么意思呢?注意到我並沒有在event.type外加單引號,說明這並不是字符串。實際上,event是局部對象--在觸發DOM上的某個事件時,會產生一個事件對象event,這個對象包含着所有與事件有關的信息。而這里是彈出了對象了類型,即為click。

HTML事件處理程序的三個缺點(重點):

1. 時差問題。 因為用戶可能在HTML元素一出現就開始觸發相應事件,但是有可能該事件的腳本(如例4中show()函數的函數定義在script中)還沒有加載完成,此時不具備執行條件,故報錯。

  解決方法:將HTML事件處理程序封裝在一個try-catch塊中,以便錯誤不會浮出水面。

<input type="button" value="click me" onclick="try{show();}catch(ex){}">

2.這樣擴展事件實例程序的作用域鏈在不同的瀏覽器中會導致不同的結果(例4中我是在chrome中查看的作用域鏈,其他瀏覽器不一定是這樣的,請注意)。不同JavaScript引擎遵循的標識符解析規則略有差異,很有可能會在訪問非限定對象成員時出錯。

3.HTML和JavaScript代碼緊密耦合。 結果是:如果要更換事件處理程序,就必須改動兩個地方--HTML代碼和JavaScript代碼。

那么怎么解決上面的問題呢? DOM0級事件處理程序是一個不錯的選擇!

第二部分:DOM0級事件處理程序

  DOM0級事件處理程序用的也非常普遍。之所以成為DOM0級,我認為是當時還沒有出DOM標准,而IE和Netscape Navigator兩者使用的時間處理程序(不知是否合理,望批評指正)。 總之,我們先看看下面的例子吧。

例6:

復制代碼
    <button id="button">點我</button>

<script>
    var button=document.getElementById("button");
    button.onclick=function(){
        alert("clicked");
    }
</script>
復制代碼

即我們先在script中取得元素的引用,然后再將一個函數賦值給onclick事件處理程序。 之前介紹過,事件處理程序即為函數,而button.onclick這種形式即函數作為了對象的方法。那么對象的方法即事件處理程序是在元素(對象)的作用域中運行而非在全局作用域中運行的,因為方法是屬於對象的。(注意:例4中事件處理程序是在全局作用域中運行的)。 如果這個函數中存在this關鍵字,那么this就會指向這個對象。下面我們在瀏覽器中證明事件處理程序是在元素的作用域中運行。

我們看到alert("clicked");確實是在button中運行的。

 

我們還可以通過下面的方式刪除通過DOM0級方法指定的事件處理程序。

button.onclick=null;

通過上面的分析我們可以知道DOM0級事件處理程序是非常不錯的,它解決了HTML事件處理程序的三個缺點:時差問題、作用域鏈導致的不同瀏覽器表現不一致問題和HTML和JavaScript緊密耦合問題。

  但是,DOM0級事件處理程序並不是完美的,它同樣有兩個缺點:

  1. 我們不能給一個元素同時添加兩個事件。
  2. 我們不能控制元素的事件流(捕獲or冒泡)。

  對於第二個問題后面會講到,第一個問題舉例如下:

復制代碼
    <button id="button">點我</button>

<script>
    var button=document.getElementById("button");
    button.onclick=function(){
        alert("clicked");
    }
    button.onclick=function(){
        alert("again");
    }
復制代碼

雖然我對同一個元素設置了兩個事件處理程序,但是最終的結果是:只有第二個事件有效(覆蓋了第一個事件)。當然,人類是聰明的動物,DOM2級事件很好的解決了這個問題!

第三部分:DOM2級事件處理程序

  DOM2級事件處理程序定義了兩個方法:

  • addEventListener()   ---添加事件偵聽器
  • removeEventListener()   ---刪除事件偵聽器

 在博文的開頭我就提到了事件處理程序即事件偵聽器。這兩個方法都接收三個參數:

  1. 要處理的事件名(注意:是時間名,所以沒有on!),如click、mouseover等。
  2. 作為事件處理程序的函數,如function(){alert("clicked");}
  3.  表示事件流方式的布爾值。false為冒泡階段調用事件處理程序;true為捕獲階段調用事件處理程序。對於冒泡和捕獲這兩種時間流可以看《JavaScript中的兩種事件流


下面通過兩個例子加深理解:

例7:

復制代碼
<button id="button">點我</button>

<script>
    var button=document.getElementById("button");
    button.addEventListener("click",function(){
        alert(this.id);
    },false);
    button.addEventListener("click",function(){
        alert("another event");
    },false);
</script>
復制代碼

結果:第一次彈出窗口:button。

   第二次彈出窗口:another event。

結論通過DOM2級事件處理程序,我們可以為同一個元素添加兩個或更多的事件。事件根據順序依次觸發。且this同樣指向當前元素,故函數在元素的作用域中執行。

this分析:和前面的DOM0級事件處理程序一樣,這里的addEventListener同樣也可以看作對象的方法,不同之初在於,DOM0級的方法需要另外一個函數來賦值,而這里的方法是DOM2級規范預定義的。

 

  removeEventListener()這個刪除事件處理程序的方法值得注意的是:使用addEventListener()來添加的事件處理程序只能通過它來移除,且需要傳入相同的參數。

例8:

復制代碼
    <button id="button">點我</button>

<script>
    var button=document.getElementById("button");
    button.addEventListener("click",function(){
        alert(this.id);
    },false);
    button.removeEventListener("click",function(){
        alert("another event");
    },false);
復制代碼

上述代碼貌似可以移除click的事件處理程序,但是通過實驗證明是不可以的,原因是:事件處理程序為匿名函數時無法移除。看下面的成功移除的例子:

例9:

復制代碼
    <button id="button">點我</button>

<script>
    var button=document.getElementById("button");
    function handler(){
        alert(this.id);
    }
    button.addEventListener("click",handler,false);
    button.removeEventListener("click",handler,false);
</script>
復制代碼

  成功移除!

注意:1.傳入方法的handler沒有(),是因為這里都只是定義函數,而不是調用,需要注意。

   2.這兩個方法的第三個參數都是false,即事件處理程序添加到冒泡階段。一般不使用true,因為低版本的IE不支持捕獲階段。

 

DOM2級事件處理程序成功地解決了前面所有事件處理程序的問題,堪稱perfect!!!!  然而總是特立獨行的IE瀏覽器又有新花樣,它也有自己的一套事件處理程序,下面我們就來看看吧。

第四部分:IE事件處理程序

  IE事件處理程序中有類似與DOM2級事件處理程序的兩個方法:

  1. attachEvent()
  2. detachEvent()

  它們都接收兩個參數:

  1. 事件處理程序名稱。如onclick、onmouseover,注意:這里不是事件,而是事件處理程序的名稱,所以有on。
  2. 事件處理程序函數。如function(){alert("clicked");}

   之所以沒有和DOM2級事件處理程序中類似的第三個參數,是因為IE8及更早版本只支持冒泡事件流。

  注意:

  1.IE事件處理程序中attachEvent()的事件處理程序的作用域和DOM0與DOM2不同,她的作用域是在全局作用域中。因此,不同於DOM0和DOM2中this指向元素,IE中的this指向window。 

  2.同樣,我們可以使用attachEvent()來給同一個元素添加多個事件處理程序。但是與DOM2不同,事件觸發的順序不是添加的順序而是添加順序的相反順序。

  3.同樣地,通過attachEvent()添加的事件處理程序必須通過detachEvent()方法移除,同樣的,不能使用匿名函數。

  4.支持IE事件處理程序的瀏覽器不只有IE瀏覽器,還有Opera瀏覽器。

第五部分:跨瀏覽器的事件處理程序

  實際上,這一部分視為了跨瀏覽器使用,將前面的幾部分結合起來就可以了。

  這一部分需要創建兩個方法:

  • addHandler()  --這個方法職責是視情況來使用DOM0級、DOM2級、IE事件處理程序來添加事件。
  • removeHandler()--這個方法就是移除使用addHandler添加的事件。

  這兩個方法接收相同的三個參數:

  1. 要操作的元素--通過dom方法獲取
  2. 事件名稱--注意:沒有on,如click、mouseover
  3. 事件處理程序函數--即handler函數

  這兩個方法的構造情況如下:

復制代碼
var EventUtil={
    addHandler:function(element,type,handler){
        if(element.addEventListener){
            element.addEventListener(type,handler,false);//注意:這里默認使用了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);//注意:這里默認使用了false(冒泡)
        }else if(element.detachEvent){
            element.detachEvent("on"+type,handler);
        }else{
            element["on"+type]=null;
        }
    }
};
復制代碼

即先判斷DOM2級事件處理程序,再判斷IE事件處理程序,最后使用DOM0級事件處理程序。

例10:通過這個例子來使用上面構造的方法。

復制代碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>跨瀏覽器事件處理程序</title>
</head>
<body>
    

    <button id="button">點我</button>

<script>
var EventUtil={
    addHandler:function(element,type,handler){
        if(element.addEventListener){
            element.addEventListener(type,handler,false);//注意:這里默認使用了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);//注意:這里默認使用了false(冒泡)
        }else if(element.detachEvent){
            element.detachEvent("on"+type,handler);
        }else{
            element["on"+type]=null;
        }
    }
};

    function handler(){
        alert("clicked");
    }

    var button=document.getElementById("button");
    EventUtil.addHandler(button,"click",handler);
</script>
</body>
</html>
復制代碼

最后瀏覽器成功彈出“clicked”。

4.使用單頁應用將文件上傳到服務器的有哪些方法?

提示:XMLHttpRequest2(streaming),fetch(non-streaming),File API

一、XMLHttpRequest2(streaming)

 

XMLHttpRequest是一個瀏覽器接口,使得Javascript可以進行HTTP(S)通信。

 

最早,微軟在IE 5引進了這個接口。因為它太有用,其他瀏覽器也模仿部署了,ajax操作因此得以誕生。

 

但是,這個接口一直沒有標准化,每家瀏覽器的實現或多或少有點不同。HTML 5的概念形成后,W3C開始考慮標准化這個接口。2008年2月,就提出了XMLHttpRequest Level 2 草案。

 

這個XMLHttpRequest的新版本,提出了很多有用的新功能,將大大推動互聯網革新。本文就對這個新版本進行詳細介紹。

 

 

一、老版本的XMLHttpRequest對象

 

在介紹新版本之前,我們先回顧一下老版本的用法。

 

首先,新建一個XMLHttpRequest的實例。

 

  var xhr = new XMLHttpRequest();

 

然后,向遠程主機發出一個HTTP請求。

 

  xhr.open('GET', 'example.php');

  xhr.send();

 

接着,就等待遠程主機做出回應。這時需要監控XMLHttpRequest對象的狀態變化,指定回調函數。

 

  xhr.onreadystatechange = function(){

    if ( xhr.readyState == 4 && xhr.status == 200 ) {

      alert( xhr.responseText );

    } else {

      alert( xhr.statusText );

    }

  };

 

上面的代碼包含了老版本XMLHttpRequest對象的主要屬性:

 

  * xhr.readyState:XMLHttpRequest對象的狀態,等於4表示數據已經接收完畢。

  * xhr.status:服務器返回的狀態碼,等於200表示一切正常。

  * xhr.responseText:服務器返回的文本數據

  * xhr.responseXML:服務器返回的XML格式的數據

  * xhr.statusText:服務器返回的狀態文本。

 

二、老版本的缺點

 

老版本的XMLHttpRequest對象有以下幾個缺點:

 

  * 只支持文本數據的傳送,無法用來讀取和上傳二進制文件。

  * 傳送和接收數據時,沒有進度信息,只能提示有沒有完成。

  * 受到"同域限制"(Same Origin Policy),只能向同一域名的服務器請求數據。

 

三、新版本的功能

 

新版本的XMLHttpRequest對象,針對老版本的缺點,做出了大幅改進。

 

  * 可以設置HTTP請求的時限。

  * 可以使用FormData對象管理表單數據。

  * 可以上傳文件。

  * 可以請求不同域名下的數據(跨域請求)。

  * 可以獲取服務器端的二進制數據。

  * 可以獲得數據傳輸的進度信息。

 

下面,我就一一介紹這些新功能。

 

四、HTTP請求的時限

 

有時,ajax操作很耗時,而且無法預知要花多少時間。如果網速很慢,用戶可能要等很久。

 

新版本的XMLHttpRequest對象,增加了timeout屬性,可以設置HTTP請求的時限。

 

  xhr.timeout = 3000;

 

上面的語句,將最長等待時間設為3000毫秒。過了這個時限,就自動停止HTTP請求。與之配套的還有一個timeout事件,用來指定回調函數。

 

  xhr.ontimeout = function(event){

    alert('請求超時!');

  }

 

目前,Opera、Firefox和IE 10支持該屬性,IE 8和IE 9的這個屬性屬於XDomainRequest對象,而Chrome和Safari還不支持。

 

五、FormData對象

 

ajax操作往往用來傳遞表單數據。為了方便表單處理,HTML 5新增了一個FormData對象,可以模擬表單。

 

首先,新建一個FormData對象。

 

  var formData = new FormData();

 

然后,為它添加表單項。

 

  formData.append('username', '張三');

  formData.append('id', 123456);

 

最后,直接傳送這個FormData對象。這與提交網頁表單的效果,完全一樣。

 

  xhr.send(formData);

 

FormData對象也可以用來獲取網頁表單的值。

 

  var form = document.getElementById('myform');

  var formData = new FormData(form);

  formData.append('secret', '123456'); // 添加一個表單項

  xhr.open('POST', form.action);

  xhr.send(formData);

 

六、上傳文件

 

新版XMLHttpRequest對象,不僅可以發送文本信息,還可以上傳文件。

 

假定files是一個"選擇文件"的表單元素(input[type="file"]),我們將它裝入FormData對象。

 

  var formData = new FormData();

  for (var i = 0; i < files.length;i++) {

    formData.append('files[]', files[i]);

  }

 

然后,發送這個FormData對象。

 

  xhr.send(formData);

 

七、跨域資源共享(CORS)

 

新版本的XMLHttpRequest對象,可以向不同域名的服務器發出HTTP請求。這叫做"跨域資源共享"(Cross-origin resource sharing,簡稱CORS)。

 

使用"跨域資源共享"的前提,是瀏覽器必須支持這個功能,而且服務器端必須同意這種"跨域"。如果能夠滿足上面的條件,則代碼的寫法與不跨域的請求完全一樣。

 

  xhr.open('GET', 'http://other.server/and/path/to/script');

 

目前,除了IE 8和IE 9,主流瀏覽器都支持CORS,IE 10也將支持這個功能。服務器端的設置,請參考《Server-Side Access Control》

 

八、接收二進制數據(方法A:改寫MIMEType)

 

老版本的XMLHttpRequest對象,只能從服務器取回文本數據(否則它的名字就不用XML起首了),新版則可以取回二進制數據。

 

這里又分成兩種做法。較老的做法是改寫數據的MIMEType,將服務器返回的二進制數據偽裝成文本數據,並且告訴瀏覽器這是用戶自定義的字符集。

 

  xhr.overrideMimeType("text/plain; charset=x-user-defined");

 

然后,用responseText屬性接收服務器返回的二進制數據。

 

  var binStr = xhr.responseText;

 

由於這時,瀏覽器把它當做文本數據,所以還必須再一個個字節地還原成二進制數據。

 

  for (var i = 0, len = binStr.length; i < len; ++i) {

    var c = binStr.charCodeAt(i);

    var byte = c & 0xff;

  }

 

最后一行的位運算"c & 0xff",表示在每個字符的兩個字節之中,只保留后一個字節,將前一個字節扔掉。原因是瀏覽器解讀字符的時候,會把字符自動解讀成Unicode的0xF700-0xF7ff區段。

 

八、接收二進制數據(方法B:responseType屬性)

 

從服務器取回二進制數據,較新的方法是使用新增的responseType屬性。如果服務器返回文本數據,這個屬性的值是"TEXT",這是默認值。較新的瀏覽器還支持其他值,也就是說,可以接收其他格式的數據。

 

你可以把responseType設為blob,表示服務器傳回的是二進制對象。

 

  var xhr = new XMLHttpRequest();

  xhr.open('GET', '/path/to/image.png');

  xhr.responseType = 'blob';

 

接收數據的時候,用瀏覽器自帶的Blob對象即可。

 

  var blob = new Blob([xhr.response], {type: 'image/png'});

 

注意,是讀取xhr.response,而不是xhr.responseText。

 

你還可以將responseType設為arraybuffer,把二進制數據裝在一個數組里。

 

  var xhr = new XMLHttpRequest();

  xhr.open('GET', '/path/to/image.png');

  xhr.responseType = "arraybuffer";

 

接收數據的時候,需要遍歷這個數組。

 

  var arrayBuffer = xhr.response;

  if (arrayBuffer) {

    var byteArray = new Uint8Array(arrayBuffer);

    for (var i = 0; i < byteArray.byteLength; i++) {

      // do something

    }
  }

 

更詳細的討論,請看Sending and Receiving Binary Data

 

九、進度信息

 

新版本的XMLHttpRequest對象,傳送數據的時候,有一個progress事件,用來返回進度信息。

 

它分成上傳和下載兩種情況。下載的progress事件屬於XMLHttpRequest對象,上傳的progress事件屬於XMLHttpRequest.upload對象。

 

我們先定義progress事件的回調函數。

 

  xhr.onprogress = updateProgress;

  xhr.upload.onprogress = updateProgress;

 

然后,在回調函數里面,使用這個事件的一些屬性。

 

  function updateProgress(event) {

    if (event.lengthComputable) {

      var percentComplete = event.loaded / event.total;

    }

  }

 

上面的代碼中,event.total是需要傳輸的總字節,event.loaded是已經傳輸的字節。如果event.lengthComputable不為真,則event.total等於0。

 

與progress事件相關的,還有其他五個事件,可以分別指定回調函數:

 

  * load事件:傳輸成功完成。

  * abort事件:傳輸被用戶取消。

  * error事件:傳輸中出現錯誤。

  * loadstart事件:傳輸開始。

  * loadEnd事件:傳輸結束,但是不知道成功還是失敗。

 

二、fetch(non-streaming)

與XMLHttpRequest(XHR)類似,fetch()方法允許你發出AJAX請求。區別在於Fetch API使用Promise,因此是一種簡潔明了的API,比XMLHttpRequest更加簡單易用。

從Chrome 40開始,Fetch API可以被利用在Service Worker全局作用范圍中,自Chrome 42開始,可以被利用在頁面中。

如果你還不了解Promise,需要首先補充這方面知識。

基本的Fetch請求

讓我們首先來比較一個XMLHttpRequest使用示例與fetch方法的使用示例。該示例向服務器端發出請求,得到響應並使用JSON將其解析。

XMLHttpRequest

一個XMLHttpRequest需要設置兩個事件回調函數,一個用於獲取數據成功時調用,另一個用於獲取數據失敗時調用,以及一個open()方法調用及一個send()方法調用。

function reqListener(){
    var data=JSON.parse(this.responseText);
    console.log(data);
}
function reqError(err){
    console.log("Fetch錯誤:"+err);
}
var oReq=new XMLHttpRequest();
oReq.οnlοad=reqListener;
oReq.οnerrοr=reqError;
oReq.open("get","/students.json",true);
oReq.send();

Fetch

一個fetch()方法的使用代碼示例如下所示:

fetch("/students.json")
.then(
    function(response){
        if(response.status!==200){
            console.log("存在一個問題,狀態碼為:"+response.status);
            return;
        }
        //檢查響應文本
        response.json().then(function(data){
            console.log(data);
        });
    }
)
.catch(function(err){
    console.log("Fetch錯誤:"+err);
});

 

在上面這個示例中,我們在使用JSON解析響應前首先檢查響應狀態碼是否為200。

一個fetch()請求的響應為一個Stream對象,這表示當我們調用json()方法,將返回一個Promise對象,因為流的讀取將為一個異步過程。

響應元數據

在上一個示例中我們檢查了Response對象的狀態碼,同時展示了如何使用JSON解析響應數據。我們可能想要訪問響應頭等元數據,代碼如下所示:

fetch("/students.json")
.then(
    function(response){
        console.log(response.headers.get('Content-Type'));
        console.log(response.headers.get('Date'));
        console.log(response.status);
        console.log(response.statusText);
        console.log(response.type);
        console.log(response.url);
    }
)

響應類型

當我們發出一個fetch請求時,響應類型將會為以下幾種之一:“basic”、“cors”或“opaque”。這些類型標識資源來源,提示你應該怎樣對待響應流。

當請求的資源在相同域中時,響應類型為“basic”,不嚴格限定你如何處理這些資源。

如果請求的資源在其他域中,將返回一個CORS響應頭。響應類型為“cors”。“cors”響應限定了你只能在響應頭中看見“Cache-Control”、“Content-Language”、“Content-Type”、“Expires”、“Last-Modified”以及“Progma”。

一個“opaque”響應針對的是訪問的資源位於不同域中,但沒有返回CORS響應頭的場合。如果響應類型為“opaque”,我們將不能查看數據,也不能查看響應狀態,也就是說我們不能檢查請求成功與否。目前為止不能在頁面腳本中請求其他域中的資源。

你可以為fetch請求定義一個模式以確保請求有效。可以定義的模式如下所示:

  • "same-origin":只在請求同域中資源時成功,其他請求將被拒絕。
  • "cors":允許請求同域及返回CORS響應頭的域中的資源。
  • "cors-with-forced-preflight":在發出實際請求前執行preflight檢查。
  • "no-cors"針對的是向其他不返回CORS響應頭的域中的資源發出的請求(響應類型為“opaque”),但如前所述,目前在頁面腳本代碼中不起作用。

為了定義模式,在fetch方法的第二個參數中添加選項對象並在該對象中定義模式:

fetch("http://www.html5online.com.cn/cors-enabled/students.json",{mode:"cors"})
.then(
    function(response){
        console.log(response.headers.get('Content-Type'));
        console.log(response.headers.get('Date'));
        console.log(response.status);
        console.log(response.statusText);
        console.log(response.type);
        console.log(response.url);
    }
)
.catch(function(err){
    console.log("Fetch錯誤:"+err);
});

Promise方法鏈

Promise API的一個重大特性是可以鏈接方法。對於fetch來說,這允許你共享fetch請求邏輯。

如果使用JSON API,你需要檢查狀態並且使用JSON對每個響應進行解析。你可以通過在不同的返回Promise對象的函數中定義狀態及使用JSON進行解析來簡化代碼,你將只需要關注於處理數據及錯誤:

function status(response){
    if(response.status>=200 && response.status<300){
        return Promise.resolve(response);
    }
    else{
        return Promise.reject(new Error(response.statusText));
    }
}
function json(response){
    return response.json();
}
fetch("/students.json")
.then(status)
.then(json)
.then(function(data){
    console.log("請求成功,JSON解析后的響應數據為:",data);
})
.catch(function(err){
    console.log("Fetch錯誤:"+err);
});

在上述代碼中,我們定義了status函數,該函數檢查響應的狀態碼並返回Promise.resolve()方法或Promise.reject()方法的返回結果(分別為具有肯定結果的Promise及具有否定結果的Promise)。這是fetch()方法鏈中的第一個方法。如果返回肯定結果,我們調用json()函數,該函數返回來自於response.json()方法的Promise對象。在此之后我們得到了一個被解析過的JSON對象,如果解析失敗Promise將返回否定結果,導致catch段代碼被執行。

這樣書寫的好處在於你可以共享fetch請求的邏輯,代碼容易閱讀、維護及測試。

POST請求

在Web應用程序中經常需要使用POST方法提交頁面中的一些數據。

為了執行POST提交,我們可以將method屬性值設置為post,並且在body屬性值中設置需要提交的數據。

fetch(url,{
    method:"post",
    headers:{
        "Content-type":"application:/x-www-form-urlencoded:charset=UTF-8"
    },
    body:"name=lulingniu&age=40"
})
.then(status)
.then(json)
.then(function(data){
    console.log("請求成功,JSON解析后的響應數據為:",data);
})
.catch(function(err){
    console.log("Fetch錯誤:"+err);
});

使用Fetch請求發送憑證

你可能想要使用Fetch發送帶有諸如cookie之類的憑證的請求。你可以在選項對象中將credentials屬性值設置為“include”:

fetch(url,{
credentials:"include"
})

三、File API

file對象是對文件對象的一種表現

代表input上傳時的文件獨享對象

IE9中沒有這個對象,所以無法操作文件

5.CSS 重排和重繪之間有什么區別?

哪些 CSS 屬性會導致重排及重繪?

重繪是一個元素的外觀變化所引發的瀏覽器行為;

重排是引起DOM樹重新計算的行為;

1、回流/重排
渲染樹的一部分必須要更新且節點的尺寸發生了變化,會觸發重排操作。每個頁面至少在初始化的時候會有一次重排操作。
2、重繪
部分節點需要更新,但沒有改變其形狀,會觸發重繪操作。
會觸發重繪或回流/重排的操作
1、添加、刪除元素(回流+重繪)
2、隱藏元素,display:none(回流+重繪),visibility:hidden(只重繪,不回流)
3、移動元素,如改變top、left或移動元素到另外1個父元素中(重繪+回流)
4、改變瀏覽器大小(回流+重繪)
5、改變瀏覽器的字體大小(回流+重繪)
6、改變元素的padding、border、margin(回流+重繪)
7、改變瀏覽器的字體顏色(只重繪,不回流)
8、改變元素的背景顏色(只重繪,不回流)

6. 什么是 CSS 選擇器權重以及它如何工作?

說說計算 CSS 選擇器權重的算法。

 4個等級的定義如下: 第一等:代表內聯樣式,如: style=””,權值為1000。 第二等:代表ID選擇器,如:#content,權值為100。 第三等:代表類,偽類和屬性選擇器,如.content,權值為10。 第四等:代表類型選擇器和偽元素選擇器,如div p,權值為1。 例如上圖為例,其中#NAV為二等選擇器,.ACTIVE為三等選擇器,UL、LI和A為四等選擇器。則整個選擇器表達式的特殊性的值為1*100+1*10+3*1=113
注意:通用選擇器(*),子選擇器(>)和相鄰同胞選擇器(+)並不在這四個等級中,所以他們的權值都為0。

7.CSS 中的 pixel 與硬件/物理中的 pixel 有何不同?

提示:像素不是像素不是像素 – ppk。

css里所有的px都是css pixel,其顯示大小是相對的而不是絕對的,是設備相關的。
而css pixel * devicePixelRatio = 實際屏幕像素。
(平時電腦屏幕的devicePixelRatio都是1所以感覺不到,會讓你誤以為css里的px就是實際屏幕像素)
同時media query使用的px顯然也是css pixel。

8.什么是 sectioning 算法?

提示:它也被稱為 HTML5 大綱算法。特別是在構建具有語義結構的網站時非常重要。

在html5中有一個很重要的概念,叫做html5大綱算法(HTML5 Outliner),它的用途為用戶提供一份頁面的信息結構目錄。合理的使用HTML5元素標簽,可以生成一個非常清晰的文檔大綱。

HTML5大綱算法

我們可以通過各種工具去查看當前頁面,這里推薦使用一個測試工具:HTML5 Outliner,網址如下:https://gsnedders.html5.org/outliner/

1. 了解一個 section 和 div 的區別

①div元素在html5之前是最常用的最流行的標簽,但他本身是沒有任何語義的,它只不過是用來布局頁面和css樣式以及js樣式。

②在html5中 section 標簽並不是用來取代 div 的。他是具有語義的文檔標簽,在大綱規范中規定 session 至少要包含一個標題。也就是 section 標簽內至少包含一個h1~h6。

③如果是頁面布局,且不是 header、footer之類的專屬區域都應該使用div。

2. body\nav\section 都是需要有標題才規范,header和div則是不需要標題的。

3. section 和 nav 元素大綱要求有標題h1~h6,暗示 section 必須有才規范,而 nav 如果沒有標題,也是合理的。給他添加了標題會讓大綱更好看,所以我們可以添加完了再隱藏,就不會破壞布局了。(通過display:none;將其隱藏)

9.如果你用過 CSS Flex / CSS Grid(網格)布局,請說明你為什么要使用它?它為你解決了什么問題?

  • 使用 CSS Grid,百分比%和 fr 單位有何不同?
  • fr是一個相對尺寸單位,表示剩余空間做等分,此項分配到的百分比(如果只有一個項使用此單位,那就占剩余空間的100%,所以多個項聯合使用更有意義)
  • 使用 CSS flexbox,有時 flex-items/children 會不考慮 flex 容器設置的寬度/高度?為什么會這樣?
  • 可以使用 CSS Grid 創建 Masonry layout(瀑布流布局)嗎?如果可以,怎么做?
  • 解釋 CSS Grid 和 CSS flexbox 術語?
  • CSS Grid布局 (又名"網格"),是一個基於二維網格布局的系統,主要目的是改變我們基於網格設計的用戶接口方式
  • 浮動元素(float: left | right;)如何在 CSS Grid 和 flexbox 中渲染?

提示:等高的列,垂直居中,復雜網格等。

10.什么時候應該使用 CSS animations 而不是 CSS transitions ?你做出這個決定標准是什么?

11.如果你正在 Review CSS 代碼,那么你在代碼中經常遇到的問題是什么?

示例:使用魔性數字,如 width: 67px; 或使用 em 代替 rem 單位,在通用代碼之前編寫 media queries(媒體查詢),濫用 ID 和類等。

12.如何在 JavaScript 中檢測觸摸事件?

有三種情況,1為觸摸鼠標都可以使用 2為觸摸 3為鼠標

if((document.hasOwnProperty("ontouchstart")) && (document.hasOwnProperty("onmousedown"))){
}else if(document.body.ontouchstart !== undefined){
 
}else{
 
}
// document.body.ontouchstart !== undefined也是特性檢測的一種和那位兄弟的代碼差不多

mouse 事件是所有瀏覽器都支持的,一款普通的觸屏手機也可以通過 USB OTG 外接鼠標。

所以你只能判斷瀏覽器是否支持觸屏,這里的關鍵就是

【ontouchstart,ontouchmove,ontouchend,ontouchcancel】

支持觸屏的瀏覽器都會有這四個 touch 事件

window.onload = function() {
    if(document.hasOwnProperty("ontouchstart")) {
        alert("瀏覽器支持觸屏");
    }
    else {
        alert("瀏覽器不支持觸屏");
    }
};
  1. 你是否不看好檢測設備對觸摸事件的支持?如果是,為什么?
  2. 主流做法一般是通過 UserAgent 判斷。
  3. 比較觸摸事件和點擊事件。
  4. 在觸摸設備上,touch事件從手指剛碰到屏幕就會觸發,而click事件則要晚一會兒才被觸發。觸發順序如下:

    touchstart
    mouseover
    mousemove(一次)
    mousedown
    mouseup
    click
    touchend
    所以,如果想提升web在觸摸設備下的用戶體驗,讓用戶覺得響應非常迅速,應該對頁面的觸摸事件進行事件處理程序的注冊,而不應再關注click事件。

  5. 當設備同時支持觸摸和鼠標事件時,你認為這些事件的正確事件順序是什么或應該是什么?
  • touchstart
  • Zero or more touchmove events, depending on movement of the finger(s)
  • touchend
  • mousemove
  • mousedown
  • mouseup
  • click

13.為 script 標簽定義的 async 和 defer 屬性有什么用?

  • 現在我們有 HTTP/2 和 ES 模塊,它們真的很有用嗎?
  • 向html頁面中插入javascript代碼的主要方法就是通過script標簽。其中包括兩種形式,第一種直接在script標簽之間插入js代碼,第二種即是通過src屬性引入外部js文件。由於解釋器在解析執行js代碼期間會阻塞頁面其余部分的渲染,對於存在大量js代碼的頁面來說會導致瀏覽器出現長時間的空白和延遲,為了避免這個問題,建議把全部的js引用放在</body>標簽之前。

      script標簽存在兩個屬性,defer和async,因此script標簽的使用分為三種情況:

      1.<script src="example.js"></script>

       沒有defer或async屬性,瀏覽器會立即加載並執行相應的腳本。也就是說在渲染script標簽之后的文檔之前,不等待后續加載的文檔元素,讀到就開始加載和執行,此舉會阻塞后續文檔的加載;

      2.<script async src="example.js"></script>

         有了async屬性,表示后續文檔的加載和渲染與js腳本的加載和執行是並行進行的,即異步執行;

      3.<script defer src="example.js"></script>

             有了defer屬性,加載后續文檔的過程和js腳本的加載(此時僅加載不執行)是並行進行的(異步),js腳本的執行需要等到文檔所有元素解析完成之后,DOMContentLoaded事件觸發執行之前。

      下圖可以直觀的看出三者之間的區別:

     

      

     

      其中藍色代表js腳本網絡加載時間,紅色代表js腳本執行時間,綠色代表html解析。

      從圖中我們可以明確一下幾點:

      1.defer和async在網絡加載過程是一致的,都是異步執行的;

      2.兩者的區別在於腳本加載完成之后何時執行,可以看出defer更符合大多數場景對應用腳本加載和執行的要求;

      3.如果存在多個有defer屬性的腳本,那么它們是按照加載順序執行腳本的;而對於async,它的加載和執行是緊緊挨着的,無論聲明順序如何,只要加載完成就立刻執行,它對於應用腳本用處不大,因為它完全不考慮依賴。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM