五天六萬字,JavaScript 面試大全

1、什麼是編譯型語言?什麼是解釋型語言?JS 是什麼類型的語言?

一、編譯型語言

1. 解釋

程序在執行以前須要一個專門的編譯過程,把程序編譯成 爲機器語言的文件,運行時不須要從新翻譯,直接使用編譯的結果就好了。javascript

2. 優缺點

程序執行效率高,依賴編譯器,跨平臺性差些。css

3. 舉例

CC++ 都是編譯型語言。html

二、解釋型語言

1. 解釋

程序不須要編譯,程序在運行時才翻譯成機器語言,每執 行一次都要翻譯一次。前端

2. 優缺點

解釋型語言執行效率較低,且不能脫離解釋器運行,但它的跨平臺型比較容易,只需提供特定解釋器便可。vue

3. 舉例

pythonJS 都是解釋型語言。java

2、強制類型轉換 & 隱式類型轉換

一、JS 中有哪些強制類型轉換和隱式類型轉換?

1. 強制類型轉換

  • String()
  • Number()
  • Boolean()
  • parseInt()
  • parseFloat()

2. 隱式類型轉換

  • + string 轉爲數字
  • a + " " 轉爲字符串
  • !var 轉爲布爾值

類型轉換

3、基本數據類型和引用數據類型

一、區別

1. 做爲函數的參數時:

  1. 基本數據類型傳入的是數據的副本,原數據的更改不會影響傳入後的數據。
  2. 引用數據類型傳入的是數據的引用地址,原數據的更改會影響傳入後的數據。

2. 內存中的存儲位置:

  1. 基本數據類型存儲在棧中。
  2. 引用數據類型在棧中存儲了指針,該指針指向的數據實體存儲在堆中。

二、棧和堆在內存中的分配

棧和堆在內存中的分配

egvar a = {name: 'yuhua'} 變量存儲狀況

  1. 將這句代碼放入 代碼區域 Code Segment
  2. 將變量 a 放入 棧(Stack):本地變量、指針
  3. {name: 'yuhua'} 放入HeapTotal(堆):對象,閉包

三、symbol

1. symbol 做爲一個對象的鍵名時,如何獲取?

不能獲取 symbol 鍵:node

  1. for infor of 循環遍歷中,不會獲取 symbol 鍵;
  2. Object.keys()Object.getOwnPropertyNames()JSON.stringify() 方法獲取不到 symbol 鍵;

能獲取 symbol 鍵:python

  1. Object.getOwnPropertySymbols() 方法能夠獲取,返回一個數組;
  2. Reflect.ownKeys() 能夠獲取全部的鍵名,包括 symbol

2. symbol 的類型轉換

1)能夠轉換爲 string
const symbolKey = Symbol(123)
String(symbolKey) // "Symbol(123)"
symbolKey.toString() // "Symbol(123)"
複製代碼
2)能夠轉化爲布爾值
Boolean(symbolKey) // true
複製代碼
3)不能轉化爲數字(報錯)
Number(symbolKey)
Uncaught TypeError: Cannot convert a Symbol value to a number
    at Number (<anonymous>)
    at <anonymous>:1:1
複製代碼
4)轉化爲對象
b = Object(symbolKey)
Symbol {Symbol(123)}
  description: "123"
  __proto__: Symbol
    constructor: ƒ Symbol()
    description: "123"
    toString: ƒ toString()
    valueOf: ƒ valueOf()
    Symbol(Symbol.toPrimitive): ƒ [Symbol.toPrimitive]()
    Symbol(Symbol.toStringTag): "Symbol"
    get description: ƒ description()
    __proto__: Object
    [[PrimitiveValue]]: Symbol(123)
typeof b // "object"
b.constructor() // Symbol()
b instanceof Symbol // true
b instanceof Object // true
Object.prototype.toString.call(b) // "[object Symbol]"
複製代碼

四、字符串轉函數

1. eval()

let funcStr = "function test(value){alert(value)}";
let test = eval("(false || "+funcStr+")");
test("函數可以執行");
複製代碼

2. new Function()

function add(a, b) {
  return a + b;
}
//等價於
var add = new Function ('a', 'b', 'return a + b');
let funcStr = "function test(value){alert(value)}";
let funcTest = new Function('return '+funcStr);
funcTest()("函數也可以執行")
複製代碼

4、nullundefined 的區別

一、Null

  • null 表示一個"無"的對象,轉爲數值爲 0
  • 做爲函數的參數,表示該函數的參數不是對象;
  • 做爲對象原型鏈的終點。
  • Number(null)0
  • 5 + null5

二、Undefined

  • 變量被聲明瞭,可是沒有賦值,就等於 undefined
  • 調用函數時,應該提供的參數沒有提供,該參數等於 undefined
  • 對象沒有賦值屬性,該屬性的值爲 undefined
  • 函數沒有返回值時,默認返回 undefined
  • Number(undefined)NaN
  • 5 + undefinedNaN

5、typeofinstanceof 的區別

一、主要區別

  • typeof 表示對某個變量類型的檢測,基本數據類型除了 null 都能正常的顯示爲對應的類型,引用類型除了函數會顯示爲 function 外,其餘的都是會顯示爲 object
  • instanceof 用於檢測某個構造函數的原型對象在不在某個對象的原型鏈上。

二、typeofnull 的錯誤顯示

這只是 JS 存在的一個悠久 Bug。在 JS 的最第一版本中使用的是 32 位系統,爲了性能考慮使用低位存儲變量的類型信息,000 開頭表明是對象然而 null 表示爲全零,因此將它錯誤的判斷爲 object 。webpack

三、實現一個 instanceof

Object.getPrototypeOf()Object.getPrototypeOf() 方法返回指定對象的原型(內部[[ Prototype ]]屬性的值)。ios

function myInstance (left, right) {
	let proto = Object.getPrototypeOf(left) // Object.getPrototypeOf() 方法返回指定對象的原型(內部[[Prototype]]屬性的值)。
  	while(true) {
  		if (proto === null) return false
	    if (proto === right.prototype) return true
    	proto = Object.getPrototypeOf(proto)
  	}
}
複製代碼

驗證

myInstance([], Object) //true
myInstance(Map, Object) //true
myInstance(new Map(), Object) //true
myInstance(Map, Function) //true
myInstance(class {}, Function) //true
myInstance(1, Number) //true
myInstance('1', String) //true
複製代碼

6、this

一、描述下 this

對於函數而言指向最後調用函數的那個對象,是函數運行時內部自動生成的一個內部對象,只能在函數內部使用;對於全局來講,this 指向 window

二、函數內的 this 是在何時肯定的?

函數調用時,指向最後調用的那個對象

三、callapplybind 三者的區別

三個函數的做用都是將函數綁定到上下文中,用來改變函數中 this 的指向;三者的不一樣點在於語法的不一樣。

fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.apply(thisArg, [argsArray])
var bindFn = fun.bind(thisArg[, arg1[, arg2[, ...]]])
bindFn()
複製代碼

applycall 的區別是 call 方法接受的是若干個參數列表,而 apply 接收的是一個包含多個參數的數組。 而 bind() 方法建立一個新的函數, 當被調用時,將其 this 關鍵字設置爲提供的值,在調用新函數時,在任何提供以前提供一個給定的參數序列。

const name = 'window'
const sayName = function (param) {
    console.log('my name is:' + this.name + ',my param is ' + param)
}
sayName('window param') //my name is:window,my param is window param

const callObj = {
    name: 'call'
}
sayName.call(callObj, 'call param') //my name is:call,my param is call param

const applyObj = {
    name: 'apply'
}
sayName.apply(applyObj, ['apply param']) //my name is:apply,my param is apply param

const bindObj = {
    name: 'bind'
}
const bindFn = sayName.bind(bindObj, 'bind param')
bindFn() //my name is:bind,my param is bind param
複製代碼

四、this 的指向有哪幾種?

  1. 默認綁定:全局環境中,this 默認綁定到 window
  2. 隱式綁定:通常地,被直接對象所包含的函數調用時,也稱爲方法調用,this 隱式綁定到該直接對象。
  3. 隱式丟失:隱式丟失是指被隱式綁定的函數丟失綁定對象,從而默認綁定到 window。顯式綁定:經過 call()apply()bind() 方法把對象綁定到 this 上,叫作顯式綁定。
  4. new 綁定:若是函數或者方法調用以前帶有關鍵字 new,它就構成構造函數調用。對於 this 綁定來講,稱爲 new 綁定。

this 指向示意圖

五、箭頭函數的 this

  1. 箭頭函數沒有 this,因此須要經過查找做用域鏈來肯定 this 的值,這就意味着若是箭頭函數被非箭頭函數包含,this 綁定的就是最近一層非箭頭函數的 this
  2. 箭頭函數沒有本身的 arguments 對象,可是能夠訪問外圍函數的 arguments 對象。
  3. 不能經過 new 關鍵字調用,一樣也沒有 new.target 值和原型。

六、手動實現 callapplybind

1. call

Function.prototype.myCall = function (thisArg, ...args) {
    const fn = Symbol('fn') // 聲明一個獨有的 symbol 屬性,防止 fn 覆蓋已有屬性
  	thisArg = thisArg || window // 若沒有 this 傳入,則綁定 window 對象
  	thisArg[fn] = this // this 指向調用 call 的對象,即咱們要改變 this 指向的函數
  	const result = thisArg[fn](...args) // 執行當前函數
  	delete thisArg[fn] // 刪除咱們聲明的 fn
  	return result // 返回函數執行結果
}
複製代碼

2. apply

Function.prototype.myApply = function (thisArg, args) {
    const fn = Symbol('fn') // 聲明一個 symbol 
  	thisArg = thisArg || window // 設置 thisArg 
  	thisArg[fn] = this // this 指向改變
  	const result = thisArg[fn](...args) // 執行函數
  	delete thisArg[fn] // 刪除 fn
 	return result // 返回結果
}
複製代碼

3. bind

Function.prototype.myBind = function (thisArg, ...args) {
    const self = this
  	const fbound = function () {
    	self.apply(this instanceof self ? this : thisArg,args.concat(Array.prototype.slice.call(arguments)))
  	}
  	fbound.prototype = Object.create(self.prototype)
  	return fbound
}
複製代碼

七、判斷 this 指向

1. obj0.obj.test()

const a = 1
function test () {
    console.log(this.a)
}
const obj = {
    a: 2,
    test
}
const obj0 = {
    a: 3,
    obj 
}
obj0.obj.test() // 2
複製代碼

2. testcopy()

var a = 1
function test () {
    console.log(this.a)
}
const obj = {
    a: 2,
    test
}
const testCopy = obj.test
testCopy() // 1 
// this 指向是在函數執行時肯定
複製代碼

3. 在 setTimeout

var a = 1
function test () {
    console.log(this.a)
}
const obj = {
    a: 2,
    test
}
setTimeout(obj.test) // 1
// this 指向是在函數執行時肯定
複製代碼

7、JS 模塊化

一、模塊化發展歷程

  • IIFE 自執行函數
  • AMD 使用 requireJS 來編寫模塊化(依賴必須提早聲明好。)
  • CMD 使用 seaJS 來編寫模塊化(支持動態引入依賴文件。)
  • CommonJS nodeJs 中自帶的模塊化
  • UMD 兼容 AMDCommonJS 語法
  • webpack(require.ensure)webpack 2.x 版本中的代碼分割
  • ES ModulesES6 引入的模塊化,支持 import 來引入另外一個 js
  • script 標籤 type="module"

js 模塊化

二、AMDCMD 的區別

AMDCMD 最大的區別是對依賴模塊的執行時機處理不一樣,注意不是加載的時機或者方式不一樣,兩者皆爲異步加載模塊

  • AMD 推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊
  • CMD 推崇就近依賴,只有在用到某個模塊的時候再去 require

三、CommonJS 規範的特色

  1. 因此代碼都是運行在模塊做用域中,不會污染全局做用域
  2. 模塊是同步加載的,只有引入的模塊加載完成,纔會執行後面的操做
  3. 模塊在首次執行後就會緩存,再次加載只返回緩存的結果
  4. CommonJS 輸出的是值的拷貝,模塊內部再次改變也不會影響這個值(引用類型和基本類型有區別)

四、ES6 modules 規範有什麼特色

  1. 輸出使用 export
  2. 引入使用 import
  3. 能夠使用 export ... from ... 來達到一箇中轉的效果
  4. 輸入的模塊變量是不可從新賦值的。只是個可讀引用,可是能夠改寫屬性
  5. exportimport 命令處於模塊頂層,不能位於做用域內,處於代碼塊中,無法作靜態優化,違背了 ES6 模塊的設計初衷
  6. import 有提高效果,會提高到整個模塊的頭部,首先執行
  7. Babel 會把 export/import 轉化爲 exports/require 的形式,因此能夠使用 exportsimport

五、CommonJSES6 Modules 規範的區別

  1. CommonJS 模塊是運行時加載,ES6Modules 是編譯時加載
  2. CommonJS 輸出值的拷貝,ES6Modules 輸出值的引用(模塊內部改變會影響引用)
  3. CommonJS 導入模塊能夠是一個表達式(是使用 require() 引入),ES6Modules 導入只能是字符串
  4. CommonJS 中 this 指向當前模塊,ES6Modulesthis 指向 undefined
  5. ES6Modules 中沒有 argumentsrequiremoduleexports__filename__dirname 這些頂層變量

六、如何異步進行模塊的加載

AMDCMD 支持異步加載模塊

七、開發一個模塊須要考慮哪些問題?

  1. 安全性
  2. 封閉性
  3. 避免變量衝突
  4. 隔離做用域
  5. 公共代碼的抽離

八、node require(X) 引入的處理順序是什麼樣的?

  1. 若是 X 是內置模塊,返回該模塊,再也不繼續執行;
  2. 若是 X'./'、'/'、'../' 開頭,將根據 X 所在的父模塊,肯定 X 的絕對路徑: a. 將 X 當成文件,依次查找,存在,返回該文件,再也不繼續執行; b. 將 X 當成目錄,依次查找目錄下的文件,存在,返回該文件,再也不繼續執行;
  3. 若是 X 不帶有路徑: a. 根據 X 所在的父模塊,肯定 X 可能的安裝目錄 b. 依次在每一個目錄中,將 X 當成文件名或者目錄名加載
  4. 拋出 not found 錯誤

九、node 中相互引用

有個 a.jsb.js 兩個文件,互相引用

