JS
是什麼類型的語言?程序在執行以前須要一個專門的編譯過程,把程序編譯成 爲機器語言的文件,運行時不須要從新翻譯,直接使用編譯的結果就好了。javascript
程序執行效率高,依賴編譯器,跨平臺性差些。css
C
、C++
都是編譯型語言。html
程序不須要編譯,程序在運行時才翻譯成機器語言,每執 行一次都要翻譯一次。前端
解釋型語言執行效率較低,且不能脫離解釋器運行,但它的跨平臺型比較容易,只需提供特定解釋器便可。vue
python
、JS
都是解釋型語言。java
JS
中有哪些強制類型轉換和隱式類型轉換?String()
Number()
Boolean()
parseInt()
parseFloat()
+ string
轉爲數字a + " "
轉爲字符串!var
轉爲布爾值eg
:var a = {name: 'yuhua'}
變量存儲狀況代碼區域 Code Segment
;a
放入 棧(Stack):本地變量、指針
;{name: 'yuhua'}
放入HeapTotal(堆):對象,閉包
。symbol
symbol
做爲一個對象的鍵名時,如何獲取?不能獲取 symbol
鍵:node
for in
與 for of
循環遍歷中,不會獲取 symbol
鍵;Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
方法獲取不到 symbol
鍵;能獲取 symbol
鍵:python
Object.getOwnPropertySymbols()
方法能夠獲取,返回一個數組;Reflect.ownKeys()
能夠獲取全部的鍵名,包括 symbol
鍵symbol
的類型轉換string
const symbolKey = Symbol(123)
String(symbolKey) // "Symbol(123)"
symbolKey.toString() // "Symbol(123)"
複製代碼
Boolean(symbolKey) // true
複製代碼
Number(symbolKey)
Uncaught TypeError: Cannot convert a Symbol value to a number
at Number (<anonymous>)
at <anonymous>:1:1
複製代碼
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]"
複製代碼
eval()
let funcStr = "function test(value){alert(value)}";
let test = eval("(false || "+funcStr+")");
test("函數可以執行");
複製代碼
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()("函數也可以執行")
複製代碼
null
和 undefined
的區別Null
null
表示一個"無"的對象,轉爲數值爲 0
;Number(null)
爲 0
5 + null
位 5
Undefined
undefined
;undefined
;undefined
;undefined
;Number(undefined)
爲 NaN
;5 + undefined
爲 NaN
。typeof
和 instanceof
的區別typeof
表示對某個變量類型的檢測,基本數據類型除了 null
都能正常的顯示爲對應的類型,引用類型除了函數會顯示爲 function
外,其餘的都是會顯示爲 object
;instanceof
用於檢測某個構造函數的原型對象在不在某個對象的原型鏈上。typeof
對 null
的錯誤顯示這只是 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
複製代碼
this
this
對於函數而言指向最後調用函數的那個對象,是函數運行時內部自動生成的一個內部對象,只能在函數內部使用;對於全局來講,this
指向 window
。
this
是在何時肯定的?函數調用時,指向最後調用的那個對象
call
、apply
、bind
三者的區別三個函數的做用都是將函數綁定到上下文中,用來改變函數中 this
的指向;三者的不一樣點在於語法的不一樣。
fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.apply(thisArg, [argsArray])
var bindFn = fun.bind(thisArg[, arg1[, arg2[, ...]]])
bindFn()
複製代碼
apply
和 call
的區別是 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
默認綁定到 window
。this
隱式綁定到該直接對象。window
。顯式綁定:經過 call()
、apply()
、bind()
方法把對象綁定到 this
上,叫作顯式綁定。new
綁定:若是函數或者方法調用以前帶有關鍵字 new
,它就構成構造函數調用。對於 this
綁定來講,稱爲 new
綁定。this
this
,因此須要經過查找做用域鏈來肯定 this
的值,這就意味着若是箭頭函數被非箭頭函數包含,this
綁定的就是最近一層非箭頭函數的 this
。arguments
對象,可是能夠訪問外圍函數的 arguments
對象。new
關鍵字調用,一樣也沒有 new.target
值和原型。call
、apply
和 bind
?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 // 返回函數執行結果
}
複製代碼
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 // 返回結果
}
複製代碼
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
指向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
複製代碼
testcopy()
var a = 1
function test () {
console.log(this.a)
}
const obj = {
a: 2,
test
}
const testCopy = obj.test
testCopy() // 1
// this 指向是在函數執行時肯定
複製代碼
setTimeout
中var a = 1
function test () {
console.log(this.a)
}
const obj = {
a: 2,
test
}
setTimeout(obj.test) // 1
// this 指向是在函數執行時肯定
複製代碼
JS
模塊化IIFE
自執行函數AMD
使用 requireJS
來編寫模塊化(依賴必須提早聲明好。)CMD
使用 seaJS
來編寫模塊化(支持動態引入依賴文件。)CommonJS
nodeJs
中自帶的模塊化UMD
兼容 AMD
、CommonJS
語法webpack(require.ensure)
:webpack 2.x
版本中的代碼分割ES Modules
: ES6
引入的模塊化,支持 import
來引入另外一個 js
script
標籤 type="module"
AMD
和 CMD
的區別
AMD
和CMD
最大的區別是對依賴模塊的執行時機處理不一樣,注意不是加載的時機或者方式不一樣,兩者皆爲異步加載模塊
AMD
推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊CMD
推崇就近依賴,只有在用到某個模塊的時候再去 require
CommonJS
規範的特色CommonJS
輸出的是值的拷貝,模塊內部再次改變也不會影響這個值(引用類型和基本類型有區別)ES6 modules
規範有什麼特色export
import
export ... from ...
來達到一箇中轉的效果export
和 import
命令處於模塊頂層,不能位於做用域內,處於代碼塊中,無法作靜態優化,違背了 ES6
模塊的設計初衷import
有提高效果,會提高到整個模塊的頭部,首先執行Babel
會把 export/import
轉化爲 exports/require
的形式,因此能夠使用 exports
和 import
CommonJS
和 ES6 Modules
規範的區別CommonJS
模塊是運行時加載,ES6Modules
是編譯時加載CommonJS
輸出值的拷貝,ES6Modules
輸出值的引用(模塊內部改變會影響引用)CommonJS
導入模塊能夠是一個表達式(是使用 require()
引入),ES6Modules
導入只能是字符串CommonJS
中 this 指向當前模塊,ES6Modules
中 this
指向 undefined
ES6Modules
中沒有 arguments
、require
、module
、exports
、__filename
、__dirname
這些頂層變量AMD
和 CMD
支持異步加載模塊
node require(X)
引入的處理順序是什麼樣的?X
是內置模塊,返回該模塊,再也不繼續執行;X
以 './'、'/'、'../'
開頭,將根據 X
所在的父模塊,肯定 X
的絕對路徑: a. 將 X
當成文件,依次查找,存在,返回該文件,再也不繼續執行; b. 將 X
當成目錄,依次查找目錄下的文件,存在,返回該文件,再也不繼續執行;X
不帶有路徑: a. 根據 X
所在的父模塊,肯定 X
可能的安裝目錄 b. 依次在每一個目錄中,將 X
當成文件名或者目錄名加載not found
錯誤有個 a.js
和 b.js
兩個文件,互相引用
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 執行完畢');
複製代碼
a.js
腳本先輸出一個 done
變量,而後加載另外一個腳本文件 b.js
。注意,此時 a.js
代碼就停在這裏,等待 b.js
執行完畢,再往下執行。b.js
執行到第二行,就會去加載 a.js
,這時,就發生了"循環加載"。系統會去 a.js
模塊對應對象的 exports
屬性取值,但是由於 a.js
尚未執行完,從 exports
屬性只能取回已經執行的部分,而不是最後的值。a.js
已經執行的部分,只有一行。exports.done = false;
複製代碼
b.js
來講,它從 a.js
只輸入一個變量 done
,值爲 false
。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
複製代碼
b.js
之中,a.js
沒有執行完畢,只執行了第一行。二是,main.js
執行到第二行時,不會再次執行 b.js
,而是輸出緩存的 b.js
的執行結果,即它的第四行。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
複製代碼
上面代碼中,參數 n
從 10
變爲 0
的過程當中,foo()
一共會執行 6
次,因此變量 counter
等於 6
。第二次調用 even()
時,參數 n
從 20
變爲 0
,foo()
一共會執行 11
次,加上前面的 6
次,因此變量 counter
等於17
。
JS
事件事件委託/事件代理:通常來講就是經過事件冒泡把一個元素的響應事件的函數代理到它的父層或者更外層元素上。
缺點:
focus/blur
)document
、window
、html
、body
的層級關係window > document > html > body
window
是 BOM
的核心對象,一方面用來獲取或者設置瀏覽器的屬性和行爲,一方面做爲一個全局對象;document
是一個跟文檔相關的對象,擁有一些操做文檔內容的功能;html
元素 和 document
元素對象是屬於 html
文檔的 DOM
對象。addEventListener
函數的第三個參數是什麼?boolean
時:true
時是捕獲,爲 false
時是冒泡。Object
時:capture
: Boolean
,表示 listener
會在該類型的事件捕獲階段傳播到該 EventTarget
時觸發。once
: Boolean
,表示 listener
在添加以後最多隻調用一次。若是是 true
, listener
會在其被調用以後自動移除。passive
:Boolean
,設置爲 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
Event
customEvent
document.createEvent('customEventName')
和 initEvent()
Event
let myEvent = new Event('my_event_name')
複製代碼
customEvent
let myEvent = new CustomEvent('my_event_name', {
detail: {
// 須要傳遞的參數
// 在監聽的回調函數中獲取到:event.detail
}
})
複製代碼
document.createEvent('CustomEvent')
和initEvent()
let myEvent = document.createEvent('CustomEvent')
myEvent.initEvent(
// event_name 是事件名
// canBubble 是否冒泡
// cancelable 是否能夠取消默認行爲
)
複製代碼
dom.addEventListener('my_custom_name', function(e) {})
複製代碼
dispatchEvent(myEvent)
複製代碼
// 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)
複製代碼
JS
內部函數和閉包MDN:一個函數和對其周圍狀態(lexical environment,詞法環境)的引用捆綁在一塊兒(或者說函數被引用包圍),這樣的組合就是閉包(closure)。
簡單來講:可以讀取其餘函數內部變量的函數就是閉包。
for (var i = 0; i < 10; i++) {
(function (i) {
setTimeout(() => {
console.log(i)
}, 1000)
})(i)
}
複製代碼
通常來講在一個函數內部定義另一個函數,這樣的函數就是內部函數。
DOM
操做(44. 閉包
不是,即便是 1byte
的內存,也叫內存泄露。
不是,着通常是無限遞歸函數調用,致使棧內存溢出。
堆區。棧區不會泄露
大多數狀況下,後果不是很嚴重。可是過多的 DOM
操做會使網頁執行速度變慢。
仍然存在,直到瀏覽器關閉。
EventLoop
的執行過程EventLoop
的執行過程script
做爲一個宏任務進行執行;UI
線程渲染工做;web worker
任務,有則執行;requestAnimationFrame
由於 rAF
是官方推薦的用來作一些流暢動畫所應該使用的 API
,作動畫不可避免的會去更改 DOM
,而若是在渲染以後再去更改 DOM
,那就只能等到下一輪渲染機會的時候才能去繪製出來了,這顯然是不合理的。
rAF
在瀏覽器決定渲染以前給你最後一個機會去改變 DOM
屬性,而後很快在接下來的繪製中幫你呈現出來,因此這是作流暢動畫的不二選擇。
requestIdleCallback
requestIdleCallback
方法將在瀏覽器的空閒時段內調用的函數排隊。這使開發者可以在主事件循環上執行後臺和低優先級工做,而不會影響延遲關鍵事件,如動畫和輸入響應。
50ms
能夠確保用戶在無感知的延遲下獲得迴應。
EventLoop
循環注意點requestAnimationFrame
在從新渲染屏幕以前執行,很是適合用來作動畫。requestIdleCallback
在渲染屏幕以後執行,而且是否有空執行要看瀏覽器的調度,若是你必定要它在某個時間內執行,請使用 timeout
參數。resize
和 scroll
事件其實自帶節流,它只在 Event Loop
的渲染階段去派發事件到 EventTarget
上。for
循環和 setTimeout
在 for
循環中加入 setTimeout
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
複製代碼
var
改爲 let
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
複製代碼
for (var i = 0; i < 10; i++) {
(function (i) {
setTimeout(() => {
console.log(i)
}, 1000)
})(i)
}
複製代碼
for
循環改爲 forEach
循環[1,2,3,4].forEach(item => {
setTimeout(() => {
console.log(item)
}, 1000)
})
複製代碼
setTimeout
傳參for (var i = 0; i < arr.length; i++) {
setTimeout((i) => {
console.log(arr[i])
}, 1000, i)
}
複製代碼
for (var i = 0; i< 10; i++){
setTimeout(console.log(i),1000);
}
複製代碼
JS
中的 let
、const
、var
JS
中有幾種定義變量的方法?let
const
var
class
import
function
let
、const
、var
有什麼區別?var |
let |
const |
---|---|---|
沒有塊級做用域 | 有塊級做用域 | 有塊級做用域 |
聲明全局變量在 window 下 |
(全局屬性下) 全局變量不在全局屬性下 | 全局變量不在全局屬性下 |
重定義變量不會報錯 | 會報錯 | 會報錯 |
聲明變量 | 聲明變量 | 聲明一個常量 |
存在變量提高 | 不存在變量提高 | 不存在變量提高 |
聲明以後隨時賦值 | 聲明以後隨時賦值 | 聲明以後當即賦值 |
const
定義常量可不能夠修改?const
定義基礎類型是不能夠修改的;const
定義引用類型是能夠修改引用類型裏面的值。const
定義引用類型也不能改變它的值該怎麼作?Object.freeze
;proxy/Object.defineProperty
);configurable
、writable
屬性。ES5
的狀況下實現 let
和 const
?let
能夠經過自執行函數。
const
能夠經過 Object.defineProperty()
實現,設置 writable
。
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()
。
some
和 every
有什麼區別?從中文含義能看出來,some
是某些,every
是每個,它們都返回一個 Boolean
值。
用時基本上同樣,由於 js
裏面沒有數組類型,數組其實也是一個對象,key
和 value
。
for
循環;ES6
Set
去重;filter/includes/indexOf
;Map
、Object
去重。for
循環和 forEach
的性能哪一個更好一點?for
循環的性能更好
for
循環沒有任何額外的函數調用棧和上下文;forEach
不是普通的 for
循環的語法糖,還有諸多參數和上下文須要在執行的時候考慮進來,這裏可能拖慢性能。sort
排序是按照什麼方式來排序的?默認排序順序是在將元素轉換爲字符串,而後比較它們的 UTF-16
代碼單元值序列時構建的。
reduce
遞歸實現join
和 split
實現flat
方法toString
和 split
實現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
}
複製代碼
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)
複製代碼
num.replace(/(\d)(?=(\d{3})+(\.|$))/g, "$1,")
複製代碼
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 : "");
}
複製代碼
map
、find
、every
、some
、forEach
等方法的第二個參數是幹什麼的?arr.every(callback(element[, index[, array]])[, thisArg])
複製代碼
thisArg
執行 callback
時使用的 this
值。
for in
和 for of
有什麼區別?比較 | for in |
for of |
---|---|---|
不一樣點 | 能夠遍歷普通對象 遍歷出數組的原型對象 能夠遍歷出數組自身屬性 遍歷出來的值是 key 不能夠遍歷 map/set 不能夠迭代 generators IE 支持 |
不能遍歷普通對象 不會遍歷出原型對象 不會遍歷自身屬性 遍歷出來的值是 value 能夠遍歷 map/set 能夠迭代 generators IE 不支持 |
相同點 | 能夠遍歷數組 能夠 break 中斷遍歷 |
能夠遍歷數組 能夠 break 中斷遍歷 |
Promise
sleep
函數(延遲函數)經過 promise
和 setTimeout
來簡單實現
/** * 延遲函數 * @param {Number} time 時間 */
function sleep (time = 1500) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, time)
})
}
複製代碼
promise
構造函數、then
方法、catch
方法、finally
方法哪一個異步哪一個同步?promise
構造函數是同步執行的,then
、catch
和 finally
方法是異步執行的。
promise
?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.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)
)
}
複製代碼
Promise.any
Promise.any(iterable)
接收一個 Promise
對象的集合,當其中的一個 promise
成功,就返回那個成功的 promise
的值。
缺點:有兼容問題
promise
,全部的 promise
都取得返回結果(無論成功/失敗都要返回值)Promise.all
改進和上面原理相似,只不過是當成功的時候不進行操做,當 reject
時進行 resolve
操做
Promise.allSettled()
Promise.allSettled(iterable)
返回一個在全部給定的 promise
都已經 fulfilled
或 rejected
後的 promise
。
缺點:有兼容問題
promise
的靜態方法有哪些?Promise.all(iterable)
接收一個 promise
數組對象(可迭代的 promise
實例對象),所有成功時,返回全部 promise
的數組集合;當其中一個失敗時,返回當前失敗的 promise
對象。
Promise.allSettled(iterable)
接收一個 promise
數組對象,所有完成時(無論成功/失敗)返回新的 promise
數組集合
Promise.any(iterable)
接收一個 promise
數組對象,當其中任何一個成功時,返回成功的 promise
值
Promise.race(iterable)
接收一個 promise
數組對象,當其中任意一個成功/失敗時,返回該 promise
值
Promise.reject(reason)
返回一個狀態爲失敗的 Promise
對象。
Promise.resolve(value)
返回一個狀態由給定 value
決定的 Promise
對象。
Promise.finally(onFinally)
在當前 promise
運行完畢後被調用,不管當前 promise
的狀態是完成( fulfilled
)仍是失敗( rejected
)
Promise.try(f)
接收一個函數,返回一個 promise
。
爲全部操做提供了統一的處理機制,因此若是想用 then
方法管理流程,最好都用 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
有幾種狀況?Promise
實例參數是 Promise
實例,那麼 Promise.resolve
將不作任何修改、原封不動地返回這個實例。
thenable
對象Promise.resolve()
方法會將這個對象轉爲 Promise
對象,而後就當即執行 thenable
對象的 then()
方法。
then()
方法的對象,或根本就不是對象若是參數是一個原始值,或者是一個不具備 then()
方法的對象,則 Promise.resolve()
方法返回一個新的 Promise
對象,狀態爲 resolved
。
直接返回一個 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
finally
的回調函數中不接收任何參數;promise
結束時,不管結果是 fulfilled
或者是 rejected
,都會執行 finally
回調函數;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
中接收到的數組順序同樣。
promise
爲何能夠進行鏈式調用由於 then
、catch
、finally
方法會返回一個新的 promise
,因此容許咱們進行鏈式調用。
async/await
async
函數是基於 generator
實現,因此涉及到 generator
相關知識。 在沒有async
函數以前,一般使用 co
庫來執行 generator
,因此經過 co
咱們也能模擬 async
的實現。
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();
});
}
複製代碼
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);
});
});
}
複製代碼
JSON.stringify
和 JSON.parse
JSON.stringify
定義:將一個 JavaScript
對象或值轉換爲 JSON
字符串。 參數:有三個參數
JSON.stringify(value[, replacer [, space]])
複製代碼
replacer
replacer
參數能夠是一個函數或者一個數組。 做爲函數,它有兩個參數,鍵( key
)和值( value
),它們都會被序列化。 replacer
是一個數組,數組的值表明將被序列化成 JSON
字符串的屬性名。 2. space
space
參數用來控制結果字符串裏面的間距。 若是是一個數字, 則在字符串化時每一級別會比上一級別縮進多這個數字值的空格; 若是是一個字符串,則每一級別會比上一級別多縮進該字符串。
JSON.parse
定義:用來解析 JSON
字符串。 參數:有兩個參數
JSON.parse(text[, reviver])
複製代碼
reviver
轉換器, 若是傳入該參數(函數),能夠用來修改解析生成的原始值。
toJSON()
方法,該方法定義什麼值將被序列化。undefined
、任意的函數以及 symbol
值,在序列化過程當中會被忽略(出如今非數組對象的屬性值中時)或者被轉換成 null
(出如今數組中時)。函數、undefined
被單獨轉換時,會返回 undefined
,如JSON.stringify(function(){})
or JSON.stringify(undefined)
。symbol
爲屬性鍵的屬性都會被徹底忽略掉,即使 replacer 參數中強制指定包含了它們。Date
日期調用了 toJSON()
將其轉換爲了 string
字符串(同Date.toISOString()
),所以會被當作字符串處理。NaN
和 Infinity
格式的數值及 null
都會被當作 null
。Map/Set/WeakMap/WeakSet
,僅會序列化可枚舉的屬性。==
、===
和 Object.is()
==
兩邊值類型不一樣的時候,先進行類型轉換,在比較===
不進行類型轉換,直接值比較Object.is(val1, val2)
判斷兩個值是否爲同一值==
類型轉換是怎麼轉換的?null
或者是 undefined
,若是是,返回 true
string
或者 number
,若是是,將 string
轉換爲 number
boolean
,若是是,將其中一方轉爲 number
在進行判斷object
,且另一方是 string
、number
、symbol
,若是是,將 object
轉爲原始類型進行判斷(valueOf()
方法)NaN
,則直接返回 false
[] == ![]
的值爲何?答案:爲 true
![]
會被轉換爲 false
,所以此時爲 [] == false
boolean
,把 boolean
轉爲 number
,因此此時爲 [] == 0
[]
轉爲原始類型,調用數組的 toString()
方法,[].toString() = ''
,因此此時爲 '' == 0
string
轉爲 number
,''
轉爲 number
爲 0,因此此時 0 == 0
0 == 0
,爲 true
不會進行強制類型轉換
undefined
null
true
或 false
+0
-0
NaN
NaN
且爲同一個值防抖:是屢次執行改成最後一次執行 節流:是將屢次執行改成每隔一段時間執行
思路: 觸發高頻事件後 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))
複製代碼
高頻事件觸發,但在 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))
複製代碼
cookie
、sessionStorage
和localStorage
cookie
用來保存登陸信息,大小限制爲 4KB
左右localStorage
是 Html5
新增的,用於本地數據存儲,保存的數據沒有過時時間,通常瀏覽器大小限制在 5MB
sessionStorage
接口方法和 localStorage
相似,但保存的數據的只會在當前會話中保存下來,頁面關閉後會被清空。名稱 | 生命期 | 大小限制 | 與服務器通訊 | 是否能夠跨域 |
---|---|---|---|---|
cookie |
通常由服務器生成,可設置失效時間。若是在瀏覽器端生成 Cookie ,默認是關閉瀏覽器後失效 |
4KB |
每次都會攜帶在 HTTP 頭中,若是使用 cookie 保存過多數據會帶來性能問題 |
通常不可,相同 domain 下能夠容許接口請求攜帶 cookie |
localStorage |
除非被清除,不然永久保存 | 5MB |
僅在瀏覽器中保存,不與服務器通訊 | 不可 |
sessionStorage |
僅在當前會話下有效,關閉頁面或瀏覽器後被清除 | 5MB |
僅在瀏覽器中保存,不與服務器通訊 | 不可 |
localStorage
進行怎麼進行跨域存儲?localStorage
是不能夠進行跨域操做的,可是想進行跨域操做能夠使用 postMessage
,websocket
進行變相的跨域操做。
同源策略是一個重要的安全策略,它用於限制一個 origin
的文檔或者它加載的腳本如何能與另外一個源的資源進行交互,它能幫助阻隔惡意文檔,減小可能被攻擊的媒介。
所謂同源策略,是指只有在地址的:
均同樣的狀況下,才容許訪問相同的 cookie
、localStorage
,以及訪問頁面的 DOM
或是發送 Ajax
請求。
ajxa
請求Dom
的查詢同源策略確實能規避一些危險,不是說有了同源策略就安全,只是說同源策略是一種瀏覽器最基本的安全機制,畢竟能提升一點攻擊的成本。
ajax
同源策略、Dom
同源策略)。jsonp
cors
postMessage
websocket
Node
中間件代理(兩次跨域)nginx
反向代理window.name + iframe
location.hash + iframe
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
跨域的斷定流程Access-Control-Allow-origin
字段進行匹配,若無該字段說明不容許跨域,報錯,有該字段進行比對,判斷是否能夠跨域。簡單請求是指知足如下條件的:
get
、post
、head
其中一種方法進行請求的;http
的頭信息不超出一下狀況:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:值僅限於 application/x-www-form-urlencoded
、multipart/form-data
、text/plain
XMLHttpRequestUpload
對象沒有註冊任何的事件監聽器;XMLHttpRequestUpload
對象能夠使用 XMLHttpRequest.upload
屬性訪問。 請求中沒有使用 ReadableStream
對象。對服務器有特殊要求的請求(簡單請求以外就是非簡單請求)。
例如:請求方式是 put
、delete
,Content-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
})
複製代碼
js
的繼承方式prototype
子類型的原型爲父類型的一個實例對象。
Child.prototype = new Parent()
複製代碼
優勢:
缺點:
call
在子類型構造函數中通用 call()
調用父類型構造函數
function Child(name, age, price) {
Parent.call(this, name, age) // 至關於: this.Parent(name, age)
}
複製代碼
優勢:
缺點:
prototype + call
調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用。
function Child (name, age, price) {
Parent.call(this, name, age)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child//組合繼承也是須要修復構造函數指向的
複製代碼
優勢:
缺點:
經過父類原型和子類原型指向同一對象,子類能夠繼承到父類的公有方法當作本身的公有方法,並且不會初始化兩次實例方法/屬性,避免的組合繼承的缺點。
function Child (name, age, price) {
Parent.call(this, name, age)
}
Child.prototype = Parent.prototype
複製代碼
優勢:
缺點:
藉助原型能夠基於已有的對象來建立對象,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)
複製代碼
優勢:
簡單來講就是相鄰兩個元素進行對比,按照你須要的排序方式(升序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
}
複製代碼
主要仍是從算法所佔用的「時間」和「空間」兩個維度去考量。
「 大O符號表示法 」,即 T(n) = O(f(n))
時間複雜度的公式是: T(n) = O( f(n) )
,其中 f(n)
表示每行代碼執行次數之和,而 O
表示正比例關係,這個公式的全稱是:算法的漸進時間複雜度。
• 常數階 O(1)
• 對數階 O(logN)
• 線性階 O(n)
• 線性對數階 O(nlogN)
• 平方階 O(n²)
• 立方階 O(n³)
• K次方階 O(n^k)
• 指數階 (2^n)
O(1)
不管代碼執行了多少行,只要是沒有循環等複雜結構,那這個代碼的時間複雜度就都是 O(1)
var i = 1;
var j = 2;
++i;
j++;
var m = i + j;
複製代碼
O(n)
for
循環裏面的代碼會執行 n
遍,所以它消耗的時間是隨着n的變化而變化的,所以這類代碼均可以用 O(n)
來表示它的時間複雜度。
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
複製代碼
O(logN)
var i = 1;
while(i<n)
{
i = i * 2;
}
複製代碼
在 while
循環裏面,每次都將 i
乘以 2
,乘完以後,i
距離 n
就愈來愈近了。咱們試着求解一下,假設循環 x
次以後,i
就大於 2
了,此時這個循環就退出了,也就是說 2
的 x
次方等於 n
,那麼 x = log2^n
也就是說當循環 log2^n
次之後,這個代碼就結束了。所以這個代碼的時間複雜度爲:O(logn)
O(nlogN)
將時間複雜度爲 O(logn)
的代碼循環N遍的話,那麼它的時間複雜度就是 n * O(logN)
,也就是了 O(nlogN)
。
for(m=1; m<n; m++)
{
i = 1;
while(i<n)
{
i = i * 2;
}
}
複製代碼
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)
來定義。
空間複雜度比較經常使用的有:O(1)、O(n)、O(n²)
。
O(1)
若是算法執行所須要的臨時空間不隨着某個變量 n
的大小而變化,即此算法空間複雜度爲一個常量,可表示爲 O(1)
var i = 1;
var j = 2;
++i;
j++;
var m = i + j;
複製代碼
代碼中的 i
、j
、m
所分配的空間都不隨着處理數據量變化,所以它的空間複雜度 S(n) = O(1)
O(n)
var arr = [1, 2, 3]
for(i=1; i<=arr.lemgth; ++i)
{
j = i;
j++;
}
複製代碼
第一行定義了一個數組出來,這個數據佔用的大小爲 n
,這段代碼的 2-6
行,雖然有循環,但沒有再分配新的空間,所以,這段代碼的空間複雜度主要看第一行便可,即 S(n) = O(n)
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();
}
}
}
}
複製代碼
ajax
的 readyState
的狀態0
未初始化,尚未調用 open()
方法1
啓動,已經調用 open()
方法,可是沒有調用 send()
方法2
發送,已經調用 send()
方法,可是還沒有接收響應3
接收,已經接收到部分響應數據4
完成,已經接收到所有響應數據Axios
Axios
本質上也是對原生 XHR
的封裝,只不過它是 Promise
的實現版本,符合最新的 ES
規範
node.js
建立 http
請求Promise API
CSRF
Fetch
Fetch API
提供了一個 JavaScript
接口,用於訪問和操縱 HTTP
管道的部分,例如請求和響應。它還提供了一個全局 fetch()
方法,該方法提供了一種簡單,合理的方式來跨網絡異步獲取資源。
Promise
實現,支持 async/await
isomorphic-fetch
Fetch
請求默認是不帶 cookie
的,須要設置 fetch(url, {credentials: 'include'})
reject
,只有網絡錯誤這些致使請求不能完成時,fetch
纔會被 reject
。new
操做符new
的實現流程this
就指向了這個新對象);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
}
複製代碼
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
}
複製代碼
Map
、WeakMap
和 set
、WeakSet
有什麼區別?
WeakMap
和WeakSet
都是弱引用
弱引用是指不能確保其引用的對象不會被垃圾回收器回收的引用,換句話說就是可能在任意時間被回收。
弱引用隨時都會消失,遍歷機制沒法保證成員的存在
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))
複製代碼
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 //已撤銷
複製代碼
一個程序中只能存在一個全局執行上下文。
這是默認的、最基礎的執行上下文。不在任何函數中的代碼都位於全局執行上下文中。它作了兩件事:
window
對象。this
指針指向這個全局對象。能夠有無數個函數執行上下文。
每次調用函數時,都會爲該函數建立一個新的執行上下文。每一個函數都擁有本身的執行上下文,可是隻有在函數被調用的時候纔會被建立。
Eval
函數執行上下文:js
的 eval
函數執行其內部的代碼會建立屬於本身的執行上下文, 不多用並且不建議使用。
window
對象;arguments
,提高函數聲明和變量聲明。JavaScript
始終從代碼嵌套的最內層開始,若是最內層沒有找到變量,就會跳轉到上一層父做用域中查找,直到找到該變量。this
指向:肯定 this
的指向。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上下文環境
複製代碼
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)
複製代碼
原型:JS
聲明構造函數(用來實例化對象的函數)時,會在內存中建立一個對應的對象,這個對象就是原函數的原型。
構造函數默認有一個 prototype
屬性,prototype
的值指向函數的原型。同時原型中也有一個 constructor
屬性,constructor
的值指向原函數。
經過構造函數實例化出來的對象,並不具備 prototype
屬性,其默認有一個 __proto__
屬性,__proto__
的值指向構造函數的原型對象。在原型對象上添加或修改的屬性,在全部實例化出的對象上均可共享。
當在實例化的對象中訪問一個屬性時,首先會在該對象內部尋找,如找不到,則會向其 __proto__
指向的原型中尋找,如仍找不到,則繼續向原型中 __proto__
指向的上級原型中尋找,直至找到或 Object.prototype
爲止,這種鏈狀過程即爲原型鏈。
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]
}
}
}
複製代碼
js
的垃圾回收(GC
)V8
內存限制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
區域兩個區域組成From
區域和 To
區域各佔 16MFrom
區域和 To
區域各佔 8MScavenge
算法Scavenge
爲新生代採用的算法,是一種採用複製的方式實現的垃圾回收算法。
新生代掃描的時候是一種廣度優先的掃描策略
它將內存分爲 from
和 to
兩個空間。每次 gc
,會將 from
空間的存活對象複製到 to
空間。而後兩個空間角色對換(又稱反轉)。
該算法是犧牲空間換時間,因此適合新生代,由於它的對象生存週期較短。
FROM
區域中的存活對象,若是還活着,拷貝到 TO
空間,全部存活對象拷貝完後,清空(釋放) FROM
區域TO
的空間使用佔比超過 25%,或者超大對象memory
中能夠經過拍快照看變量是否被垃圾回收undefined
或 null
都能將引用計數減去 1Mark-Sweep
和 Mark-Compact
mark-sweep
標記清除
mark-compact
標記整理
GC
耗時比較長GC
期間沒法想聽,STOP-THE-WORLD
V8
有一個優化方案,增量處理,把一個大暫停換成多個小暫停 INCREMENT-GC
假設有10個大小的內存,內存佔用了6個,
Mark-Sweep
模式垃圾回收:A b C d E f 空 空 空 空
//對上面每一個對象作上標記,大寫表示活着,小寫表示死了
//這時候,會存在一個問題,就是內存碎片沒法使用,由於小寫的內存沒有跟後面空空空空的內存放在一塊兒,不能使用
複製代碼
Mark-Compact
模式垃圾回收A C E b d f 空 空 空 空
複製代碼
A C E 空 空 空 空 空 空 空
複製代碼
回收算法 | Mark-Sweep |
Mark-Compact |
Scavenge |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空間開銷 | 少 | 少 | 雙倍空間(無碎片) |
是否移動對象 | 否 | 是 | 是 |
V8
老生代主要用 Mark-Sweep
,由於 Mark-Compact
須要移動對象,執行速度不快。空間不夠時,纔會用 Mark-Compact
SRP
)一個對象或方法只作一件事情。
LKP
)應當儘可能減小對象之間的交互。
OCP
)軟件實體(類、模塊、函數)等應該是能夠 擴展的,可是不可修改
策略模式是指對一系列的算法定義,並將每個算法封裝起來,並且使它們還能夠相互替換。策略模式讓算法獨立於使用它的客戶而獨立變化。 優勢:
示例:
緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,若是傳遞進來的參數跟以前的一致,則能夠直接返回前面存儲的運算結果,提供效率以及節省開銷。
緩存代理,就是將前面使用的值緩存下來,後續還有使用的話,就直接拿出來用。
工廠模式是用來建立對象的一種最經常使用的設計模式。咱們不暴露建立對象的具體邏輯,而是將將邏輯封裝在一個函數中,那麼這個函數就能夠被視爲一個工廠。工廠模式根據抽象程度的不一樣能夠分爲:簡單工廠,工廠方法和抽象工廠。
簡單工廠的優勢在於,你只須要一個正確的參數,就能夠獲取到你所須要的對象,而無需知道其建立的具體細節。簡單工廠只能做用於建立的對象數量較少,對象的建立邏輯不復雜時使用。
工廠方法模式的本意是將實際建立對象的工做推遲到子類中,工廠方法模式就是將這個大廠拆分出各個小廠,每次添加新的產品讓小廠去生產,大廠負責指揮就行了。
抽象工廠模式並不直接生成實例, 而是用於對產品類簇的建立。
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
確保了只有一個實例
前端應用場景:
window
對象。在 JavaScript
開發中,對於這種只須要一個的對象,每每使用單例實現。爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。
代理模式主要有三種:保護代理、虛擬代理、緩存代理
迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。
JS
中數組的 map
forEach
已經內置了迭代器
也稱做觀察者模式,定義了對象間的一種一對多的依賴關係,當一個對象的狀態發 生改變時,全部依賴於它的對象都將獲得通知。
JS中的事件就是經典的發佈-訂閱模式的實現
用一種鬆耦合的方式來設計程序,使得請求發送者和請求接收者可以消除彼此之間的耦合關係 命令(command
)指的是一個執行某些特定事情的指令
strict
模式下會報錯,非 strict
模式下靜默失敗。IIFE
中的函數是函數表達式,而不是函數聲明。FD
)Program level
),要麼處於其它函數的主體(FunctionBody
)中function funName () {}
複製代碼
FE
)// 函數表達式
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'
複製代碼
咱們將它與 FD
和 FE
區分開來。其主要特色在於這種函數的[[Scope
]]屬性僅包含全局對象
var x = 10;
function foo() {
var x = 20;
var y = 30;
var bar = new Function('alert(x); alert(y);');
bar(); // 10, "y" 未定義
}
複製代碼
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
複製代碼
XSS
攻擊和 CSRF
攻擊XSS
攻擊XSS(Cross Site Scripting)
:跨域腳本攻擊。
不須要你作任何的登陸認證,它會經過合法的操做(好比在 url
中輸入、在評論框中輸入),向你的頁面注入腳本(多是 js
、hmtl
代碼塊等)。
script
、style
、iframe
等節點)DOM Parse
轉換,校訂不配對的 DOM
標籤。HttpOnly
。DOM
:惡意修改 DOM
結構,基於客戶端CSRF
攻擊SRF(Cross-site request forgery)
:跨站請求僞造。
A
,並在本地生成 Cookie
。(若是用戶沒有登陸網站 A
,那麼網站 B
在誘導的時候,請求網站 A
的 api
接口時,會提示你登陸)。A
的狀況下,訪問危險網站 B
(實際上是利用了網站 A
的漏洞)。token
驗證;token
隱藏在 http
請求的 head
中。referer
驗證;驗證頁面來源。CSRF
:須要用戶先登陸網站 A
,獲取 cookie
。XSS
:不須要登陸。CSRF
:是利用網站 A
自己的漏洞,去請求網站 A
的 api
。XSS
:是向網站 A
注入 JS
代碼,而後執行 JS
裏的代碼,篡改網站 A
的內容。input
輸入框輸入即請求後端接口,頻繁請求以後怎樣肯定最後一次接口的返回值?前端請求接口的時候會把 input 輸入框中的值傳給後端,此時後端返回接口數據時把前端傳入的值返回回去,頁面渲染時只須要進行判斷便可。
當再次請求的時候把上次的請求終止掉:
ajax
:abort()
axios
: CancelToken
fetch
:AbortController
百度用的就是這種取消請求的方式
js:ss1.bdstatic.com/5eN1bjq8AAU…
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()
}
複製代碼
rem
rem(font size of the root element)
是指相對於根元素的字體大小的單位。 1rem
等於根元素 htm
的 font-size
,即只須要設置根元素的 font-size
,其它元素使用 rem
單位時,設置成相應的百分比便可。
rem(倍數) = width / (html的font-size)=> width = (html的font-size) * rem(倍數)
複製代碼
只要 html
的 font-size
的大小變了,width
就會自動變,因此 rem
是經過動態設置 html
的 font-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 = 100px
,iphone6/7/8 plus
中設置 width: 6.5rem
元素的寬爲多少?plus
中寬度爲 414 因此寬度爲 414 / 750 * 6.5 * 100 0.32 rem
爲 414 / 750 * 0.32 * 100
dns-prefetch
、prefetch
、preload
、defer
、async
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
方法是資源加載完成的回調函數defer
和 async
//defer
<script defer src="script.js"></script>
//async
<script async src="script.js"></script>
複製代碼
defer
和 async
都是異步(並行)加載資源,不一樣點是 async
是加載完當即執行,而 defer
是加載完不執行,等到全部元素解析完再執行,也就是 DOMContentLoaded
事件觸發以前。 由於 async
加載的資源是加載完執行,因此它比不能保證順序,而 defer
會按順序執行腳本。
HTML
,生成 DOM
樹CSS
,生成 CSSOM
樹DOM
樹和 CSSOM
樹結合,生成渲染樹(Render Tree
)Layout
(迴流):根據生成的渲染樹,進行迴流(Layout
),獲得節點的幾何信息(位置,大小)Painting
(重繪):根據渲染樹以及迴流獲得的幾何信息,獲得節點的絕對像素Display
:將像素髮送給 GPU
,展現在頁面上。(這一步其實還有不少內容,好比會在 GPU
將多個合成層合併爲同一個層,並展現在頁面中。而 css3
硬件加速的原理則是新建合成層)DOM
元素color
、background-color
、visibility
等),瀏覽器會將新樣式賦予給元素並從新繪製它,這個過程稱爲重繪。css
table
佈局。DOM
樹的最末端改變 class
。position
屬性爲 absolute
或 fixed
的元素上CSS
表達式(例如:calc()
)CSS3
硬件加速(GPU
加速)
transform
opacity
filters
Will-change
JavaScript
style
屬性,或者將樣式列表定義爲 class
並一次性更改 class
屬性,修改 style
的 cssText
屬性或者修改元素的 className
值。DOM
,建立一個 documentFragment
,在它上面應用全部 DOM
操做,最後再把它添加到文檔中display: none
,操做結束後再把它顯示出來。由於在 display
屬性爲 none
的元素上進行的 DOM
操做不會引起迴流和重繪css3
硬件加速,可讓 transform
、opacity
、filters
這些動畫不會引發迴流重繪 。可是對於動畫的其它屬性,好比 background-color
這些,仍是會引發迴流重繪的,不過它仍是能夠提高這些動畫的性能。瀏覽器接收到頁面文檔後,會將文檔中的標記語言解析爲 DOM
樹。DOM
樹和 CSS
結合後造成瀏覽器構建頁面的渲染樹。 渲染樹中包含了大量的渲染元素,每個渲染元素會被分到一個圖層中,每一個圖層又會被加載到 GPU
造成渲染紋理,而圖層在 GPU
中 transform
是不會觸發 repaint
的,最終這些使用 transform
的圖層都會由獨立的合成器進程進行處理。
3D
或者 CSS transform
<video>
和 <canvas>
標籤CSS filters
z-index
屬性3D
和 2D transform
的區別就在於,瀏覽器在頁面渲染前爲 3D
動畫建立獨立的複合圖層,而在運行期間爲 2D
動畫建立。動畫開始時,生成新的複合圖層並加載爲 GPU
的紋理用於初始化 repaint
。而後由 GPU
的複合器操縱整個動畫的執行。最後當動畫結束時,再次執行 repaint
操做刪除複合圖層。
GPU
加載了大量的紋理,那麼很容易就會發生內容問題,這一點在移動端瀏覽器上尤其明顯,因此,必定要牢記不要讓頁面的每一個元素都使用硬件加速。GPU
渲染會影響字體的抗鋸齒效果。這是由於 GPU
和 CPU
具備不一樣的渲染機制。即便最終硬件加速中止了,文本仍是會在動畫期間顯示得很模糊。JSBridge
JSBridge
JSBridge
是一種 JS
實現的 Bridge
,鏈接着橋兩端的 Native
和 H5
。它在 APP
內方便地讓 Native
調用 JS
,JS
調用 Native
,是雙向通訊的通道。JSBridge
主要提供了 JS
調用 Native
代碼的能力,實現原生功能如查看本地相冊、打開攝像頭、指紋支付等。
H5
和 native
的區別name |
H5 |
Native |
---|---|---|
穩定性 | 調用系統瀏覽器內核,穩定性較差 | 使用原生內核,更加穩定 |
靈活性 | 版本迭代快,上線靈活 | 迭代慢,須要應用商店審覈,上線速度受限制 |
受網速 影響 | 較大 | 較小 |
流暢度 | 有時加載慢,給用戶「卡頓」的感受 | 加載速度快,更加流暢 |
用戶體驗 | 功能受瀏覽器限制,體驗有時較差 | 原生系統 api 豐富,能實現的功能較多,體驗較好 |
可移植性 | 兼容跨平臺跨系統,如 PC 與 移動端,iOS 與 Android |
可移植性較低,對於 iOS 和 Android 須要維護兩套代碼 |
### 三、JSBridge 的用途 |
||
JSBridge 就像其名稱中的『Bridge 』的意義同樣,是 Native 和非 Native 之間的橋樑,它的核心是 構建 Native 和非 Native 間消息通訊的通道,並且是 雙向通訊的通道。 |
||
雙向通訊的通道: |
JS
向 Native
發送消息 : 調用相關功能、通知 Native
當前 JS
的相關狀態等。Native
向 JS
發送消息 : 回溯調用結果、消息推送、通知 JS
當前 Native
的狀態等。JSBridge
流程
H5
->經過某種方式觸發一個url
->Native
捕獲到url
,進行分析->原生作處理->Native
調用H5
的JSBridge
對象傳遞迴調。
實現流程
Native
與 JS
交互的全局橋對象JS
如何調用 Native
Native
如何得知 api
被調用url-
參數和回調的格式Native
如何調用 JS
H5
中 api
方法的註冊以及格式JSBridge
的實現原理JavaScript
調用 Native
推薦使用 注入 API
的方式(iOS6
忽略,Android 4.2
如下使用 WebViewClient
的 onJsPrompt
方式)。Native
調用 JavaScript
則直接執行拼接好的 JavaScript
代碼便可。React Native
的 iOS
端舉例:JavaScript
運行在 JSCore
中,實際上能夠與上面的方式同樣,利用注入 API
來實現 JavaScript
調用 Native
功能。不過 React Native
並無設計成 JavaScript
直接調用 Object-C
,而是 爲了與 Native
開發裏事件響應機制一致,設計成 須要在 Object-C
去調 JavaScript
時才經過返回值觸發調用。原理基本同樣,只是實現方式不一樣。
Native
調 JS
native
調用 js
比較簡單,只要遵循:」javascript:
方法名(‘參數,須要轉爲字符串’)」的規則便可。
mWebView.evaluateJavascript("javascript: 方法名('參數,須要轉爲字符串')", new ValueCallback() {
@Override public void onReceiveValue(String value) { //這裏的value即爲對應JS方法的返回值 }
});
複製代碼
IOS
Native
經過 stringByEvaluatingJavaScriptFromString
調用 Html
綁定在 window
上的函數。
JS
調 Native
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;
}
複製代碼
IOS
Native
中經過引入官方提供的 JavaScriptCore
庫(iOS7
以上),而後能夠將 api
綁定到 JSContext
上(而後 Html
中 JS
默認經過 window.top.*
可調用)。
JSBridge
接口實現JSBridge
的接口主要功能有兩個: 調用 Native
(給 Native
發消息) 和 接被 Native
調用(接收 Native
消息)。
Native
功能時 Callback
怎麼實現的?JSBridge
的 Callback
,其實就是 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
如何引用Native
端進行注入注入方式和 Native
調用 JavaScript
相似,直接執行橋的所有代碼。
優勢:橋的版本很容易與 Native
保持一致,Native 端不用對不一樣版本的 JSBridge
進行兼容;與此同時,
缺點:注入時機不肯定,須要實現注入失敗後重試的機制,保證注入的成功率,同時 JavaScript
端在調用接口時,須要優先。
JavaScript
端引用直接與 JavaScript
一塊兒執行。
優勢:JavaScript
端能夠肯定 JSBridge
的存在,直接調用便可; 缺點:若是橋的實現方式有更改,JSBridge
須要兼容多版本的 Native Bridge
或者 Native Bridge
兼容多版本的 JSBridge
。
web worker
web worker
?有哪些好處?有哪些問題?Web Worker
就是爲 JavaScript
創造多線程環境,容許主線程建立 Worker
線程,將一些任務分配給後者運行。在主線程運行的同時,Worker
線程在後臺運行,二者互不干擾。等到 Worker
線程完成計算任務,再把結果返回給主線程。
好處就是,一些計算密集型或高延遲的任務,被 Worker
線程負擔了,主線程(一般負責 UI
交互)就會很流暢,不會被阻塞或拖慢。
Worker
線程一旦新建成功,就會始終運行,不會被主線程上的活動(好比用戶點擊按鈕、提交表單)打斷。這樣有利於隨時響應主線程的通訊。可是,這也形成了 Worker
比較耗費資源,不該該過分使用,並且一旦使用完畢,就應該關閉。
web worker
有哪些限制?分配給 worker
的腳本文件,必須與主線程腳本文件同源。
DOM
限制worker
線程沒法讀取主線程所在網頁的 DOM
對象,沒法使用 document
、window
、parent
這些對象,能夠使用 navigator
和 location
對象。
worker
線程和主線程再也不同一個上下文環境中,不能直接通訊,必須經過消息完成。
worker
線程不能執行 alert
方法和 confirm
方法,可是能夠發出 ajax
請求。
worker
線程沒法讀取本地文件,不能打開文件系統,所加載的腳本,必須來自網絡,不能是 file://
文件。
worker
線程怎樣監聽主線程的消息的?如何發送消息的?worker
線程又是如何關閉的?Worker
線程內部須要有一個監聽函數,監聽 message
事件。
// 監聽
self.addEventListener('message', function (e) {
// 發送消息
self.postMessage('You said: ' + e.data);
}, false);
複製代碼
worker
線程worker
線程worker.terminate()
worker
線程關閉self.close()
worker
線程如何加載其餘腳本?importScript('scripts.js')
importScript('scripts1.js', 'scripts2.js')
複製代碼
worker
線程的 API
主線程 | worker 線程 |
---|---|
Worker.onerror :指定 error 事件的監聽函數 |
self.name : Worker 的名字 |
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 腳本 |
webSocket
webSocket
?有什麼特色?ws
客戶端與服務端數據交換時,數據包頭部較小,更好的控制開銷;TCP
協議之上,服務器端的實現比較容易。HTTP
協議有着良好的兼容性。默認端口也是 80 和 443,而且握手階段採用 HTTP
協議,所以握手時不容易屏蔽,能經過各類 HTTP
代理服務器。ws
(若是加密,則爲 wss
),服務器網址就是 URL
。webSocket
的連接狀態?0 (WebSocket.CONNECTING)
正在連接中1 (WebSocket.OPEN)
已經連接而且能夠通信2 (WebSocket.CLOSING)
鏈接正在關閉3 (WebSocket.CLOSED)
鏈接已關閉或者沒有連接成功