1. CommonJS

{
  id: '...',
  exports: { ... },
  loaded: true, parent: null, filename: '', children: [], paths: []
}
複製代碼

CommonJS 的一個模塊,就是一個腳本文件。require 命令第一次加載該腳本,就會執行整個腳本,而後在內存生成一個對象。之後須要用到這個模塊的時候,就會到 exports 屬性上面取值。即便再次執行 require 命令,也不會再次執行該模塊,而是到緩存之中取值。

CommonJS 重要特性是加載時執行,腳本代碼在 require 時,所有執行。

CommonJS 的作法是,一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。

// a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 執行完畢');
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 執行完畢');
複製代碼
  1. a.js 腳本先輸出一個 done 變量,而後加載另外一個腳本文件 b.js。注意,此時 a.js 代碼就停在這裏,等待 b.js 執行完畢,再往下執行。
  2. b.js 執行到第二行,就會去加載 a.js,這時,就發生了"循環加載"。系統會去 a.js 模塊對應對象的 exports 屬性取值,但是由於 a.js 尚未執行完,從 exports 屬性只能取回已經執行的部分,而不是最後的值。
  3. a.js 已經執行的部分,只有一行。
exports.done = false;
複製代碼
  1. 所以,對於 b.js 來講,它從 a.js 只輸入一個變量 done,值爲 false
  2. b.js 接着往下執行,等到所有執行完畢,再把執行權交還給 a.js。因而,a.js 接着往下執行,直到執行完畢。咱們寫一個腳本 main.js,並運行,驗證這個過程。
// main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
// 運行
// 在 b.js 之中,a.done = false
// b.js 執行完畢
// 在 a.js 之中,b.done = true
// a.js 執行完畢
// 在 main.js 之中, a.done=true, b.done=true
複製代碼
  1. 上面的代碼證實了兩件事。一是,在 b.js 之中,a.js 沒有執行完畢,只執行了第一行。二是,main.js 執行到第二行時,不會再次執行 b.js,而是輸出緩存的 b.js 的執行結果,即它的第四行。

2. ES6

ES6 模塊的運行機制與 CommonJS 不同,它遇到模塊加載命令 import 時,不會去執行模塊,而是隻生成一個引用。等到真的須要用到時,再到模塊裏面去取值。

ES6 模塊是動態引用,不存在緩存值的問題,並且模塊裏面的變量,綁定其所在的模塊。ES6 模塊不會緩存運行結果,而是動態地去被加載的模塊取值,以及變量老是綁定其所在的模塊。

ES6 根本不會關心是否發生了"循環加載",只是生成一個指向被加載模塊的引用,須要開發者本身保證,真正取值的時候可以取到值。

// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
  counter++;
  return n == 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {
  return n != 0 && even(n - 1);
}
複製代碼

按照 CommonJS 規範,是無法加載的,是會報錯的,可是 ES6 就能夠執行。 之因此可以執行,緣由就在於 ES6 加載的變量,都是動態引用其所在的模塊。只要引用是存在的,代碼就能執行。

$ babel-node
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
> m.even(20)
true
> m.counter
17
複製代碼

上面代碼中,參數 n10 變爲 0 的過程當中,foo() 一共會執行 6 次,因此變量 counter 等於 6。第二次調用 even() 時,參數 n20 變爲 0foo() 一共會執行 11 次,加上前面的 6 次,因此變量 counter 等於17

8、JS 事件

一、什麼是事件委託

事件委託/事件代理:通常來講就是經過事件冒泡把一個元素的響應事件的函數代理到它的父層或者更外層元素上。

缺點

  1. 只能支持冒泡的事件,對於不冒泡的事件沒法代理( focus/blur )
  2. 全部事件都代理容易出錯,建議就近委託
  3. 內部元素層級過多,容易被某層阻止掉

二、documentwindowhtmlbody 的層級關係

window > document > html > body

  • windowBOM 的核心對象,一方面用來獲取或者設置瀏覽器的屬性和行爲,一方面做爲一個全局對象;
  • document 是一個跟文檔相關的對象,擁有一些操做文檔內容的功能;
  • html 元素 和 document 元素對象是屬於 html 文檔的 DOM 對象。

三、addEventListener 函數的第三個參數是什麼?

1. 當爲 boolean 時:

  • 第三個參數涉及到是冒泡仍是捕獲;
    • true 時是捕獲,爲 false 時是冒泡。

2. 當爲 Object 時:

  • captureBoolean,表示 listener 會在該類型的事件捕獲階段傳播到該 EventTarget 時觸發。
  • onceBoolean,表示 listener 在添加以後最多隻調用一次。若是是 truelistener 會在其被調用以後自動移除。
  • passiveBoolean,設置爲 true 時,表示 listener 永遠不會調用 preventDefault()。若是 listener 仍然調用了這個函數,客戶端將會忽略它並拋出一個控制檯警告。
  • mozSystemGroup:只能在 XBL 或者是 Firefox' chrome 使用,這是個 Boolean,表示 listener 被添加到 system group

四、冒泡和捕獲的具體過程

冒泡:當給某個元素綁定了事件以後,這個事件會依次在它的父級元素中被觸發; 捕獲:從上層向下層傳遞,與冒泡相反。

<!-- 會依次執行 button li ul -->
<ul onclick="alert('ul')">
  <li onclick="alert('li')">
    <button onclick="alert('button')">點擊</button>
  </li>
</ul>
<script> window.addEventListener('click', function (e) { alert('window') }) document.addEventListener('click', function (e) { alert('document') }) </script>
複製代碼

冒泡:button -> li -> ul -> document -> window 捕獲:window -> document -> ul -> li -> button

五、有哪些不冒泡的事件

  • onblur
  • onfoucs
  • onmouseenter
  • onmouseleave

六、原生自定義事件

1. 有哪些自定義事件

  • 使用 Event
  • 使用 customEvent
  • 使用 document.createEvent('customEventName')initEvent()

2. 建立自定義事件

1)使用 Event
let myEvent = new Event('my_event_name')
複製代碼
2)使用 customEvent
let myEvent = new CustomEvent('my_event_name', {
    detail: {
    // 須要傳遞的參數
    // 在監聽的回調函數中獲取到:event.detail
  }
})
複製代碼
3)使用 document.createEvent('CustomEvent')initEvent()
let myEvent = document.createEvent('CustomEvent')
myEvent.initEvent(
    // event_name 是事件名
  // canBubble 是否冒泡
  // cancelable 是否能夠取消默認行爲
)
複製代碼

3. 事件的監聽

dom.addEventListener('my_custom_name', function(e) {})
複製代碼

4. 事件的觸發

dispatchEvent(myEvent)
複製代碼

5. 案例

// 1.
let myEvent = new Event('myEvent');
// 2.
let myEvent = new CustomEvent('myEvent', {
  detail: {
    name: 'lindaidai'
  }
})
// 3.
let myEvent = document.createEvent('CustomEvent');
myEvent.initEvent('myEvent', true, true)
let btn = document.getElementsByTagName('button')[0]
btn.addEventListener('myEvent', function (e) {
  console.log(e)
  console.log(e.detail)
})
setTimeout(() => {
  btn.dispatchEvent(myEvent)
}, 2000)
複製代碼

9、JS 內部函數和閉包

一、什麼是閉包

MDN:一個函數和對其周圍狀態(lexical environment,詞法環境)的引用捆綁在一塊兒(或者說函數被引用包圍),這樣的組合就是閉包(closure)。

簡單來講:可以讀取其餘函數內部變量的函數就是閉包。

for (var i = 0; i < 10; i++) {
  (function (i) {
    setTimeout(() => {
      console.log(i)
    }, 1000)
  })(i)
}
複製代碼

二、什麼是內部函數

通常來講在一個函數內部定義另一個函數,這樣的函數就是內部函數。

三、閉包的做用?

  1. 使用閉包能夠訪問函數中的變量;
  2. 能夠使變量長期保存在內存中。

四、內存泄露

1. 形成內存泄露的狀況:

  1. 循環引用
  2. 自動類型裝箱轉換
  3. 某些 DOM 操做

(44. 閉包

2. 內存泄露解決方案:

  1. 低於類型轉換,能夠經過顯示類型轉換的方式來避免;
  2. 避免事件致使的循環引用;
  3. 垃圾箱操做;
  4. 對於變量的手動刪除;

3. 內存泄露是內存佔用很大嗎?

不是,即便是 1byte 的內存,也叫內存泄露。

4. 程序中提示內存不足,是內存泄露嗎?

不是,着通常是無限遞歸函數調用,致使棧內存溢出。

5. 內存泄露是哪一個區域?

堆區。棧區不會泄露

6. 內存泄露的後果?

大多數狀況下,後果不是很嚴重。可是過多的 DOM 操做會使網頁執行速度變慢。

7. 跳轉網頁,內存泄露仍然存在嗎?

仍然存在,直到瀏覽器關閉。

10、EventLoop 的執行過程

一、簡述下 EventLoop 的執行過程

  • 整個 script 做爲一個宏任務進行執行;
  • 執行中同步代碼直接執行,宏任務進入宏任務隊列,微任務進入微任務隊列;
  • 當前宏任務執行完成以後,檢測微任務列表,有則進行微任務執行,直到微任務列表所有執行完;
  • 執行瀏覽器的 UI 線程渲染工做;
  • 檢查是否有 web worker 任務,有則執行;
  • 執行完本輪宏任務,回到第二步,依次循環,直到宏任務和微任務隊列都爲空。

EventLoop執行順序

二、requestAnimationFrame

1. 特徵

  1. 在從新渲染前調用。
  2. 極可能在宏任務以後不調用。

2. 爲何要在從新渲染前去調用?

由於 rAF 是官方推薦的用來作一些流暢動畫所應該使用的 API,作動畫不可避免的會去更改 DOM,而若是在渲染以後再去更改 DOM,那就只能等到下一輪渲染機會的時候才能去繪製出來了,這顯然是不合理的。

rAF 在瀏覽器決定渲染以前給你最後一個機會去改變 DOM 屬性,而後很快在接下來的繪製中幫你呈現出來,因此這是作流暢動畫的不二選擇。

三、requestIdleCallback

requestIdleCallback 方法將在瀏覽器的空閒時段內調用的函數排隊。這使開發者可以在主事件循環上執行後臺和低優先級工做,而不會影響延遲關鍵事件,如動畫和輸入響應。

1. 渲染有序進行

requestIdleCallback1

2. 渲染長期空閒

在這裏插入圖片描述 50ms 能夠確保用戶在無感知的延遲下獲得迴應。

四、EventLoop 循環注意點

  • 事件循環不必定每輪都伴隨着重渲染,可是若是有微任務,必定會伴隨着微任務執行。
  • 決定瀏覽器視圖是否渲染的因素不少,瀏覽器是很是聰明的。
  • requestAnimationFrame 在從新渲染屏幕以前執行,很是適合用來作動畫。
  • requestIdleCallback 在渲染屏幕以後執行,而且是否有空執行要看瀏覽器的調度,若是你必定要它在某個時間內執行,請使用 timeout 參數。
  • resizescroll 事件其實自帶節流,它只在 Event Loop 的渲染階段去派發事件到 EventTarget 上。

五、for 循環和 setTimeout

for 循環中加入 setTimeout

for (var i = 0; i < 10; i++) {
     setTimeout(() => {
       console.log(i)
     }, 1000)
 }
複製代碼

setTimeout

1. var 改爲 let

for (let i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i)
  }, 1000)
}
複製代碼

let

2. 使用自執行函數

for (var i = 0; i < 10; i++) {
  (function (i) {
    setTimeout(() => {
      console.log(i)
    }, 1000)
  })(i)
}
複製代碼

自執行函數

3. for 循環改爲 forEach 循環

[1,2,3,4].forEach(item => {
  setTimeout(() => {
    console.log(item)
  }, 1000)
})
複製代碼

forEach

4. setTimeout 傳參

for (var i = 0; i < arr.length; i++) {
  setTimeout((i) => {
    console.log(arr[i])
  }, 1000, i)
}
複製代碼

setTimeout 傳參

5. 直接輸出

for (var i = 0; i< 10; i++){
  setTimeout(console.log(i),1000);
}
複製代碼

直接輸出

11、JS 中的 letconstvar

一、JS 中有幾種定義變量的方法?

  • let
  • const
  • var
  • class
  • import
  • function

二、letconstvar 有什麼區別?

var let const
沒有塊級做用域 有塊級做用域 有塊級做用域
聲明全局變量在 window (全局屬性下) 全局變量不在全局屬性下 全局變量不在全局屬性下
重定義變量不會報錯 會報錯 會報錯
聲明變量 聲明變量 聲明一個常量
存在變量提高 不存在變量提高 不存在變量提高
聲明以後隨時賦值 聲明以後隨時賦值 聲明以後當即賦值

三、const 定義常量可不能夠修改?

  1. const 定義基礎類型是不能夠修改的;
  2. const 定義引用類型是能夠修改引用類型裏面的值。

四、若是我想 const 定義引用類型也不能改變它的值該怎麼作?

  1. Object.freeze
  2. 代理( proxy/Object.defineProperty );
  3. 修改對象的 configurablewritable 屬性。

五、如何在 ES5 的狀況下實現 letconst

1. 實現 let

能夠經過自執行函數。

2. 實現 const

能夠經過 Object.defineProperty() 實現,設置 writable

12、JS 數組

一、ES6 新增數組方法

Array.from()Array.of()copyWithin()find()findIndex()fill()entries()keys()values()includes()

二、ES5 新增數組方法

forEach()map()filter()some()every()indexOf()lastIndexOf()reduce()reduceRight()

三、數組的這些方法,哪些能改變原數組?

copyWithin()fill()pop()push()reverse()shift()sort()splice()

四、someevery 有什麼區別?

從中文含義能看出來,some 是某些,every 是每個,它們都返回一個 Boolean 值。

五、數組裏面有 10 萬條數據,取第一個元素和第 10 萬個元素哪一個用時長?

用時基本上同樣,由於 js 裏面沒有數組類型,數組其實也是一個對象,keyvalue

六、數組去重你有幾種方法?

1. 多層循環遍歷法

  • 雙重 for 循環;
  • 遞歸循環。

2. 利用語法自身鍵不可重複性或者API去重

  • ES6 Set 去重;
  • 新建空對象去重;
  • 單層循環 + filter/includes/indexOf
  • 單層循環 + MapObject 去重。

七、for 循環和 forEach 的性能哪一個更好一點?

for 循環的性能更好

  • for 循環沒有任何額外的函數調用棧和上下文;
  • forEach 不是普通的 for 循環的語法糖,還有諸多參數和上下文須要在執行的時候考慮進來,這裏可能拖慢性能。

八、sort 排序是按照什麼方式來排序的?

默認排序順序是在將元素轉換爲字符串,而後比較它們的 UTF-16 代碼單元值序列時構建的。

九、多維數組轉爲一維數組

  • reduce 遞歸實現
  • joinsplit 實現
  • 遞歸遍歷
  • flat 方法
  • toStringsplit 實現
  • 廣度優先遍歷/深度優先遍歷

十、廣度優先遍歷和深度優先遍歷如何實現

JS深度優先遍歷和廣度優先遍歷

1. 深度優先遍歷

  • 訪問頂點 v
  • 依次從 v 的未被訪問的鄰接點出發,對圖進行深度優先遍歷;直至圖中和 v 有路徑相通的頂點都被訪問;
  • 若此時途中尚有頂點未被訪問,則從一個未被訪問的頂點出發,從新進行深度優先遍歷,直到全部頂點均被訪問過爲止。
const depth = (node) => {
    let stack = []
    let nodes = []
    if (node) {
        stack.push(node)
        while (stack.length) {
            //每次取最後一個
            let item = stack.pop()
            let children = item.children || []
            nodes.push(item)
            //判斷children的長度
            for (let i = children.length - 1; i >= 0; i--) {
                stack.push(children[i])
            }
        }
    }
    return nodes
}
複製代碼

2. 廣度優先遍歷

  • 建立一個隊列,並將開始節點放入隊列中;
  • 若隊列非空,則從隊列中取出第一個節點,並檢測它是否爲目標節點;
  • 如果目標節點,則結束搜尋,並返回結果;
  • 若不是,則將它全部沒有被檢測過的字節點都加入隊列中;
  • 若隊列爲空,表示圖中並無目標節點,則結束遍歷。
const breadth = (node) => {
    let nodes = []
    let stack = []
    if (node) {
        stack.push(node)
        while (stack.length) {
            //取第一個
            let item = stack.shift()
            let children = item.children || []
            nodes.push(item)
            for (let i = 0; i < children.length; i++) {
                stack.push(children[i])
            }
        }
    }
    return nodes
}
複製代碼

十一、實現一個 reduce

Array.prototype.myReduce = function (fn, init) {
    if (!init && this.length === 0) { // 若是數組長度爲0
        return this
    }
    let start = 1, pre = this[0]; // 從數組第二個開始下標爲1
    if (init !== undefined) { // 若是 init 字段存在,從第一個開始,下標爲 0
        start = 0;
        pre = init;
    }
    for (let i = start; i < this.length; i++) { // 循環
        let current = this[i]
        pre = fn.call(this, pre, current, i, this) // 把每次的 reduce 的值返回
    }
    return pre
}
複製代碼

十二、實現一個數組隨機打亂的算法

function disOrder2 (arr) {
    for (let i = 0; i < arr.length; i++) { // 遍歷
        const randomIndex = Math.floor(Math.random() * ary.length) // 生成隨機數
        swap(arr, i, randomIndex)
    }
}
function swap(arr, i, _i) { // 交換
    const tem = arr[i]
    arr[i] = arr[_i]
    arr[_i] = tem  
}
arr = [1,2,3,4,5,6,7,8]
disOrder(arr)
console.log(arr)
複製代碼

1三、給一串數字增長逗號分隔

1. 正則

num.replace(/(\d)(?=(\d{3})+(\.|$))/g, "$1,")
複製代碼

2. 遍歷

function formatNumber(num) {
  if (!num) return "";
  let [int, float] = num.split(".");
  let intArr = int.split("");
  let result = [];
  let i = 0;
  while (intArr.length) {
    if (i !== 0 && i % 3 === 0) {
      result.unshift(intArr.pop() + ",");
    } else {
      result.unshift(intArr.pop());
    }
    i++;
  }
  return result.join("") + "." + (float ? float : "");
}
複製代碼

1四、mapfindeverysomeforEach 等方法的第二個參數是幹什麼的?

arr.every(callback(element[, index[, array]])[, thisArg])
複製代碼
  • thisArg

執行 callback 時使用的 this 值。

十3、for infor of 有什麼區別?

比較 for in for of
不一樣點 能夠遍歷普通對象
遍歷出數組的原型對象
能夠遍歷出數組自身屬性
遍歷出來的值是 key
不能夠遍歷 map/set
不能夠迭代 generators
IE 支持
不能遍歷普通對象
不會遍歷出原型對象
不會遍歷自身屬性
遍歷出來的值是 value
能夠遍歷 map/set
能夠迭代generators
IE 不支持
相同點 能夠遍歷數組
能夠 break 中斷遍歷
能夠遍歷數組
能夠 break 中斷遍歷

十4、Promise

一、如何實現一個 sleep 函數(延遲函數)

經過 promisesetTimeout 來簡單實現

/** * 延遲函數 * @param {Number} time 時間 */
function sleep (time = 1500) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(true)
        }, time)
    })
}
複製代碼

二、promise 構造函數、then 方法、catch 方法、finally 方法哪一個異步哪一個同步?

promise 構造函數是同步執行的,thencatchfinally 方法是異步執行的。

三、如何取消一個 promise

取消一個promise

1. 使用 promise.race()

  • Promise.race(iterable)

iterable 參數裏的任意一個子 promise 被成功或失敗後,父 promise 立刻也會用子 promise 的成功返回值或失敗詳情做爲參數調用父 promise 綁定的相應句柄,並返回該 promise 對象。

/** * @author guoqiankunmiss */
//封裝一個取消promise的函數,使用promise.race的特性
function stopPromise (stopP) {
	let proObj = {};
	let promise = new Promise((resolve, reject) => {
		proObj.resolve = resolve;
		proObj.reject = reject;
	})
	proObj.promise = Promise.race([stopP, promise])
	return proObj
}
//一個5秒鐘以後執行的.then方法的promise
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(123);
    }, 5000);
});
//調用函數
let obj = stopPromise(promise);
//收集返回值
obj.promise.then(res => {
    console.log(res);
});
//兩秒鐘以後進行取消promise操做
setTimeout(() => {
	obj.resolve("Promise 請求被取消了!");
}, 2000)
複製代碼

四、多個 promise 如何獲取第一個成功promise

多個Promise中獲取第一個成功的Promise

1. Promise.all 改進

利用 promise.all 的特性,遍歷 promise 數組,根據返回值進行判斷,當成功的時候,轉爲 reject 返回,當失敗的時候轉爲 resolve 繼續執行。

//第一個成功的Promise
function firstProSuccess (allProMise) {
  //遍歷promise數組,根據返回值進行判斷,當成功的時候,轉爲reject返回,當失敗的時候轉爲resolve繼續執行。
  return Promise.all(allProMise.map(item => {
    return item.then(
      res => Promise.reject(res),
      err => Promise.resolve(err)
    )
  })).then(
    errors => Promise.reject(errors),
    val => Promise.resolve(val)
  )
}
複製代碼

2. Promise.any

  • Promise.any(iterable)

接收一個 Promise 對象的集合,當其中的一個 promise 成功,就返回那個成功的 promise 的值。

缺點:有兼容問題

五、多個 promise,全部的 promise 都取得返回結果(無論成功/失敗都要返回值)

1. Promise.all 改進

和上面原理相似,只不過是當成功的時候不進行操做,當 reject 時進行 resolve 操做

2. Promise.allSettled()

  • Promise.allSettled(iterable)

返回一個在全部給定的 promise 都已經 fulfilledrejected 後的 promise

缺點:有兼容問題

六、說說 promise 的靜態方法有哪些?

1. Promise.all(iterable)

接收一個 promise 數組對象(可迭代的 promise 實例對象),所有成功時,返回全部 promise 的數組集合;當其中一個失敗時,返回當前失敗的 promise 對象。

2. Promise.allSettled(iterable)

接收一個 promise 數組對象,所有完成時(無論成功/失敗)返回新的 promise 數組集合

3. Promise.any(iterable)

接收一個 promise 數組對象,當其中任何一個成功時,返回成功的 promise

4. Promise.race(iterable)

接收一個 promise 數組對象,當其中任意一個成功/失敗時,返回該 promise

5. Promise.reject(reason)

返回一個狀態爲失敗的 Promise 對象。

6. Promise.resolve(value)

返回一個狀態由給定 value 決定的 Promise 對象。

7. Promise.finally(onFinally)

在當前 promise 運行完畢後被調用,不管當前 promise 的狀態是完成( fulfilled )仍是失敗( rejected )

8. Promise.try(f)

接收一個函數,返回一個 promise

爲全部操做提供了統一的處理機制,因此若是想用 then 方法管理流程,最好都用 Promise.try 包裝一下。

  • 更好的錯誤處理
  • 更好的互操做性
  • 易於瀏覽

Promise-try

七、Promise.then 的第二個參數有了解嗎?和 .catch 有什麼區別?

then() 方法返回一個 Promise

它最多須要有兩個參數Promise 的成功和失敗狀況的回調函數。

p.then(onFulfilled[, onRejected]);
p.then(value => {
  // fulfillment
}, reason => {
  // rejection
});
複製代碼

第二個參數也是一個函數,是對失敗狀況的回調函數。

then 第二個參數 catch
then 方法的參數 Promise 的實例方法
then 的第一個參數拋出異常捕獲不到 then 的第一個參數拋出異常能夠捕獲
是一個函數 本質是 then 方法的語法糖
若是第二個參數和 catch 同時存在,promise 內部報錯,第二個參數能夠捕獲 此時,catch 捕獲不到,第二個參數不存在,catch 纔會捕獲到
不建議使用 建議使用 catch 進行錯誤捕獲

八、Promise.resolve 有幾種狀況?

1. 參數是一個 Promise 實例

參數是 Promise 實例,那麼 Promise.resolve 將不作任何修改、原封不動地返回這個實例。

2. 參數是一個 thenable 對象

Promise.resolve() 方法會將這個對象轉爲 Promise 對象,而後就當即執行 thenable 對象的 then() 方法。

3. 參數不是具備 then() 方法的對象,或根本就不是對象

若是參數是一個原始值,或者是一個不具備 then() 方法的對象,則 Promise.resolve() 方法返回一個新的 Promise 對象,狀態爲 resolved

4. 不帶有任何參數

直接返回一個 resolved 狀態的 Promise 對象。

九、若是 .then 中的參數不是函數,那會怎樣?

Promise.resolve(1)
    .then(2)
    .then(console.log)
// 1
複製代碼

若是 .then 中的參數不是函數,則會在內部被替換爲 (x) => x,即原樣返回 promise 最終結果的函數。

十、若是 .finally 後面繼續跟了個 .then,那麼這個 then 裏面的值是什麼?

Promise.resolve('resolve')
  .finally(() => {
    console.log('this is finally')
    return 'finally value'
  })
  .then(res => {
    console.log('finally後面的then函數, res的值爲:', res)
  })
// this is finally
複製代碼

finally 後面的 then 函數, res 的值爲: resolve

  1. finally 的回調函數中不接收任何參數;
  2. promise 結束時,不管結果是 fulfilled 或者是 rejected,都會執行 finally 回調函數;
  3. finally 返回的是一個上一次的 Promise 對象值。

十一、.all.race 在傳入的數組有第一個拋出異常的時候,其餘異步任務還會繼續執行嗎?

會的,會繼續執行,只是不會在 then / catch 中表現出來。

瀏覽器執行下面代碼,能夠看出當報錯的時候 console 仍是會繼續執行的,只是在 對應的回調函數裏面沒有表現出來。

function sleep (n) {
    return new Promise((resolve, reject) => {
        console.log(n)
        Math.random() > 0.5 ? reject(n) : resolve(n)
    }, n % 2 === 0 ? 1000 * n : 1000)
}
Promise.all([sleep(1), sleep(2), sleep(3)])
  .then(res => console.log('all res: ', res))
  .catch(err => console.log('all err:', err))
Promise.race([sleep(1), sleep(2), sleep(3)])
  .then(res => console.log('race res: ', res))
  .catch(err => console.log('race err:', err))
複製代碼

十二、.all 是併發的仍是串行的?

是併發的,可是返回值和 promise.all 中接收到的數組順序同樣。

1三、promise 爲何能夠進行鏈式調用

由於 thencatchfinally 方法會返回一個新的 promise,因此容許咱們進行鏈式調用。

1四、async/await

1. 實現原理

async 函數是基於 generator 實現,因此涉及到 generator 相關知識。 在沒有async 函數以前,一般使用 co 庫來執行 generator,因此經過 co 咱們也能模擬 async 的實現。

2. 簡單實現

1)co
function Asyncfn() {
  return co(function*() {
    //.....
  });
}
function co(gen) {
  return new Promise((resolve, reject) => {
    const fn = gen();
    function next(data) {
      let { value, done } = fn.next(data);
      if (done) return resolve(value);
      Promise.resolve(value).then(res => {
        next(res);
      }, reject);
    }
    next();
  });
}
複製代碼
2)Generator 函數和自執行器
function spawn(genF) {
    return new Promise(function(resolve, reject) {
        const gen = genF();
        function step(nextF) {
            let next;
            try {
                next = nextF();
            } catch (e) {
                return reject(e);
            }
            if (next.done) {
                return resolve(next.value);
            }
            Promise.resolve(next.value).then(
                function(v) {
                    step(function() {
                        return gen.next(v);
                    });
                },
                function(e) {
                    step(function() {
                        return gen.throw(e);
                    });
                }
            );
        }
        step(function() {
            return gen.next(undefined);
        });
    });
}
複製代碼

十5、說下 JSON.stringifyJSON.parse

一、JSON.stringify

定義:將一個 JavaScript 對象或值轉換爲 JSON 字符串。 參數:有三個參數

JSON.stringify(value[, replacer [, space]])
複製代碼
  1. replacer

replacer 參數能夠是一個函數或者一個數組。 做爲函數,它有兩個參數,鍵( key )和值( value ),它們都會被序列化。 replacer 是一個數組,數組的值表明將被序列化成 JSON 字符串的屬性名。 2. space space 參數用來控制結果字符串裏面的間距。 若是是一個數字, 則在字符串化時每一級別會比上一級別縮進多這個數字值的空格; 若是是一個字符串,則每一級別會比上一級別多縮進該字符串。

二、JSON.parse

定義:用來解析 JSON 字符串。 參數:有兩個參數

JSON.parse(text[, reviver])
複製代碼
  1. reviver

轉換器, 若是傳入該參數(函數),能夠用來修改解析生成的原始值。

特性

  1. 轉換值若是有 toJSON() 方法,該方法定義什麼值將被序列化。
  2. 非數組對象的屬性不能保證以特定的順序出如今序列化後的字符串中。
  3. 布爾值、數字、字符串的包裝對象在序列化過程當中會自動轉換成對應的原始值。
  4. undefined、任意的函數以及 symbol 值,在序列化過程當中會被忽略(出如今非數組對象的屬性值中時)或者被轉換成 null(出如今數組中時)。函數、undefined 被單獨轉換時,會返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined)
  5. 對包含循環引用的對象(對象之間相互引用,造成無限循環)執行此方法,會拋出錯誤。
  6. 全部以 symbol 爲屬性鍵的屬性都會被徹底忽略掉,即使 replacer 參數中強制指定包含了它們。
  7. Date 日期調用了 toJSON() 將其轉換爲了 string 字符串(同Date.toISOString()),所以會被當作字符串處理。
  8. NaNInfinity 格式的數值及 null 都會被當作 null
  9. 其餘類型的對象,包括 Map/Set/WeakMap/WeakSet,僅會序列化可枚舉的屬性。

十6、=====Object.is()

一、區別

  • == 兩邊值類型不一樣的時候,先進行類型轉換,在比較
  • === 不進行類型轉換,直接值比較
  • Object.is(val1, val2) 判斷兩個值是否爲同一值

二、== 類型轉換是怎麼轉換的?

  1. 若是類型不一樣,進行類型轉換
  2. 判斷比較的是不是 null 或者是 undefined,若是是,返回 true
  3. 判斷類型是否爲 string 或者 number,若是是,將 string 轉換爲 number
  4. 判斷其中一方是否爲 boolean,若是是,將其中一方轉爲 number 在進行判斷
  5. 判斷其中一方是否爲 object,且另一方是 stringnumbersymbol,若是是,將 object 轉爲原始類型進行判斷(valueOf() 方法)
  6. 若是有一個是 NaN,則直接返回 false
  7. 若是兩個都是對象,則比較是否指向同一個對象

==比較

三、[] == ![] 的值爲何?

答案:爲 true

轉換步驟

  1. ! 運算符優先級最高,![] 會被轉換爲 false,所以此時爲 [] == false
  2. 根據第四條,其中一方爲 boolean,把 boolean 轉爲 number,因此此時爲 [] == 0
  3. 再根據第五條,把數組 [] 轉爲原始類型,調用數組的 toString() 方法,[].toString() = '',因此此時爲 '' == 0
  4. 再根據第三條,把 string 轉爲 number'' 轉爲 number 爲 0,因此此時 0 == 0
  5. 兩邊數據類型相同 0 == 0,爲 true

四、Object.is() 判斷兩值相等的狀況

不會進行強制類型轉換

  • 都是 undefined
  • 都是 null
  • 都是 truefalse
  • 都是相同長度的字符串且相同字符按相同順序排列
  • 都是相同對象(意味着每一個對象有同一個引用)
  • 都是數字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 或都是非零並且非 NaN 且爲同一個值

十7、防抖和節流

一、什麼是防抖和節流

防抖:是屢次執行改成最後一次執行 節流:是將屢次執行改成每隔一段時間執行

二、簡單實現防抖和節流

1. 防抖實現

思路: 觸發高頻事件後 n 秒內函數只會執行一次,若是 n 秒內高頻事件再次被觸發,則從新計算時間,每次觸發事件時都取消以前的延時調用方法

function debounce (fn, time = 500) {
  let timeout = null; // 建立一個標記用來存放定時器的返回值
  return function () {
    clearTimeout(timeout) // 每當觸發時,把前一個 定時器 clear 掉
    timeout = setTimeout(() => { // 建立一個新的 定時器,並賦值給 timeout
      fn.apply(this, arguments)
    }, time)
  }
}
function testDebounce () {
  console.log('測試防抖')
}
const inp = document.getElementById('testInp')
inp.addEventListener('input', debounce(testDebounce))
複製代碼

2. 節流實現

高頻事件觸發,但在 n 秒內只會執行一次,因此節流會稀釋函數的執行頻率,每次觸發事件時都判斷當前是否有等待執行的延時函數

function throttle (fn, time = 100) {
  let timeout;
  return function () {
    let context = this
    let args = arguments
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null
        fn.apply(context, args)
      }, time)
    }
  }
}
function testThro () {
  console.log('測試節流')
}
const inp = document.getElementById('testInp')
inp.addEventListener('input', throttle(testThro))
複製代碼

十8、cookiesessionStoragelocalStorage

一、三者區別

  • cookie 用來保存登陸信息,大小限制爲 4KB 左右
  • localStorageHtml5 新增的,用於本地數據存儲,保存的數據沒有過時時間,通常瀏覽器大小限制在 5MB
  • sessionStorage 接口方法和 localStorage 相似,但保存的數據的只會在當前會話中保存下來,頁面關閉後會被清空。
名稱 生命期 大小限制 與服務器通訊 是否能夠跨域
cookie 通常由服務器生成,可設置失效時間。若是在瀏覽器端生成 Cookie,默認是關閉瀏覽器後失效 4KB 每次都會攜帶在 HTTP 頭中,若是使用 cookie 保存過多數據會帶來性能問題 通常不可,相同 domain 下能夠容許接口請求攜帶 cookie
localStorage 除非被清除,不然永久保存 5MB 僅在瀏覽器中保存,不與服務器通訊 不可
sessionStorage 僅在當前會話下有效,關閉頁面或瀏覽器後被清除 5MB 僅在瀏覽器中保存,不與服務器通訊 不可

二、localStorage 進行怎麼進行跨域存儲?

localStorage 是不能夠進行跨域操做的,可是想進行跨域操做能夠使用 postMessagewebsocket 進行變相的跨域操做。

十9、瀏覽器跨域問題

一、什麼是瀏覽器同源策略?

同源策略是一個重要的安全策略,它用於限制一個 origin 的文檔或者它加載的腳本如何能與另外一個源的資源進行交互,它能幫助阻隔惡意文檔,減小可能被攻擊的媒介。

所謂同源策略,是指只有在地址的:

  1. 協議名
  2. 域名
  3. 端口名

均同樣的狀況下,才容許訪問相同的 cookielocalStorage,以及訪問頁面的 DOM 或是發送 Ajax 請求。

二、沒有同源策略限制有哪些危險場景?

  • ajxa 請求
  • Dom 的查詢

同源策略確實能規避一些危險,不是說有了同源策略就安全,只是說同源策略是一種瀏覽器最基本的安全機制,畢竟能提升一點攻擊的成本。

三、爲何瀏覽器會禁止跨域?

  • 跨域只存在瀏覽器端,由於瀏覽器的形態很開放,須要對它進行限制。
  • 同源策略用於保護用戶信息安全,防止惡意竊取數據(ajax 同源策略、Dom 同源策略)。

四、跨域有哪些解決方式?

CSDN 跨域問題解決

  1. jsonp
  2. cors
  3. postMessage
  4. websocket
  5. Node 中間件代理(兩次跨域)
  6. nginx 反向代理
  7. window.name + iframe
  8. location.hash + iframe
  9. document.domain + iframe

五、CORS 經常使用的配置有哪些?

  • Access-Control-Allow-Origin 容許的域名
  • Access-Control-Allow-Methods 容許的 http 請求方法
  • Access-Control-Allow-Headers 支持的請求頭
  • Access-Control-Allow-Credentials 是否發送 cookie
  • Access-Control-Max-Age 以秒爲單位的緩存時間

六、CORS 跨域的斷定流程

  1. 瀏覽器先判斷是否同源,若同源,直接發送數據,不然,發送跨域請求;
  2. 服務器收到跨域請求後,根據自身配置返回對應的文件頭;
  3. 瀏覽器根據收到的響應頭裏的 Access-Control-Allow-origin 字段進行匹配,若無該字段說明不容許跨域,報錯,有該字段進行比對,判斷是否能夠跨域。

七、什麼是簡單請求?

簡單請求是指知足如下條件的:

  • 使用 getposthead 其中一種方法進行請求的;
  • http 的頭信息不超出一下狀況:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID

Content-Type:值僅限於 application/x-www-form-urlencodedmultipart/form-datatext/plain

  • 請求中 XMLHttpRequestUpload 對象沒有註冊任何的事件監聽器;
  • XMLHttpRequestUpload 對象能夠使用 XMLHttpRequest.upload 屬性訪問。 請求中沒有使用 ReadableStream 對象。

八、非簡單請求

對服務器有特殊要求的請求(簡單請求以外就是非簡單請求)。

例如:請求方式是 putdeleteContent-Type 的類型是 application/json

非簡單請求會在正式通訊前使用 options 發起一個預檢請求,詢問服務器當前的域名是否在服務器容許的名單之中,以及使用哪些頭信息字段等。

九、有哪些方法可一嵌入跨源的資源?

  • script 標籤,嵌入跨域腳本;
  • link 標籤,嵌入 css
  • img 標籤,嵌入圖片;
  • video/audio 標籤,嵌入視頻、音頻;
  • object/embed/applet 標籤,嵌入 svg /圖片 等;
  • svg 標籤,嵌入 svg
  • 經過 @font-face 嵌入字體;
  • 經過 iframe 嵌入資源

十、手動實現一個 JSONP

//Promise封裝
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    // 建立 script 標籤
    let script = document.createElement('script')
    // 把 callback 掛載在 window 上,執行以後刪除 script 
    window[callback] = function(data) {
      resolve(data)
      document.body.removeChild(script)
    }
    // 添加參數
    params = { ...params, callback } // wd=b&callback=callFun
    let arrs = []
    for (let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    // 設置 script 的 URL
    script.src = `${url}?${arrs.join('&')}`
    // 插入 body 中
    document.body.appendChild(script)
  })
}
// 調用示例
jsonp({
  url: 'http://localhost:3000/code',
  params: { wd: 'hello world' },
  callback: 'callFun'
}).then(data => {
  console.log(data) // 你好啊
  //再此回調結束後刪除該script
})
複製代碼

二10、說下 js 的繼承方式

JS 常見的六種繼承方式

一、原型鏈繼承 prototype

子類型的原型爲父類型的一個實例對象。

Child.prototype = new Parent()
複製代碼

優勢:

  1. 繼承方式簡單
  2. 父類新增方法、屬性,子類都能訪問到

缺點:

  1. 沒法實現多繼承
  2. 來自父類的全部屬性被全部實例共享
  3. 要想爲子類新增屬性和方法,必需要在Child.prototype = new Parent() 以後,由於會被覆蓋
  4. 建立子類時,不能像父類傳遞參數

二、構造函數繼承 call

在子類型構造函數中通用 call() 調用父類型構造函數

function Child(name, age, price) {
    Parent.call(this, name, age)  // 至關於: this.Parent(name, age)
}
複製代碼

優勢:

  1. 原型鏈繼承中子類實例共享父類引用屬性的問題
  2. 建立子類實例時,能夠向父類傳遞參數
  3. 能夠實現多繼承(call多個父類對象)

缺點:

  1. 實例並非父類的實例,只是子類的實例
  2. 只能繼承父類的實例屬性和方法,不能繼承原型屬性和方法
  3. 沒法實現函數複用,每一個子類都有父類實例函數的副本,影響性能

三、原型鏈+構造函數的組合繼承 prototype + call

調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用。

function Child (name, age, price) {
  Parent.call(this, name, age)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child//組合繼承也是須要修復構造函數指向的
複製代碼

優勢:

  1. 能夠繼承實例屬性/方法,也能夠繼承原型屬性/方法
  2. 不存在引用屬性共享問題
  3. 可傳參

缺點:

  1. 調用了兩次父類構造函數,生成了兩份實例

四、組合繼承優化1

經過父類原型和子類原型指向同一對象,子類能夠繼承到父類的公有方法當作本身的公有方法,並且不會初始化兩次實例方法/屬性,避免的組合繼承的缺點。

function Child (name, age, price) {
    Parent.call(this, name, age)
}
Child.prototype = Parent.prototype
複製代碼

優勢:

  1. 不會調用了兩次父類構造函數

缺點:

  1. 沒辦法辨別是實例是子類仍是父類創造的,子類和父類的構造函數指向是同一個。

五、組合繼承優化2

藉助原型能夠基於已有的對象來建立對象,var B = Object.create(A)A 對象爲原型,生成了 B 對象。B 繼承了 A 的全部屬性和方法。

function Child (name, age, price) {
    Parent.call(this, name, age)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
複製代碼

六、es6 class 的繼承

class 關鍵字只是原型的語法糖,JavaScript 繼承仍然是基於原型實現的。

class Parent {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    setName () {
        console.log('parent')
    }
}
let child1 = new Parent('name1', 18)
let child2 = new Parent('name2', 16)
class Child extends Parent {
    constructor(name, age, price) {
        super(name, age)
        this.price = price
    }
    setAge () {
        console.log('子類方法')
    }
}
let child3 = new Child('name3', 20, 15000)
let child4 = new Child('name4', 21, 10000)
複製代碼

優勢:

  1. 簡單繼承

二11、排序算法

一、冒泡排序

簡單來講就是相鄰兩個元素進行對比,按照你須要的排序方式(升序or降序)進行位置替換,替換時須要額外一個變量看成中間變量去暫存值。 冒泡排序

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        //相鄰元素兩兩對比
                var temp = arr[j+1];        //元素交換
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}
複製代碼

二、快速排序

選擇一個基準,將比基準小的放左邊,比基準小的放在右邊(基準處在中間位置) 快速排序

function quickSort(arr) {
    //若是數組<=1,則直接返回
    if (arr.length <= 1) { return arr; }
    var pivotIndex = Math.floor(arr.length / 2);
    //找基準,並把基準從原數組刪除
    var pivot = arr.splice(pivotIndex, 1)[0];
    //定義左右數組
    var left = [];
    var right = [];
    //比基準小的放在left,比基準大的放在right
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] <= pivot) {
            left.push(arr[i]);
        }
        else {
            right.push(arr[i]);
        }
    }
    //遞歸
    return quickSort(left).concat([pivot], quickSort(right));
}
複製代碼

三、選擇排序

首先從原始數組中找到最小的元素,並把該元素放在數組的最前面,而後再從剩下的元素中尋找最小的元素,放在以前最小元素的後面,直到排序完畢 選擇排序

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
      minIndex = i;
      for (var j = i + 1; j < len; j++) {
        if (arr[j] < arr[minIndex]) {
          minIndex = j;
        }
      }
      temp = arr[i];
      arr[i] = arr[minIndex];
      arr[minIndex] = temp;
    }
    return arr;
}
複製代碼

四、插入排序

從第二個元素開始(假定第一個元素已經排序了),取出這個元素,在已經排序的元素中從後向前進行比較,若是該元素大於這個元素,就將該元素移動到下一個位置,而後繼續向前進行比較,直到找到小於或者等於該元素的位置,將該元素插入到這個位置後.重複這個步驟直到排序完成 插入排序

function insertionSort(arr) {
  var len = arr.length;
  var preIndex, current;
  for (var i = 1; i < len; i++) {
    preIndex = i - 1;
    current = arr[i];
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = current;
  }
  return arr;
}
複製代碼

五、歸併排序

歸併排序是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。歸併排序是一種穩定的排序方法。將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。 歸併排序

function mergeSort(arr) {  //採用自上而下的遞歸方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right){
    var result = [];
    console.time('歸併排序耗時');
    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }
    while (left.length)
        result.push(left.shift());
    while (right.length)
        result.push(right.shift());
    console.timeEnd('歸併排序耗時');
    return result;
}
複製代碼

六、希爾排序

利用步長來進行兩兩元素比較,而後縮減步長在進行排序。 說明:希爾排序的實質是分組插入排序,該方法又稱縮小增量排序。該方法的基本思想是:先將整個待排元素序列分割爲若干個子序列(由相隔某個‘增量’的元素組成的)分別進行直接插入排序,而後依次縮減增量再進行排序,帶這個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。由於直接插入排序在元素基本有序的狀況下(接近最好狀況)效率是很高的,所以希爾排序在時間效率上有較大的提升。 與插入排序的不一樣之處:它會優先比較距離較遠的元素 希爾排序

function shellSort(arr) {
  let temp,
    gap = 1;
  while (gap < arr.length / 3) {
    gap = gap * 3 + 1//動態定義間隔序列
  }
  for (gap; gap > 0; gap = Math.floor(gap / 3)) {//控制步長(間隔)並不斷縮小
    for (var i = gap; i < arr.length; i++) {//按照增量個數對序列進行排序
      temp = arr[i]
      for (var j = i - gap; j >= 0 && arr[j] > temp; j -= gap) {//例:j=0 arr[1]>arr[5]
        arr[j + gap] = arr[j]
      }
      arr[j + gap] = temp
    }
  }
  return arr
}
複製代碼

七、各種排序算法比較

算法比較

二12、時間複雜度、空間複雜度

一、如何去衡量不一樣算法之間的優劣呢?

主要仍是從算法所佔用的「時間」和「空間」兩個維度去考量。

  • 時間維度:是指執行當前算法所消耗的時間,咱們一般用「時間複雜度」來描述。
  • 空間維度:是指執行當前算法須要佔用多少內存空間,咱們一般用「空間複雜度」來描述。

二、時間複雜度

1. 表示方法

大O符號表示法 」,即 T(n) = O(f(n))

時間複雜度的公式是: T(n) = O( f(n) ),其中 f(n) 表示每行代碼執行次數之和,而 O 表示正比例關係,這個公式的全稱是:算法的漸進時間複雜度。

2. 常見的複雜度量級

• 常數階 O(1) • 對數階 O(logN) • 線性階 O(n) • 線性對數階 O(nlogN) • 平方階 O(n²) • 立方階 O(n³) • K次方階 O(n^k) • 指數階 (2^n)

3. O(1)

不管代碼執行了多少行,只要是沒有循環等複雜結構,那這個代碼的時間複雜度就都是 O(1)

var i = 1;
var j = 2;
++i;
j++;
var m = i + j;
複製代碼

4. O(n)

for 循環裏面的代碼會執行 n 遍,所以它消耗的時間是隨着n的變化而變化的,所以這類代碼均可以用 O(n) 來表示它的時間複雜度。

for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}
複製代碼

5. 對數階 O(logN)

var i = 1;
while(i<n)
{
    i = i * 2;
}
複製代碼

while 循環裏面,每次都將 i 乘以 2,乘完以後,i 距離 n 就愈來愈近了。咱們試着求解一下,假設循環 x 次以後,i 就大於 2 了,此時這個循環就退出了,也就是說 2x 次方等於 n,那麼 x = log2^n 也就是說當循環 log2^n 次之後,這個代碼就結束了。所以這個代碼的時間複雜度爲:O(logn)

6. O(nlogN)

將時間複雜度爲 O(logn) 的代碼循環N遍的話,那麼它的時間複雜度就是 n * O(logN),也就是了 O(nlogN)

for(m=1; m<n; m++)
{
    i = 1;
    while(i<n)
    {
        i = i * 2;
    }
}
複製代碼

7. O(n²)O(m*n)O(n³)O(n^k)

平方階 O(n²)O(n) 的代碼再嵌套循環一遍,它的時間複雜度就是O(n*n),即 O(n²)

for(x=1; i<=n; x++)
{
   for(i=1; i<=n; i++)
    {
       j = i;
       j++;
    }
}
複製代碼

O(m*n) 將其中一層循環的 n 改爲 m,那它的時間複雜度就變成了 O(m*n)

for(x=1; i<=m; x++)
{
   for(i=1; i<=n; i++)
    {
       j = i;
       j++;
    }
}
複製代碼

三、空間複雜度

空間複雜度是對一個算法在運行過程當中臨時佔用存儲空間大小的一個量度,一樣反映的是一個趨勢,咱們用 S(n) 來定義。

1. 常見的複雜度量級

空間複雜度比較經常使用的有:O(1)、O(n)、O(n²)

2. O(1)

若是算法執行所須要的臨時空間不隨着某個變量 n 的大小而變化,即此算法空間複雜度爲一個常量,可表示爲 O(1)

var i = 1;
var j = 2;
++i;
j++;
var m = i + j;
複製代碼

代碼中的 ijm 所分配的空間都不隨着處理數據量變化,所以它的空間複雜度 S(n) = O(1)

3. O(n)

var arr = [1, 2, 3]
for(i=1; i<=arr.lemgth; ++i)
{
   j = i;
   j++;
}
複製代碼

第一行定義了一個數組出來,這個數據佔用的大小爲 n,這段代碼的 2-6行,雖然有循環,但沒有再分配新的空間,所以,這段代碼的空間複雜度主要看第一行便可,即 S(n) = O(n)

二十3、接口請求

一、AJAX

一、簡單實現一個 ajax

function stringify (json) {
  var str = "";
  for (var i in json) {
    str += i + "=" + json[i] + "&";
  }
  return str.slice(0, -1);
}
function myAjax (type, url, params, callback, errback) {
  let xhr = null;
  //表IE
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  } else {
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
  }
  if (type == "get") {
    xhr.open(type, url + "?" + stringify(params), true);
    xhr.send();
  } else {
    xhr.open(type, url, true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    //json轉換成name=張三&age=1
    xhr.send(stringify(params));
  }
  xhr.onreadystatechange = function () {
    // 表示請求已完成
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        if (callback) {
          callback(xhr.responseText);
        }
      } else {
        errback && errback();
      }
    }
  }
}
複製代碼

二、ajaxreadyState 的狀態

  • 0 未初始化,尚未調用 open() 方法
  • 1 啓動,已經調用 open() 方法,可是沒有調用 send() 方法
  • 2 發送,已經調用 send() 方法,可是還沒有接收響應
  • 3 接收,已經接收到部分響應數據
  • 4 完成,已經接收到所有響應數據

二、Axios

Axios 本質上也是對原生 XHR 的封裝,只不過它是 Promise 的實現版本,符合最新的 ES 規範

1. 特性:

  • node.js 建立 http 請求
  • 支持 Promise API
  • 客戶端支持防止 CSRF
  • 提供了一些併發請求的接口(重要,方便了不少的操做)

三、Fetch

Fetch API 提供了一個 JavaScript 接口,用於訪問和操縱 HTTP 管道的部分,例如請求和響應。它還提供了一個全局 fetch() 方法,該方法提供了一種簡單,合理的方式來跨網絡異步獲取資源。

1. 優勢

  • 語法簡潔,更加語義化
  • 基於標準 Promise 實現,支持 async/await
  • 同構方便,使用 isomorphic-fetch

2. 缺點

  • Fetch 請求默認是不帶 cookie 的,須要設置 fetch(url, {credentials: 'include'})
  • 服務器返回 400,500 錯誤碼時並不會 reject,只有網絡錯誤這些致使請求不能完成時,fetch 纔會被 reject

二十4、new 操做符

一、new 的實現流程

  • 1.建立一個新對象;
  • 2.將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象);
  • 3.執行構造函數中的代碼(爲這個新對象添加屬性);
  • 4.返回新對象。
  • 5.將構造函數的 prototype 關聯到實例的 __proto__

二、如何實現一個 new

function myNew (foo, ...args) {
  // 建立一個新對象,並繼承 foo 的 prototype 屬性
    let obj = Object.create(foo.prototype)
  // 執行構造方法,並綁定新 this,
  let result = foo.apply(obj, args)
  // 若是構造方法返回了一個對象,那麼就返回該對象,不然就返回 myNew 建立的新對象
  return Object.prototype.toString().call(result) === '[object Object]' ? result : obj
}
複製代碼

二十5、網頁全屏怎麼實現?

document.documentElement.requestFullscreen()
複製代碼

須要兼容實現

一、網頁全屏

function fullScreen() {
    if (!document.fullscreenElement &&
        !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) { // current working methods
        if (document.documentElement.requestFullscreen) {
            document.documentElement.requestFullscreen();
        } else if (document.documentElement.msRequestFullscreen) {
            document.documentElement.msRequestFullscreen();
        } else if (document.documentElement.mozRequestFullScreen) {
            document.documentElement.mozRequestFullScreen();
        } else if (document.documentElement.webkitRequestFullscreen) {
            document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
        }
    }
}
複製代碼

二、取消網頁全屏

function exitFullScreen() {
    if (document.exitFullscreen) {
        document.exitFullscreen();
    } else if (document.msExitFullscreen) {
        document.msExitFullscreen();
    } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
    }
}
複製代碼

三、檢測是否全屏

/** * 檢查是否全屏 * @return {[Boolean]} [是否全屏,爲 true 沒有全屏,false 全屏] */
function checkFullScreenValue () {
    return !document.fullscreenElement &&
        !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement
}
複製代碼

二十6、MapWeakMapsetWeakSet 有什麼區別?

WeakMapWeakSet 都是弱引用

一、什麼是弱引用

弱引用是指不能確保其引用的對象不會被垃圾回收器回收的引用,換句話說就是可能在任意時間被回收。

弱引用隨時都會消失,遍歷機制沒法保證成員的存在

二、Set

  • 遍歷順序:插入順序
  • 沒有鍵只有值,可認爲鍵和值兩值相等
  • 添加多個 NaN 時,只會存在一個 NaN
  • 添加相同的對象時,會認爲是不一樣的對象
  • 添加值時不會發生類型轉換(5 !== "5")
  • keys()values() 的行爲徹底一致,entries() 返回的遍歷器同時包括鍵和值且兩值相等

三、weakSet 做用

  • Set 結構相似,成員值只能是對象
  • 儲存 DOM 節點:DOM 節點被移除時自動釋放此成員,不用擔憂這些節點從文檔移除時會引起內存泄漏
  • 臨時存放一組對象或存放跟對象綁定的信息:只要這些對象在外部消失,它在 WeakSet 結構中的引用就會自動消
  • 成員都是弱引用,垃圾回收機制不考慮 WeakSet 結構對此成員的引用
  • 成員不適合引用,它會隨時消失,所以 ES6 規定 WeakSet 結構不可遍歷
  • 其餘對象再也不引用成員時,垃圾回收機制會自動回收此成員所佔用的內存,不考慮此成員是否還存在於 WeakSet 結構中

四、Map

  • 遍歷順序:插入順序
  • 對同一個鍵屢次賦值,後面的值將覆蓋前面的值
  • 對同一個對象的引用,被視爲一個鍵
  • 對一樣值的兩個實例,被視爲兩個鍵
  • 鍵跟內存地址綁定,只要內存地址不同就視爲兩個鍵
  • 添加多個以 NaN 做爲鍵時,只會存在一個以 NaN 做爲鍵的值
  • Object 結構提供字符串—值的對應,Map 結構提供值—值的對應

五、WeakMap

  • Map 結構相似,成員鍵只能是對象
  • 儲存 DOM 節點:DOM 節點被移除時自動釋放此成員鍵,不用擔憂這些節點從文檔移除時會引起內存泄漏
  • 部署私有屬性:內部屬性是實例的弱引用,刪除實例時它們也隨之消失,不會形成內存泄漏
  • 成員鍵都是弱引用,垃圾回收機制不考慮 WeakMap 結構對此成員鍵的引用
  • 成員鍵不適合引用,它會隨時消失,所以 ES6 規定 WeakMap 結構不可遍歷
  • 其餘對象再也不引用成員鍵時,垃圾回收機制會自動回收此成員所佔用的內存,不考慮此成員是否還存在於 WeakMap 結構中
  • 一旦再也不須要,成員會自動消失,不用手動刪除引用
  • 弱引用的只是鍵而不是值,值依然是正常引用
  • 即便在外部消除了成員鍵的引用,內部的成員值依然存在

六、Object 轉爲 Map

let obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj))
複製代碼

二十7、Proxy

一、語法

  • target 要使用 Proxy 包裝的目標對象(能夠是任何類型的對象,包括原生數組,函數,甚至另外一個代理
  • handler 一個一般以函數做爲屬性的對象,用來定製攔截行爲

代理只會對 proxy 對象生效,對代理對象沒有任何效果

origin = {}
obj = new Proxy(origin, {
  get: function (target, propKey, receiver) {
        return '10'
  }
});
obj.a // 10
obj.b // 10
origin.a // undefined
origin.b // undefined
複製代碼

二、Handler 對象經常使用的方法

方法 描述
handler.has() in 操做符的捕捉器。
handler.get() 屬性讀取操做的捕捉器。
handler.set() 屬性設置操做的捕捉器。
handler.deleteProperty() delete 操做符的捕捉器。
handler.ownKeys() Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply() 函數調用操做的捕捉器。
handler.construct() new 操做符的捕捉器
### 三、proxy 代理是否能夠撤銷?
proxy 有一個惟一的靜態方法,Proxy.revocable(target, handler)
Proxy.revocable() 方法能夠用來建立一個可撤銷的代理對象
該方法的返回值是一個對象,其結構爲: {"proxy": proxy, "revoke": revoke}
  • proxy 表示新生成的代理對象自己,和用通常方式 new Proxy(target, handler) 建立的代理對象沒什麼不一樣,只是它能夠被撤銷掉。
  • revoke 撤銷方法,調用的時候不須要加任何參數,就能夠撤銷掉和它一塊兒生成的那個代理對象。
const target = { name: 'vuejs'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值輸出 vuejs
revoke() // 取值完成對proxy進行封閉,撤消代理
proxy.name // TypeError: Revoked //已撤銷
複製代碼

二十8、執行上下文

一、執行上下文的類型?

1. 全局執行上下文:

一個程序中只能存在一個全局執行上下文。

這是默認的、最基礎的執行上下文。不在任何函數中的代碼都位於全局執行上下文中。它作了兩件事:

  1. 建立一個全局對象,在瀏覽器中這個全局對象就是 window 對象。
  2. this 指針指向這個全局對象。

2. 函數執行上下文:

能夠有無數個函數執行上下文。

每次調用函數時,都會爲該函數建立一個新的執行上下文。每一個函數都擁有本身的執行上下文,可是隻有在函數被調用的時候纔會被建立。

3. Eval 函數執行上下文:

jseval 函數執行其內部的代碼會建立屬於本身的執行上下文, 不多用並且不建議使用。

二、執行上下文的特色

  1. 單線程;
  2. 同步執行,從上往下順序執行;
  3. 全局上下文只有一個,也就是 window 對象;
  4. 函數執行上下文沒有數量限制;
  5. 函數只有在調用的時候纔會被建立,每調用一次就會產生一個新的執行上下文環境。

三、執行上下文的生命週期

1. 建立階段

  1. 建立變量對象:首先初始化函數的參數 arguments,提高函數聲明和變量聲明。
  2. 建立做用域鏈:做用域鏈是在變量對象以後建立的。做用域鏈自己包含變量對象。做用域鏈用於解析變量。當被要求解析變量時,JavaScript 始終從代碼嵌套的最內層開始,若是最內層沒有找到變量,就會跳轉到上一層父做用域中查找,直到找到該變量。
  3. 肯定 this 指向:肯定 this 的指向。

2. 執行階段

  1. 執行變量賦值。
  2. 函數引用。
  3. 執行其餘代碼。

3. 回收階段

  1. 執行上下文出棧
  2. 等待虛擬機回收執行上下文

四、js 如何管理多個執行上下文的?

管理多個執行上下文靠的就是執行棧,也被叫作調用棧。

特色:後進先出(LIFO:last-in, first-out)的結構。 做用:存儲在代碼執行期間的全部執行上下文。

示例:

var a = 1; // 1. 全局上下文環境
function bar (x) {
    console.log('bar')
    var b = 2;
    fn(x + b); // 3. fn上下文環境
}
function fn (c) {
    console.log(c);
}
bar(3); // 2. bar上下文環境
複製代碼

執行上下文

二十9、實現一些特殊函數

一、一次性函數

function once (func) {
  let done;
  return function () {
    if (!done) {
      func.apply(null, arguments)
      done = true
    }
  }
}
const onlyDoOne = once(function() {
  console.log('1')
})
onlyDoOne() // 1
onlyDoOne() // 沒有輸出,不會再次執行
複製代碼

二、延遲函數(沉睡函數)

function sleep (time) {
    return new Promise(resolve => {
    window.setTimeout(resolve, time)
  })
}
// 調用
sleep(1000).then(res => {
    console.log('延遲')
})
// 調用
async function useSleep () {
    const sleepval = await sleep(1000)
}
useSleep()
複製代碼

三、setTimeout 實現 setInterval

;(() => {
  const list = new Set();
  function myInterval(fn, ms) {
    const ref = {};
    const exec = () => {
      return setTimeout(() => {
        fn.apply(null);
        const timer = exec();
        ref.current = timer;
      }, ms);
    };
    ref.current = exec();
    list.add(ref);
    return ref;
  }
  function myClearInterval(ref) {
    clearTimeout(ref.current);
    list.delete(ref);
  }
  window.myInterval = myInterval;
  window.myClearInterval = myClearInterval;
})()
myInterval(() => {console.log(1)}, 5000)
myClearInterval({current: 1186})
複製代碼

四、前端生成 excel 表格並下載

/** * 前端下載表格 * @param {[Array]} data [數據數組] * @param {[String]} tableHead [表頭字符串] * @return {[undefined]}  */
function downExcel (data, tableHead) {
  tableHead = tableHead
  data.forEach(item => {
    for (let i in item) {
      tableHead += `${item[i] + '\t'},`
    }
    tableHead += '\n'
  })
  const url = 'data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(tableHead);
  //經過建立a標籤實現
  const link = document.createElement("a");
  link.href = url;
  //對下載的文件命名
  link.download = "個人EXCEL表格.csv";
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}
// excel 數據
let tableData = [{
  name: '你好啊',
  time: 130000000000,
  pre: '127.130',
  source: '淘寶',
  otherTime: 1571276232000
}]
// excel 頭部
let str = `用戶名,時間,座標,來源,受權時間\n`;
// 下載表格執行
downExcel(tableData, str)
複製代碼

三10、什麼是原型、原型鏈?

原型JS 聲明構造函數(用來實例化對象的函數)時,會在內存中建立一個對應的對象,這個對象就是原函數的原型。

構造函數默認有一個 prototype 屬性,prototype 的值指向函數的原型。同時原型中也有一個 constructor 屬性,constructor 的值指向原函數。

經過構造函數實例化出來的對象,並不具備 prototype 屬性,其默認有一個 __proto__ 屬性,__proto__ 的值指向構造函數的原型對象。在原型對象上添加或修改的屬性,在全部實例化出的對象上均可共享。

原型原型鏈 當在實例化的對象中訪問一個屬性時,首先會在該對象內部尋找,如找不到,則會向其 __proto__ 指向的原型中尋找,如仍找不到,則繼續向原型中 __proto__ 指向的上級原型中尋找,直至找到或 Object.prototype 爲止,這種鏈狀過程即爲原型鏈

三11、實現一個 EventBus

簡單實現

class myEventBus {
  constructor(props) {
    this.events = {}
  }
  on (event, fn) {
    const events = this.events
    events[event] ? events[event].push(fn) : (events[event] = [fn])
  }
  emit (event, ...res) {
    this.events[event] && this.events[event].forEach(fn => {
      return fn.apply(this, res)
    })
  }
  remove (event, fn) {
    if (this.events[event]) {
      delete this.events[event]
    }
  }
}
複製代碼

三12、js 的垃圾回收(GC)

一、V8 內存限制

  • 64 位系統可用 1.4G 內存
  • 32 位系統可用 0.7G 內存

二、V8 內存管理

  • JS 對象都是經過 V8 進行分配管理內存的
  • process.memoryUsage() 返回一個對象,包含了 Node 進程的內存佔用信息

三、內存佔用結構圖

內存佔用圖

  • var a = {name:‘yuhua’}; 這句代碼會作以下幾步:
    • 將這句代碼放入「代碼區域 Code Segment
    • 將變量 a 放入「棧( Stack ):本地變量、指針」
    • {name:‘yuhua’} 放入「 HeapTotal (堆):對象,閉包」
  • 注意:基本數據類型都在棧中,引用類型都在堆中

四、爲什麼限制內存大小

  • 由於 V8 垃圾收集工做原理致使的,1.4G 內存徹底一次垃圾收集須要 1s 以上
  • 這個垃圾回收這段時間(暫停時間)成爲 Stop The World,在這期間,應用的性能和響應能力都會降低

五、V8 的垃圾回收機制

  • V8 是基於分代的垃圾回收
  • 不一樣代垃圾回收機制也不同,採用的算法不同
  • 按存貨的時間分爲新生代和老生代

六、分代

  • 年齡小的是新生代,由 From 區域和 To 區域兩個區域組成
  • 在 64 位系統裏,新生代內存是 32M,From 區域和 To 區域各佔 16M
  • 在 32 位系統裏,新生代內存是 16M,From 區域和 To 區域各佔 8M
  • 年齡大的是老生代,默認狀況下:
    • 64 位系統下老生代內存是 1400M
    • 32 位系統下老生代內存是 700M

七、新生代採用 Scavenge 算法

Scavenge 爲新生代採用的算法,是一種採用複製的方式實現的垃圾回收算法。

新生代掃描的時候是一種廣度優先的掃描策略

它將內存分爲 fromto 兩個空間。每次 gc,會將 from 空間的存活對象複製到 to 空間。而後兩個空間角色對換(又稱反轉)。

該算法是犧牲空間換時間,因此適合新生代,由於它的對象生存週期較短。

1. 過程

  • 新生代區域一分爲二,每一個 16M,一個使用,一個空閒
  • 開始垃圾回收的時候,會檢查 FROM 區域中的存活對象,若是還活着,拷貝到 TO 空間,全部存活對象拷貝完後,清空(釋放) FROM 區域
  • 而後FROM和To區域互換

2. 特色

  • 新生代掃描的時候是一種廣度優先的掃描策略
  • 新生代的空間小,存活對象少
  • 當一個對象經理屢次的垃圾回收依然存活的時候,生存週期比較差的對象會被移動到老聲帶,這個移動過程被稱爲晉升或升級
  • 經歷過 5 次以上的回收還存在
  • TO 的空間使用佔比超過 25%,或者超大對象
  • 瀏覽器的 memory 中能夠經過拍快照看變量是否被垃圾回收
  • 置爲 undefinednull 都能將引用計數減去 1

八、老生代採用 Mark-SweepMark-Compact

1. 基礎

  • 老生代垃圾回收策略分爲兩種
    • mark-sweep 標記清除
      • 標記活着的對象,雖然清楚在標記階段沒有標記的對象,只清理死亡對象
      會出現的問題:清除後內存不連續,碎片內存沒法分配
    • mark-compact 標記整理
      • 標記死亡後會對對象進行整理,活着的左移,移動完成後清理掉邊界外的內存(死亡的對象)
  • 老生代空間大,大部分都是活着的對象,GC 耗時比較長
  • GC 期間沒法想聽,STOP-THE-WORLD
  • V8 有一個優化方案,增量處理,把一個大暫停換成多個小暫停 INCREMENT-GC
  • 也就是把大暫停分紅多個小暫停,每暫停一小段時間,應用程序運行一會,這樣垃圾回收和應用程序交替進行,停頓時間能夠減小到1/6左右

2. 過程

假設有10個大小的內存,內存佔用了6個,

1)Mark-Sweep 模式垃圾回收:
  • 那麼會給每一個對象作上標記:
A   b   C   d   E   f  空  空 空 空
//對上面每一個對象作上標記,大寫表示活着,小寫表示死了
//這時候,會存在一個問題,就是內存碎片沒法使用,由於小寫的內存沒有跟後面空空空空的內存放在一塊兒,不能使用
複製代碼
  • 這時候小寫(死)的都會被幹掉,只保留大寫(活)的,致使的問題就是內存碎片沒法使用
2)Mark-Compact 模式垃圾回收
  • 將活的左移
A C E b d f 空 空 空 空
複製代碼
  • 而後回收死了的區域
A C E 空 空 空 空 空 空 空
複製代碼

九、三種算法的對比

回收算法 Mark-Sweep Mark-Compact Scavenge
速度 中等 最慢 最快
空間開銷 雙倍空間(無碎片)
是否移動對象
  • V8 老生代主要用 Mark-Sweep,由於 Mark-Compact 須要移動對象,執行速度不快。空間不夠時,纔會用 Mark-Compact

三十3、設計模式

一、設計原則:

1. 單一職責原則(SRP

一個對象或方法只作一件事情。

2. 最少知識原則(LKP

應當儘可能減小對象之間的交互。

3. 開放-封閉原則(OCP

軟件實體(類、模塊、函數)等應該是能夠 擴展的,可是不可修改

二、策略模式

策略模式是指對一系列的算法定義,並將每個算法封裝起來,並且使它們還能夠相互替換。策略模式讓算法獨立於使用它的客戶而獨立變化。 優勢:

  • 策略模式利用組合、委託等技術和思想,能夠避免不少if條件語句
  • 策略模式提供了開放-封閉原則,使代碼更容易理解和拓展

示例:

  1. 績效等級和薪資計算獎金爲
  2. 表單驗證,一般會涉及到多個字段有效性判斷

三、緩存代理模式

緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,若是傳遞進來的參數跟以前的一致,則能夠直接返回前面存儲的運算結果,提供效率以及節省開銷。

緩存代理,就是將前面使用的值緩存下來,後續還有使用的話,就直接拿出來用。

四、工廠模式

工廠模式是用來建立對象的一種最經常使用的設計模式。咱們不暴露建立對象的具體邏輯,而是將將邏輯封裝在一個函數中,那麼這個函數就能夠被視爲一個工廠。工廠模式根據抽象程度的不一樣能夠分爲:簡單工廠,工廠方法和抽象工廠。

簡單工廠的優勢在於,你只須要一個正確的參數,就能夠獲取到你所須要的對象,而無需知道其建立的具體細節。簡單工廠只能做用於建立的對象數量較少,對象的建立邏輯不復雜時使用。

工廠方法模式的本意是將實際建立對象的工做推遲到子類中,工廠方法模式就是將這個大廠拆分出各個小廠,每次添加新的產品讓小廠去生產,大廠負責指揮就行了。

抽象工廠模式並不直接生成實例, 而是用於對產品類簇的建立。

五、單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

確保了只有一個實例

  • 由於只有惟一實例,因此節省了系統資源,記住建立和銷燬也須要浪費內存資源
  • 避免了對資源的多重佔用,好比數據庫的鏈接
  • 資源共享

前端應用場景:

  • 瀏覽器的 window 對象。在 JavaScript 開發中,對於這種只須要一個的對象,每每使用單例實現。
  • 遮罩層、登錄浮窗等。

六、代理模式

爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。

代理模式主要有三種:保護代理、虛擬代理、緩存代理

七、迭代器模式

迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。

JS 中數組的 map forEach 已經內置了迭代器

八、發佈-訂閱者模式

也稱做觀察者模式,定義了對象間的一種一對多的依賴關係,當一個對象的狀態發 生改變時,全部依賴於它的對象都將獲得通知。

JS中的事件就是經典的發佈-訂閱模式的實現

九、命令模式

用一種鬆耦合的方式來設計程序,使得請求發送者和請求接收者可以消除彼此之間的耦合關係 命令(command)指的是一個執行某些特定事情的指令

三十4、函數&自執行函數

一、自執行函數特色

  1. 函數表達式與函數聲明不一樣,函數名只在該函數內部有效,而且此綁定是常量綁定
  2. 對於一個常量進行賦值,在 strict 模式下會報錯,非 strict 模式下靜默失敗。
  3. IIFE 中的函數是函數表達式,而不是函數聲明。

二、函數類型

  1. 函數聲明
  2. 函數表達式
  3. 函數構造器建立

1. 函數聲明(FD

  1. 有一個特定的名稱
  2. 在源碼中的位置:要麼處於程序級(Program level),要麼處於其它函數的主體(FunctionBody)中
  3. 在進入上下文階段建立
  4. 影響變量對象
  5. 如下面的方式聲明
function funName () {}
複製代碼

2. 函數表達式(FE

  1. 在源碼中須出如今表達式的位置
  2. 有可選的名稱
  3. 不會影響變量對象
  4. 在代碼執行階段建立
// 函數表達式
var foo = function () {} // 匿名函數表達式賦值給變量foo
var foo2 = function _foo2() {} // 外部FE經過變量「foo」來訪問——foo(),而在函數內部(如遞歸調用),有可能使用名稱「_foo」。
// 圓括號(分組操做符)內只能是表達式
(function foo() {});
// 在數組初始化器內只能是表達式
[function bar() {}];
// 逗號也只能操做表達式
1, function baz() {};
// !
!function() {}();
(function foo() {})() // 自執行函數 IIFE
(function () {})() // IIFT
var foo = {
  bar: function (x) {
    return x % 2 != 0 ? 'yes' : 'no';
  }(1)
};
foo.bar // 'yes'
複製代碼

3. 函數構造器建立的函數

咱們將它與 FDFE 區分開來。其主要特色在於這種函數的[[Scope]]屬性僅包含全局對象

var x = 10;
function foo() {
  var x = 20;
  var y = 30;
  var bar = new Function('alert(x); alert(y);');
  bar(); // 10, "y" 未定義
}
複製代碼

三、如何建立一個函數不須要 () 就能夠執行

  1. 建立對象
  2. 對象裏面表達式定義自執行函數
var foo = {
  bar: function (x) {
    return x % 2 != 0 ? 'yes' : 'no';
  }(1)
};
foo.bar // 'yes'
複製代碼

四、爲何有些要加 () 有些能夠不加?

當函數不在表達式的位置的時候,分組操做符圓括號是必須的——也就是手工將函數轉化成 FE

若是解析器知道它處理的是 FE,就不必用圓括號。

五、具名函數表達式

當函數表達式 FE 有一個名稱(稱爲命名函數表達式,縮寫爲 NFE)時,將會出現一個重要的特色。

從定義(正如咱們從上面示例中看到的那樣)中咱們知道函數表達式不會影響一個上下文的變量對象(那樣意味着既不可能經過名稱在函數聲明以前調用它,也不可能在聲明以後調用它)。 可是,FE在遞歸調用中能夠經過名稱調用自身。

(function foo(bar) {
  if (bar) {
    return;
  }
  foo(true); // "foo" 是可用的
})();
複製代碼

foo」 儲存在什麼地方?在 foo 的活動對象中?不是,由於在 foo 中沒有定義任何」 foo 」。在上下文的父變量對象中建立 foo?也不是,由於按照定義—— FE 不會影響 VO (變量對象)——從外部調用 foo 咱們能夠實實在在的看到。那麼在哪裏呢?

當解釋器在代碼執行階段遇到命名的 FE 時,在 FE 建立以前,它建立了輔助的特定對象,並添加到當前做用域鏈的最前端。而後它建立了 FE,此時(正如咱們在第四章 做用域鏈知道的那樣)函數獲取了[[Scope]] 屬性——建立這個函數上下文的做用域鏈)。此後,FE 的名稱添加到特定對象上做爲惟一的屬性;這個屬性的值是引用到 FE 上。最後一步是從父做用域鏈中移除那個特定的對象。

六、自執行函數示例

// 例一
+function foo(){
foo=10;//個人問題代碼
    console.log(foo);//方法本身
}();
console.log(typeof foo);//undefined 觀察是否全局污染

// 例二
var b = 10;
(function b() {
   // 內部做用域,會先去查找是有已有變量b的聲明,有就直接賦值20,確實有了呀。發現了具名函數 function b(){},拿此b作賦值;
   // IIFE的函數沒法進行賦值(內部機制,相似const定義的常量),因此無效。
  // (這裏說的「內部機制」,想搞清楚,須要去查閱一些資料,弄明白IIFE在JS引擎的工做方式,堆棧存儲IIFE的方式等)
    b = 20;
    console.log(b); // [Function b]
    console.log(window.b); // 10,不是20
})();

// 嚴格模式 會報錯
var b = 10;
(function b() {
 'use strict'
  b = 20;
  console.log(b)
})() // "Uncaught TypeError: Assignment to constant variable."

// 普通函數
function a () {
    a = 1
    console.log(a)
}
a() // 1
a() // a is not a function
複製代碼

三十5、XSS 攻擊和 CSRF 攻擊

一、XSS 攻擊

1. 概念

XSS(Cross Site Scripting):跨域腳本攻擊。

2. 原理

不須要你作任何的登陸認證,它會經過合法的操做(好比在 url 中輸入、在評論框中輸入),向你的頁面注入腳本(多是 jshmtl 代碼塊等)。

3. 防範

  1. 編碼;對於用戶輸入進行編碼。
  2. 過濾;移除用戶輸入和事件相關的屬性。(過濾 scriptstyleiframe 等節點)
  3. 校訂;使用 DOM Parse 轉換,校訂不配對的 DOM 標籤。
  4. HttpOnly

4. 分類

  • 反射型(非持久):點擊連接,執行腳本
  • 存儲型(持久):惡意輸入保存數據庫,其餘用戶訪問,執行腳本
  • 基於 DOM:惡意修改 DOM 結構,基於客戶端

二、CSRF 攻擊

1. 概念

SRF(Cross-site request forgery):跨站請求僞造。

2. 原理

  1. 登陸受信任網站 A,並在本地生成 Cookie。(若是用戶沒有登陸網站 A,那麼網站 B 在誘導的時候,請求網站 Aapi 接口時,會提示你登陸)。
  2. 在不登出 A 的狀況下,訪問危險網站 B(實際上是利用了網站 A 的漏洞)。

3. 防範

  1. token 驗證;
  2. 隱藏令牌;把 token 隱藏在 http 請求的 head 中。
  3. referer 驗證;驗證頁面來源。

三、二者區別

  1. CSRF:須要用戶先登陸網站 A,獲取 cookieXSS:不須要登陸。
  2. CSRF:是利用網站 A 自己的漏洞,去請求網站 AapiXSS:是向網站 A 注入 JS 代碼,而後執行 JS 裏的代碼,篡改網站 A 的內容。

三十6、input 輸入框輸入即請求後端接口,頻繁請求以後怎樣肯定最後一次接口的返回值?

一、後端返回請求值(最簡單)

前端請求接口的時候會把 input 輸入框中的值傳給後端,此時後端返回接口數據時把前端傳入的值返回回去,頁面渲染時只須要進行判斷便可。

二、終止上一次請求

當再次請求的時候把上次的請求終止掉:

  1. ajaxabort()
  2. axios: CancelToken
  3. fetchAbortController

百度用的就是這種取消請求的方式

js:ss1.bdstatic.com/5eN1bjq8AAU…

百度

3. 定義一個全局 ID,接口請求以前自增,而後請求接口閉包保存此值,返回以後進行二者判斷。

此種方式就是不用後端返回值,前端存儲對應的值信息,進行判斷處理

實現

let id = 1
function ajax() {
  ++id
  console.log(id)
  function getData () {
    const newId = id
    const time = Math.random() * 5000 | 0 // 定義一個隨機值
    console.log('time', time)
    setTimeout(() => {
      console.log('id newId', id, newId)
      if (id === newId) { // 在此進行數據處理
        console.log('this is true-->', id)
      }
    }, time)
  }
  getData()
}
// click 頻繁點擊出發函數
document.getElementById('ajaxbtn').onclick = function () {
  ajax()
}
複製代碼

返回結果

三十7、rem

一、定義

rem(font size of the root element)是指相對於根元素的字體大小的單位。 1rem 等於根元素 htmfont-size,即只須要設置根元素的 font-size,其它元素使用 rem 單位時,設置成相應的百分比便可。

二、如何實現

rem(倍數) =  width  / (html的font-size)=>  width = (html的font-size) * rem(倍數)
複製代碼

只要 htmlfont-size 的大小變了,width 就會自動變,因此 rem 是經過動態設置 htmlfont-size 來改變 width 的大小,以達到網頁自適應大小的目的

定義公式:rem(倍數) = width / (html的font-size),根據公式咱們能夠得出:

rem(倍數) = 設計稿寬度( imgWidth ) / 你設置的font-size( defalutSize ) rem(倍數) = 網頁的實際寬度(screenWidth) / 你須要動態設置的font-size( x ) ,那麼得出設置html的font-size的公式爲:

<script type="text/javascript">
  (function(w,d) {
  function setSize() {
    var screenWidth = d.documentElement.clientWidth;
    var currentFontSize = screenWidth * 100 / 750;
    d.documentElement.style.fontSize = currentFontSize + 'px';
  }
  w.addEventListener('resize',setSize);
  w.addEventListener('pageShow',setSize)
  w.addEventListener('DOMContentLoaded',setSize)
})(window,document)
</script>
複製代碼
function setHtmlSize(){
  var pageWidth = window.innerWidth;
  if(typeof pageWidth != "number"){ 
    if(document.compatMode == "number"){ 
      pageWidth = document.documentElement.clientWidth;
    }else{ 
      pageWidth = document.body.clientWidth; 
    } 
  } 
  var fontSize = (window.innerWidth * 100) / 750;
  if(fontSize<40){
    fontSize = 40;
  }
  //根據屏幕大小肯定根節點字號
  document.getElementsByTagName('html')[0].style.fontSize = fontSize + 'px';
}
function resize(){
  setHtmlSize();
}
if (window.attachEvent) { 
  window.attachEvent("resize", resize); 
} else if (window.addEventListener) { 
  window.addEventListener("resize", resize, false);   
}
setHtmlSize();
複製代碼

三、以 750 寬度來算,1rem = 100pxiphone6/7/8 plus 中設置 width: 6.5rem 元素的寬爲多少?

plus 中寬度爲 414 因此寬度爲 414 / 750 * 6.5 * 100 0.32 rem414 / 750 * 0.32 * 100

三十8、dns-prefetchprefetchpreloaddeferasync

一、dns-prefetch

域名轉化爲 ip 是一個比較耗時的過程,dns-prefetch 能讓瀏覽器空閒的時候幫你作這件事。尤爲大型網站會使用多域名,這時候更加須要 dns 預取。

//來自百度首頁
<link rel="dns-prefetch" href="//m.baidu.com">
複製代碼

二、prefetch

prefetch 通常用來預加載可能使用的資源,通常是對用戶行爲的一種判斷,瀏覽器會在空閒的時候加載 prefetch 的資源。

<link rel="prefetch" href="http://www.example.com/">
複製代碼

三、preload

prefetch 不一樣,prefecth 一般是加載接下來可能用到的頁面資源,而 preload 是加載當前頁面要用的腳本、樣式、字體、圖片等資源。因此 preload 不是空閒時加載,它的優先級更強,而且會佔用 http 請求數量。

<link rel='preload' href='style.css' as="style" onload="console.log('style loaded')" 複製代碼

as 值包括

  • script
  • style
  • image
  • media
  • document onload 方法是資源加載完成的回調函數

四、deferasync

//defer
<script defer src="script.js"></script>
//async
<script async src="script.js"></script>
複製代碼

deferasync 都是異步(並行)加載資源,不一樣點是 async 是加載完當即執行,而 defer 是加載完不執行,等到全部元素解析完再執行,也就是 DOMContentLoaded 事件觸發以前。 由於 async 加載的資源是加載完執行,因此它比不能保證順序,而 defer 會按順序執行腳本。

三十9、瀏覽器渲染過程

瀏覽器渲染過程

一、瀏覽器渲染過程以下

  • 解析 HTML,生成 DOM
  • 解析 CSS,生成 CSSOM
  • DOM 樹和 CSSOM 樹結合,生成渲染樹(Render Tree)
  • Layout(迴流):根據生成的渲染樹,進行迴流(Layout),獲得節點的幾何信息(位置,大小)
  • Painting(重繪):根據渲染樹以及迴流獲得的幾何信息,獲得節點的絕對像素
  • Display:將像素髮送給 GPU,展現在頁面上。(這一步其實還有不少內容,好比會在 GPU 將多個合成層合併爲同一個層,並展現在頁面中。而 css3 硬件加速的原理則是新建合成層)

二、什麼時候觸發迴流和重繪

1. 迴流

  • 添加或刪除可見的 DOM 元素
  • 元素的位置發生變化
  • 元素的尺寸發生變化(包括外邊距、內邊框、邊框大小、高度和寬度等)
  • 內容發生變化,好比文本變化或圖片被另外一個不一樣尺寸的圖片所替代。
  • 頁面一開始渲染的時候(這確定避免不了)
  • 瀏覽器的窗口尺寸變化(由於迴流是根據視口的大小來計算元素的位置和大小的)

2. 重繪

  • 迴流必定會觸發重繪
  • 當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:colorbackground-colorvisibility 等),瀏覽器會將新樣式賦予給元素並從新繪製它,這個過程稱爲重繪。

三、若是避免觸發迴流和重繪

1. css

  • 避免使用 table 佈局。
  • 儘量在 DOM 樹的最末端改變 class
  • 避免設置多層內聯樣式。
  • 將動畫效果應用到 position 屬性爲 absolutefixed 的元素上
  • 避免使用 CSS 表達式(例如:calc()
  • CSS3 硬件加速(GPU 加速)
    • transform
    • opacity
    • filters
    • Will-change

2. JavaScript

  • 避免頻繁操做樣式,最好一次性重寫 style 屬性,或者將樣式列表定義爲 class 並一次性更改 class 屬性,修改 stylecssText 屬性或者修改元素的 className 值。
  • 避免頻繁操做 DOM,建立一個 documentFragment,在它上面應用全部 DOM 操做,最後再把它添加到文檔中
  • 也能夠先爲元素設置 display: none,操做結束後再把它顯示出來。由於在 display 屬性爲 none 的元素上進行的 DOM 操做不會引起迴流和重繪
  • 避免頻繁讀取會引起迴流/重繪的屬性,若是確實須要屢次使用,就用一個變量緩存起來
  • 對具備複雜動畫的元素使用絕對定位,使它脫離文檔流,不然會引發父元素及後續元素頻繁迴流。
  • 使用 css3 硬件加速,可讓 transformopacityfilters 這些動畫不會引發迴流重繪 。可是對於動畫的其它屬性,好比 background-color 這些,仍是會引發迴流重繪的,不過它仍是能夠提高這些動畫的性能。

四、硬件加速原理

瀏覽器接收到頁面文檔後,會將文檔中的標記語言解析爲 DOM 樹。DOM 樹和 CSS 結合後造成瀏覽器構建頁面的渲染樹。 渲染樹中包含了大量的渲染元素,每個渲染元素會被分到一個圖層中,每一個圖層又會被加載到 GPU 造成渲染紋理,而圖層在 GPUtransform 是不會觸發 repaint 的,最終這些使用 transform 的圖層都會由獨立的合成器進程進行處理。

1. 瀏覽器何時會建立一個獨立的複合圖層呢?

  • 3D 或者 CSS transform
  • <video><canvas> 標籤
  • CSS filters
  • 元素覆蓋時,好比使用了 z-index 屬性

3D2D transform 的區別就在於,瀏覽器在頁面渲染前爲 3D 動畫建立獨立的複合圖層,而在運行期間爲 2D 動畫建立。動畫開始時,生成新的複合圖層並加載爲 GPU 的紋理用於初始化 repaint。而後由 GPU 的複合器操縱整個動畫的執行。最後當動畫結束時,再次執行 repaint 操做刪除複合圖層。

2. 使用硬件加速的問題

  • 內存。若是 GPU 加載了大量的紋理,那麼很容易就會發生內容問題,這一點在移動端瀏覽器上尤其明顯,因此,必定要牢記不要讓頁面的每一個元素都使用硬件加速。
  • 使用 GPU 渲染會影響字體的抗鋸齒效果。這是由於 GPUCPU 具備不一樣的渲染機制。即便最終硬件加速中止了,文本仍是會在動畫期間顯示得很模糊。

四10、JSBridge

一、什麼是 JSBridge

JSBridge 是一種 JS 實現的 Bridge,鏈接着橋兩端的 NativeH5。它在 APP 內方便地讓 Native 調用 JSJS 調用 Native ,是雙向通訊的通道。JSBridge 主要提供了 JS 調用 Native 代碼的能力,實現原生功能如查看本地相冊、打開攝像頭、指紋支付等。

JSBridge

二、H5native 的區別

name H5 Native
穩定性 調用系統瀏覽器內核,穩定性較差 使用原生內核,更加穩定
靈活性 版本迭代快,上線靈活 迭代慢,須要應用商店審覈,上線速度受限制
受網速 影響 較大 較小
流暢度 有時加載慢,給用戶「卡頓」的感受 加載速度快,更加流暢
用戶體驗 功能受瀏覽器限制,體驗有時較差 原生系統 api 豐富,能實現的功能較多,體驗較好
可移植性 兼容跨平臺跨系統,如 PC 與 移動端,iOSAndroid 可移植性較低,對於 iOSAndroid 須要維護兩套代碼
### 三、JSBridge 的用途
JSBridge 就像其名稱中的『Bridge』的意義同樣,是 Native 和非 Native 之間的橋樑,它的核心是 構建 Native 和非 Native 間消息通訊的通道,並且是 雙向通訊的通道。
雙向通訊的通道:
  • JSNative 發送消息 : 調用相關功能、通知 Native 當前 JS 的相關狀態等。
  • NativeJS 發送消息 : 回溯調用結果、消息推送、通知 JS 當前 Native 的狀態等。

四、JSBridge 流程

H5 ->經過某種方式觸發一個 url -> Native捕獲到 url,進行分析->原生作處理-> Native 調用 H5JSBridge 對象傳遞迴調。

實現流程

  • 第一步:設計出一個 NativeJS 交互的全局橋對象
  • 第二步: JS 如何調用 Native
  • 第三步: Native 如何得知 api 被調用
  • 第四步:分析 url- 參數和回調的格式
  • 第五步: Native 如何調用 JS
  • 第六步: H5api 方法的註冊以及格式

五、JSBridge 的實現原理

  • JavaScript 調用 Native 推薦使用 注入 API 的方式(iOS6 忽略,Android 4.2如下使用 WebViewClientonJsPrompt 方式)。
  • Native 調用 JavaScript 則直接執行拼接好的 JavaScript 代碼便可。

React NativeiOS 端舉例:JavaScript 運行在 JSCore 中,實際上能夠與上面的方式同樣,利用注入 API 來實現 JavaScript 調用 Native 功能。不過 React Native 並無設計成 JavaScript 直接調用 Object-C,而是 爲了與 Native 開發裏事件響應機制一致,設計成 須要在 Object-C 去調 JavaScript 時才經過返回值觸發調用。原理基本同樣,只是實現方式不一樣。

1. Native 調 JS

1)安卓

native 調用 js 比較簡單,只要遵循:」javascript: 方法名(‘參數,須要轉爲字符串’)」的規則便可。

mWebView.evaluateJavascript("javascript: 方法名('參數,須要轉爲字符串')", new ValueCallback() {
        @Override public void onReceiveValue(String value) { //這裏的value即爲對應JS方法的返回值 }
});
複製代碼
2)IOS

Native 經過 stringByEvaluatingJavaScriptFromString 調用 Html 綁定在 window 上的函數。

2. JS 調 Native

1)安卓

Native 中經過 addJavascriptInterface 添加暴露出來的 JS 橋對象,而後再該對象內部聲明對應的 API 方法。

private Object getJSBridge(){  
    Object insertObj = new Object(){ @JavascriptInterface public String foo(){ return "foo";  
        } @JavascriptInterface public String foo2(final String param){ return "foo2:" + param;  
        }  
    }; return insertObj;  
}
複製代碼
2)IOS

Native 中經過引入官方提供的 JavaScriptCore 庫(iOS7 以上),而後能夠將 api 綁定到 JSContext 上(而後 HtmlJS 默認經過 window.top.* 可調用)。

六、JSBridge 接口實現

JSBridge 的接口主要功能有兩個: 調用 Native(給 Native 發消息) 和 接被 Native 調用(接收 Native 消息)。

1. 消息都是單向的,那麼調用 Native 功能時 Callback 怎麼實現的?

JSBridgeCallback ,其實就是 RPC 框架的回調機制。固然也能夠用更簡單的 JSONP 機制解釋:

當發送 JSONP 請求時,url 參數裏會有 callback 參數,其值是 當前頁面惟一 的,而同時以此參數值爲 key 將回調函數存到 window 上,隨後,服務器返回 script 中,也會以此參數值做爲句柄,調用相應的回調函數。

callback 參數這個 惟一標識 是這個回調邏輯的關鍵。這樣,咱們能夠參照這個邏輯來實現 JSBridge:用一個自增的惟一 id,來標識並存儲回調函數,並把此 id 以參數形式傳遞給 Native,而 Native 也以此 id 做爲回溯的標識。這樣,便可實現 Callback 回調邏輯。

(function () {
    var id = 0,
        callbacks = {},
        registerFuncs = {};
    window.JSBridge = {
        // 調用 Native
        invoke: function(bridgeName, callback, data) {
            // 判斷環境,獲取不一樣的 nativeBridge
            var thisId = id ++; // 獲取惟一 id
            callbacks[thisId] = callback; // 存儲 Callback
            nativeBridge.postMessage({
                bridgeName: bridgeName,
                data: data || {},
                callbackId: thisId // 傳到 Native 端
            });
        },
        receiveMessage: function(msg) {
            var bridgeName = msg.bridgeName,
                data = msg.data || {},
                callbackId = msg.callbackId, // Native 將 callbackId 原封不動傳回
                responstId = msg.responstId;
            // 具體邏輯
            // bridgeName 和 callbackId 不會同時存在
            if (callbackId) {
                if (callbacks[callbackId]) { // 找到相應句柄
                    callbacks[callbackId](msg.data); // 執行調用
                }
            } elseif (bridgeName) {
                if (registerFuncs[bridgeName]) { // 經過 bridgeName 找到句柄
                    var ret = {},
                        flag = false;
                    registerFuncs[bridgeName].forEach(function(callback) => {
                        callback(data, function(r) {
                            flag = true;
                            ret = Object.assign(ret, r);
                        });
                    });
                    if (flag) {
                        nativeBridge.postMessage({ // 回調 Native
                            responstId: responstId,
                            ret: ret
                        });
                    }
                }
            }
        },
        register: function(bridgeName, callback) {
            if (!registerFuncs[bridgeName])  {
                registerFuncs[bridgeName] = [];
            }
            registerFuncs[bridgeName].push(callback); // 存儲回調
        }
    };
})();
複製代碼

七、JSBridge 如何引用

1. 由 Native 端進行注入

注入方式和 Native 調用 JavaScript 相似,直接執行橋的所有代碼。

優勢:橋的版本很容易與 Native 保持一致,Native 端不用對不一樣版本的 JSBridge 進行兼容;與此同時,

缺點:注入時機不肯定,須要實現注入失敗後重試的機制,保證注入的成功率,同時 JavaScript 端在調用接口時,須要優先。

2. 由 JavaScript 端引用

直接與 JavaScript 一塊兒執行。

優勢JavaScript 端能夠肯定 JSBridge 的存在,直接調用便可; 缺點:若是橋的實現方式有更改,JSBridge 須要兼容多版本的 Native Bridge 或者 Native Bridge 兼容多版本的 JSBridge

四11、web worker

一、什麼是 web worker?有哪些好處?有哪些問題?

Web Worker 就是爲 JavaScript 創造多線程環境,容許主線程建立 Worker 線程,將一些任務分配給後者運行。在主線程運行的同時,Worker 線程在後臺運行,二者互不干擾。等到 Worker 線程完成計算任務,再把結果返回給主線程。

好處

好處就是,一些計算密集型或高延遲的任務,被 Worker 線程負擔了,主線程(一般負責 UI 交互)就會很流暢,不會被阻塞或拖慢。

問題:

Worker 線程一旦新建成功,就會始終運行,不會被主線程上的活動(好比用戶點擊按鈕、提交表單)打斷。這樣有利於隨時響應主線程的通訊。可是,這也形成了 Worker 比較耗費資源,不該該過分使用,並且一旦使用完畢,就應該關閉。

二、使用 web worker 有哪些限制?

1. 同源限制

分配給 worker 的腳本文件,必須與主線程腳本文件同源。

2. DOM 限制

worker 線程沒法讀取主線程所在網頁的 DOM 對象,沒法使用 documentwindowparent 這些對象,能夠使用 navigatorlocation 對象。

3. 通訊限制

worker 線程和主線程再也不同一個上下文環境中,不能直接通訊,必須經過消息完成。

4. 腳本限制

worker 線程不能執行 alert 方法和 confirm 方法,可是能夠發出 ajax 請求。

5. 文件限制

worker 線程沒法讀取本地文件,不能打開文件系統,所加載的腳本,必須來自網絡,不能是 file:// 文件。

三、worker 線程怎樣監聽主線程的消息的?如何發送消息的?worker 線程又是如何關閉的?

Worker 線程內部須要有一個監聽函數,監聽 message 事件。

// 監聽
self.addEventListener('message', function (e) {
  // 發送消息
  self.postMessage('You said: ' + e.data);
}, false);
複製代碼

關閉 worker 線程

1)主線程關閉 worker 線程

worker.terminate()

2)worker 線程關閉

self.close()

四、worker 線程如何加載其餘腳本?

importScript('scripts.js')
importScript('scripts1.js', 'scripts2.js')
複製代碼

五、主線程和 worker 線程的 API

主線程 worker 線程
Worker.onerror:指定 error 事件的監聽函數 self.nameWorker 的名字
Worker.onmessage:指定 message 事件的監聽函數 self.onmessage:指定 message 事件的監聽函數
Worker.onmessageerror:指定 messageerror 事件的監聽函數 self.onmessageerror:指定 messageerror 事件的監聽函數
Worker.postMessage():向 Worker 線程發送消息 self.close():關閉 Worker 線程
Worker.terminate():當即終止 Worker 線程 self.postMessage():向產生這個 Worker 線程發送消息
self.importScripts():加載 JS 腳本

四12、webSocket

一、爲何須要 webSocket?有什麼特色?

1. 優點:

  1. 支持雙向通訊,實時性更強;
  2. 更好的二進制支持;
  3. ws 客戶端與服務端數據交換時,數據包頭部較小,更好的控制開銷;
  4. 支持拓展。

2. 特色:

  1. 創建在 TCP 協議之上,服務器端的實現比較容易。
  2. HTTP 協議有着良好的兼容性。默認端口也是 80 和 443,而且握手階段採用 HTTP 協議,所以握手時不容易屏蔽,能經過各類 HTTP 代理服務器。
  3. 數據格式比較輕量,性能開銷小,通訊高效。
  4. 能夠發送文本,也能夠發送二進制數據。
  5. 沒有同源限制,客戶端能夠與任意服務器通訊。
  6. 協議標識符是 ws(若是加密,則爲 wss),服務器網址就是 URL

ws

二、webSocket 的連接狀態?

  • 0 (WebSocket.CONNECTING) 正在連接中
  • 1 (WebSocket.OPEN) 已經連接而且能夠通信
  • 2 (WebSocket.CLOSING) 鏈接正在關閉
  • 3 (WebSocket.CLOSED) 鏈接已關閉或者沒有連接成功
相關文章
相關標籤/搜索