限於這是個自查手冊,回答不那麼太詳細,若是某個知識點下面有連接,本身又沒有深刻了解過的,理應點擊連接或自行搜索深刻了解。javascript
另外兩個部分 :css
Number
, String
, Boolean
, Undefined
, Null
, Symbol
html
typeof NaN
返回 number
前端
typeof null
時也返回 object
,此爲歷史遺留問題vue
由於數字和字符串在使用方法時會轉換爲包裝對象java
包裝對象就是其對應的構造函數創造出來的node
(1).toString() // (new Number(1)).toString()
複製代碼
new Number(1) {
__proto__
: Number ,[[PrimitiveValue]]
: 1 }react包裝對象上會有一個內部值 [[PrimitiveValue]],值爲被包裝的原始值git
這個對象在表達式結束後就會被銷燬,因此沒法給字符串/數字上添加屬性es6
JS 採用 IEEE 754雙精度64位存儲數據,因此並不是JS獨有這個問題,採用了這個規範的語言都有
64位 :1位符號位,11位指數位,52位尾數位
JS能表示最大的整數是 2^53 - 1(52個二進制 1 ),而不是 2^52 -1
0.1 和 0.2 在二進制中表現爲無限循環,因此須要在尾數位末尾處進行舍入,被稱爲精度丟失
兩個數相加以後就獲得了十進制小數位末位爲4而不爲0的結果
toFixed 能夠精確到某一位,捨棄小數位
Number.EPSILON ['epsɪlɒn] 是 JS 能表示最小精度 2^(-52)
const isEquel = (a, b) => Math.abs(a - b) < Number.EPSILON // 相等
複製代碼
轉換成整數運算
/** * 精確加法 */
function add(num1, num2) {
const num1Digits = (num1.toString().split('.')[1] || '').length;
const num2Digits = (num2.toString().split('.')[1] || '').length;
const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
return (num1 * baseNum + num2 * baseNum) / baseNum;
}
add(0.1,0.2); // 0.3
複製代碼
除了如下 5 種,其餘都被轉爲 true
toString(10,2) // "1010" 輸入數字時,第二個參數可選爲進制
toString(new Date()) // Wed Jan 20 2021 20:06:24 GMT+0800 (中國標準時間)
toString([1,2]) // "1,2"
複製代碼
不少內置類型的原型上都被重寫了toString方法
判斷類型時,能夠調用Object上的toString方法如數組 ({}).toString.call(x) === '[object Array]'
1536416960724
的從 1970 年 1 月 1 日午夜開始計的毫秒數 ;其餘都未自定義即 Object.prototype.valueOf
,返回調用者自身。用於轉換對象到原始值的內置函數,經過Symbol.toPrimitive
能夠覆寫一個對象的轉換到原始值的行爲
它有個參數 hint
,通常有兩個可傳入值 "number" "string",內部根據上下文選擇傳入或不傳入
toString
關於對象更細節的轉化,請了解 toPrimitive
ECMAScript7規範中的ToPrimitive抽象操做
boolean,true 轉 1 false 轉 0
null 轉 0,undefined 轉 NaN
string
""
或"\n"
,返回0。對象,調用 ToPrimitive
方法,PreferredType
參數爲 "number"
,即
1. 調用 valueOf 方法
2.調用 toString 方法
3. 轉到 string 的狀況
Number([1]) // 1
1. [1].valueOf() 返回 [1]
2. [1].toSting() 返回 "1"
3. "1" 轉爲 1
Number([1,1]) // NaN
1. [1,1].valueOf() 返回 [1,1]
2. [1,,1].toSting() 返回 "1,1"
3. "1,1" 轉爲 NaN
js將對象轉換爲基本類型時,會調用內部函數 ToPrimitive
進行轉換,
分爲如下兩點
valueOf
後 toString
,toString
後 valueOf
考慮到這兩種類型實際都是調用了 toString
而 valueOf
並未改變輸出 (除包裝類型外),
因此默認對象的隱式轉換都是調用了 toString
[] == false // true []先調用 toString轉化爲"",""取布爾是false
![] // false
{} + 1 // [object Object]1
複製代碼
+
b 的轉換表中 object 是非包裝對象(下表能夠不看,看總結便可)
左右值組合\符號 | + |
---|---|
string number | number -> string |
string boolean | boolean -> string |
string object | object -> object.toString() |
number boolean | boolean -> number |
number object | number -> string,object -> object.toString() |
boolean boolean | boolean -> number |
boolean object | boolean -> string,object -> object.toString() |
object object | object -> object.toString() |
null number | null -> 0 |
null object | null -> "null",object -> object.toString() |
null boolean | null -> 0,boolean -> number |
null string | null -> "null" |
null null | 0 |
undefined number | undefined -> NaN |
undefined object | undefined -> "undefined",object -> object.toString() |
undefined boolean | undefined -> NaN,boolean -> number |
undefined string | undefined -> "undefined" |
undefined undefined | NaN |
undefined null | NaN |
注意 undefined 到 number 轉換爲 NaN,而 null 轉換爲 0
經過上表的排列組合咱們看出:
1.一邊有對象(非包裝)先轉爲字符串(其實是調用 toPrimitive)
特別注意 Date 對象,它也是轉爲字符串
new Date() + 1 // "Fri Mar 19 2021 10:59:08 GMT+0800 (中國標準時間)1"
複製代碼
2.一邊是字符串,另外一邊也轉爲字符串
1 + "1" = "11"
3.一邊是布爾值,另外一邊是數字或布爾值,布爾值轉爲數字
4.一邊是 null 或 undefined,另外一邊是 字符串 或者 數字,跟着另外一邊轉;若都不是,null 或 undefined 先轉爲數字
undefined + true // NaN
null + true // 1
複製代碼
5.不斷從上往下檢索規則,直到兩邊都是字符串或者數字。
注意 +a
和 b + a
,對 a的轉換是不同的:+a 是轉換到 number,而 b + a 須要按照上述規則進行轉換後相加
==
b 的轉換對象(非包裝)比較或者不一樣類型比較時:
1.兩邊是對象對比地址
2.一邊是對象先調用 toString(實際是 toPrimitive)
3.一邊是布爾值轉換爲數字
4.兩邊分別是數字和字符串時,字符串轉換爲數字
5.不斷從上往下檢索規則,直到兩邊類型相同。
其餘狀況
null == undefined // true undefined 和 null 不與其餘假值 `==`
NaN == NaN // false 須要判斷 NaN,應該用 Number.isNaN
複製代碼
實例
'true' == true // false
[] == ![] // true
![] 轉化爲 false:除了 0,"",NaN,undefined,null,其餘轉布爾時都轉爲true,再取反爲false
符合 2 和 3,[] 轉爲 "",false 轉爲 0
符合 4 ,"" 轉爲 0
0 == 0 返回 true
關於Symbol,使用得很少,它的做用是做爲一個惟一值,做對象的鍵,防止屬性被覆蓋或覆蓋已存在屬性
如手寫 apply,爲了防止覆蓋傳入的函數上的屬性,咱們能夠用Symbol做爲鍵
function myApply(ctx,args = []){
if(typeof this !== 'function') {
throw new TypeError('not a function!')
}
const symbol = Symbol(0)
ctx[symbol] = this
const result = ctx[symbol](...args)
delete ctx[symbol]
return result
}
Fuction.prototype.apply = myApply
複製代碼
它第二個做用,它提供咱們訪問內置方法和覆寫內置行爲的可能。
Symbol上儲存着各類內置方法的鍵,
經過重寫類上的迭代器,能夠改變實例使用迭代器的行爲如
class Collection {
*[Symbol.iterator]() {
let i = 0;
while(this[i] !== undefined) {
yield this[i];
++i;
}
}
}
let myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(let value of myCollection) {
console.log(value);
}
複製代碼
迭代器是一個對象,它符合如下規範
對象上可訪問 next 函數
next 函數 返回 {value,done},value爲本輪迭代的值,done爲布爾值,表示迭代是否結束
迭代器對象能夠經過重複調用next()顯式地迭代。 迭代一個迭代器被稱爲消耗了這個迭代器,由於它一般只能執行一次。 在產生終止值以後,對next()的額外調用應該繼續返回{done:true}。
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false } 最後一個值done爲false,下一輪再next done爲true
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
複製代碼
ES6 規定,默認的 Iterator 接口部署在數據結構的Symbol.iterator
屬性,或者說,一個數據結構只要具備Symbol.iterator
屬性,就能夠認爲是「可遍歷的」。
它是一個返回迭代器的函數。
咱們能夠經過 Symbol.interator 訪問
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
複製代碼
原生具有 Iterator 接口的數據結構以下。
- Array
- Map
- Set
- String
- TypedArray
- 函數的 arguments 對象
- NodeList 對象
生成器由於出現不久後就被 async 函數取代了,我學習時已經遍及 async 語法了。
不過咱們仍是頗有必要了解它的基礎語法,由於它與迭代器有關,進而與 for of,解構語法等有關;
同時 async 是 generator 函數的語法糖,瞭解了 gennerator 的原理後 async 的原理也就很好理解了。
生成器生成什麼?生成迭代器
生成器函數(generator)function * name{}
是一個返回迭代器的函數,這個迭代器能夠自動維護本身的狀態。
在function
關鍵詞後添加 *
,聲明生成器函數,調用生成器函數後,返回一個迭代器;
yield
是迭代器調用next
後的執行暫停處,繼續調用next
執行到下一個next
;
yield
後表達式的結果做爲 next
的返回值;
next
傳入的參數做爲上一個 next
暫停處整個yield
表達式的結果
生成器函數最後 return 沒有 yield
的效果,可是它會被保留在後續第一次調用next
返回對象的 value
上
function* f() {
for (let i=0; i<3; i++){
if(yield i) yield 10 // 返回並記錄函數狀態
}
return 20
}
const iter = f()
iter.next() //第三條 {value:0,done:false}
iter.next() // {value:1,done:false}
iter.next(true) //第四條 {value:10,done:false} 上一個 yield 是 if(yield i) 傳入true,if成功,執行到 yield 10
iter.next(true) // {value:2,done:false} 上一個 yield 是 yield 10 傳入true 無影響
iter.next() //第五條 {value:20,done:true} 返回值 20 被保存了
iter.next() //{value:undefined,done:true}
複製代碼
咱們能夠經過生成器很簡便地寫 iterator
接口
class O {
constructor(p = []) {
p.forEach(([key, value]) => (this[key] = value))
}
*[Symbol.iterator]() {
const keys = Object.keys(this)
for (let i = 0; i < keys.length; i++) {
yield this[keys[i]]
}
}
}
const c = new O([
['a', 1],
['b', 2],
['c', 3]
])
for (let value of c) {
console.log(value)
}
// 1 2 3
複製代碼
將迭代委託給另外一個迭代器
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
複製代碼
關於拋出錯誤,以及原型上的方法請看 Generator 函數的語法
由 switch case 組成的狀態機模型中, 除此以外,利用閉包技巧,保存生成器函數上下文信息。
Regenerator 經過工具函數將生成器函數包裝,爲其添加如 next/return 等方法。同時也對返回的生成器對象進行包裝,使得對 next 等方法的調用,最終進入由 switch case 組成的狀態機模型中。除此以外,利用閉包技巧,保存生成器函數上下文信息。
【轉向 Javascript 系列】深刻理解 Generators
簡單實現
Async / Await / Generator 實現原理
// 生成器函數根據yield語句將代碼分割爲switch-case塊,後續經過切換_context.prev和_context.next來分別執行各個case
function gen$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'result1';
case 2:
_context.next = 4;
return 'result2';
case 4:
_context.next = 6;
return 'result3';
case 6:
case "end":
return _context.stop();
}
}
}
// 低配版context
var context = {
next:0,
prev: 0,
done: false,
stop: function stop () {
this.done = true
}
}
// 低配版invoke
let gen = function() {
return {
next: function() {
value = context.done ? undefined: gen$(context)
done = context.done
return {
value,
done
}
}
}
}
// 測試使用
var g = gen()
g.next() // {value: "result1", done: false}
g.next() // {value: "result2", done: false}
g.next() // {value: "result3", done: false}
g.next() // {value: undefined, done: true}
複製代碼
Async / Await / Generator 實現原理
咱們知道,async 函數是 generator 函數的語法糖,它們有三點不一樣
function run(gen) {
//把返回值包裝成promise
return new Promise((resolve, reject) => {
var g = gen()
function _next(val) {
//錯誤處理
try {
var res = g.next(val)
} catch(err) {
return reject(err);
}
if(res.done) {
// 將最後 return 值返回
return resolve(res.value);
}
//res.value包裝爲promise,以兼容yield後面跟基本類型的狀況
Promise.resolve(res.value).then(
val => {
// 遞歸執行_next 達到了自動執行next功能
_next(val);
},
err => {
//拋出錯誤
g.throw(err)
});
}
_next();
});
}
function* myGenerator() {
try {
console.log(yield Promise.resolve(1))
console.log(yield 2) //2
console.log(yield Promise.reject('error'))
} catch (error) {
console.log(error)
}
}
const result = run(myGenerator) //result是一個Promise
//輸出 1 2 error
複製代碼
xxx instanceof Array
xxx.construtor === Array
Array.isArray(xxx) === true
Object.prototype.toString.call(xxx) === 'object Array'
1. 建立一個新對象
2. this 指向這個新對象
3. 執行代碼,即對 this 賦值
4. 返回 this
instance instanceof constructor
判斷實例對象 instance
的 __proto__
與構造函數 constuctor
的 prototype
是否是引用的同一個原型對象
若不是,沿instance
的原型鏈繼續向上找
function myInstanceof(l, r) {
while (l) {
if (l.__proto__ == r.prototype) {
return true
}
l = l.__proto__
}
return false
}
複製代碼
Object.prototype.entries
:返回對象自身自身的全部可枚舉的屬性名和值對的數組。
Object.keys()
:返回對象自身的全部可枚舉的屬性的鍵名。
JSON.stringify()
:只串行化對象自身的可枚舉的屬性。
Object.assign()
: 忽略enumerable
爲false
的屬性,只拷貝對象自身的可枚舉的屬性。
特別注意
for in 會遍歷到原型上的屬性,須要配合 hasOwnProperty
for (let key in obj) {
if (obj.hasOwnProperty(key)){
// dosomething
}
}
複製代碼
將子類的原型賦值爲父類實例,缺點是子類不能改變傳入父類構造函數的參數,且當原型鏈中包含引用類型值的原型時,該引用類型值會被全部實例共享;
Child.prototype = new Parent('parent');
複製代碼
在子類構造函數內call父類函數並傳入this和參數,缺點是隻能繼承父類構造函數內賦予的屬性
function Child(){
Parent.call(this,'yellow'); // 這句代碼就是藉助構造函數實現部分繼承,綁定this並執行父構造函數
this.type = 'child';
}
複製代碼
在object()函數內部, 先建立一個臨時性的構造函數, 而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例.
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
friends : ["Van","Louis","Nick"]
};
var anotherPerson = object(person);
複製代碼
function createAnother(original){
var clone = object(original);//經過調用object函數建立一個新對象
clone.sayHi = function(){//以某種方式來加強這個對象(加強:爲其添加屬性或方法)
alert("hi");
};
return clone;//返回這個對象
}
複製代碼
function extend(subClass,superClass){
var prototype = object(superClass.prototype);//建立對象,這個對象的原型是父類的原型
prototype.constructor = subClass;//加強對象
subClass.prototype = prototype;//指定對象
}
function Father(name){
this.name = name;
}
Father.prototype.sayName = function(){
alert(this.name);
};
function Son(name,age){
Father.call(this,name);//繼承實例屬性,第一次調用Father()
this.age = age;
}
//Son.prototype = new Father();
//不建立新的父類實例而是用extend完成
extend(Son,Father);
複製代碼
子類實例繼承父類實例的屬性和方法
子類繼承時,在構造函數內必須調用super方法,執行了父類的構造函數(與 ES5 中構造函數繼承同理)
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 不調用就報錯
this.color = color;
}
}
複製代碼
原型鏈繼承
構造函數做爲對象, 構造函數的屬性, 即靜態方法繼承
// 上面兩種都是 extends 後 js 自動完成
class A {}
class B extends {}
A.prototype // {} 無屬性
A.prototype.__proto__ === B.prototype // true 至關於完成了組合寄生式的extends方法
B.__proto__ === A // true 這樣B能夠訪問到A上靜態方法
複製代碼
var 是函數做用域 ,let 是塊級做用域
var有變量提高,let無變量提高
準確得說,let和var的建立是都會提高,可是let在原聲明前是禁止訪問的,這也是形成暫時性死區的緣由
let 不能在同一個做用域聲明相同的變量 而 var沒有此限制
let 在全局環境聲明不會掛載到window上 而 var會
暫時性死區:指塊級做用域內,某個let聲明的變量,在聲明前的區域,沒法訪問做用域鏈上同名的變量。
JS中一個聲明且賦值語句,它有三種狀態:
1.建立
建立在函數開始執行前就會完成,此時它還沒法被訪問,可是它會攔截函數上下文的訪問並報錯
let
在函數執行前只完成了建立
2. 初始化
初始化後,變量能夠被訪問,此時它的值爲undefined;var
變量在函數執行前就完成建立和初始化了,
let
的初始化在原句處
3. 賦值
var
和 let
的賦值都在原句處
簡單來講,全部引用了自由變量且不被銷燬的函數就是閉包
自由變量就是既不是函數內參數又不是已聲明的變量
做用域鏈和閉包:代碼中出現相同的變量,JavaScript引擎如何選擇
自由變量本來歸屬調用棧中 本層或本層如下 的調用上下文,但它不會隨調用上下文而銷燬。
編譯器建立一個閉包的步驟以下:
1. 在每一個函數執行前,它會編譯並建立一個執行上下文
2. 若是發現內部有函數定義,會快速掃描這個函數,若是函數使用了自由變量,則斷定這個函數是一個閉包,並建立自由變量所屬的執行上下文的閉包對象,這是個內部對象,存儲在堆空間。
3. 將自由變量掛載到閉包對象,若是後面有使用這個執行上下文的其餘自由變量,也一樣被掛載到這個執行上下文的閉包對象上;一個執行上下文對應一個閉包對象
4. 沒有被內部的函數使用的變量依舊在棧上
不被銷燬的閉包,與它相關的閉包對象都不會被銷燬。
若是閉包不被執行,那麼這個對象會一直佔用內存。
函數中建立變量也是使用變量,閉包使用閉包變量上的變量也是使用變量,二者有着一樣用途且都佔用內存,爲什麼說後者是內存泄漏?
首先,函數執行時建立的變量是執行時才建立,隨調用上下文銷燬而銷燬;而與閉包相關的閉包對象與閉包共生,不管閉包是否執行都佔用着一塊內存。
第二,閉包在執行時,閉包變量會被使用,此時不是內存泄漏;閉包不被執行時,閉包變量沒法被外界訪問且一直佔用內存,那麼就是內存泄漏。
以一個變量形式調用時,this 指向window,而不是調用這個函數的上下文的 this
關於 reference
如何影響 this
,請看 JavaScript深刻之從ECMAScript規範解讀this
若是不將任務進行劃分,按照隊列方式執行,當大量任務執行時,某些任務的回調遲遲得不到執行(都在隊尾),就會形成應用效果上的卡頓。因此設計者將任務分爲宏任務和微任務,微任務能夠穿插在宏任務中執行。
宏任務:
script執行
事件回調
setTimeout/setInterval
requestAnimationFrame
微任務:
執行一個宏任務,而後執行該宏任務中產生的微任務,若是微任務中產生了微任務,那麼這個微任務也會被執行,直到微任務隊列被清空,以後開啓下一輪循環
關於事件循環,更詳細請看
async function foo() {
console.log('foo')
}
async function bar() {
console.log('bar start')
await foo()
console.log('bar end')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
console.log('promise executor')
resolve();
}).then(function () {
console.log('promise then')
})
console.log('script end')
複製代碼
1. 首先在主協程中初始化異步函數foo和bar,碰到console.log打印script start;
2. 解析到setTimeout,初始化一個Timer,建立一個新的task
3. 執行bar函數,將控制權交給協程,輸出bar start,碰到await,執行foo,輸出foo,建立一個 Promise返回給主協程
4. 將返回的promise添加到微任務隊列,向下執行 new Promise,輸出 promise executor,返回resolve 添加到微任務隊列
5. 輸出script end
6. 當前task結束以前檢查微任務隊列,執行第一個微任務,將控制器交給協程輸出bar end
7. 執行第二個微任務 輸出 promise then
8. 當前任務執行完畢進入下一個任務,輸出setTimeout
特別注意 await 非 Promise 的值時,它會隱式建立 Promise 實例並 resolve 這個值
Promise.all(iterable)
這個方法返回一個新的promise對象,該promise對象在iterable參數對象裏全部的promise對象都成功的時候纔會觸發成功,一旦有任何一個iterable裏面的promise對象失敗則當即觸發該promise對象的失敗。這個新的promise對象在觸發成功狀態之後,會把一個包含iterable裏全部promise返回值的數組做爲成功回調的返回值,順序跟iterable的順序保持一致;若是這個新的promise對象觸發了失敗狀態,它會把iterable裏第一個觸發失敗的promise對象的錯誤信息做爲它的失敗錯誤信息。Promise.all方法常被用於處理多個promise對象的狀態集合。
Promise.race(iterable)
首先 race 的返回值是一個 promise;當 iterable 參數裏的任意一個 promise 成功或失敗後,race 將這個 promise 的成功返回值或失敗詳情做爲參數傳入 race 返回的 promise 的 resolve 或 reject 中。
因此數組內的Promise實例,誰執行的快,就繼承誰的執行結果和執行狀態,無論是成功仍是失敗
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(獲取全部的promise,都執行then,把結果放到數組,一塊兒返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
複製代碼
// 來源
class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn){
return this.then(null,fn);
}
}
function resolvePromise(promise2, x, resolve, reject){
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
複製代碼
Vue源碼詳解之nextTick:MutationObserver只是浮雲,microtask纔是核心!
數據被修改觸發setter函數,修改被watcher收集了,watcher將本身放入待更新的數組,在這次宏任務中的調用了this.$nextTick
中的回調函數也被收集入回調的數組中;宏任務結束後,nextTick回調數組在微任務執行,nextTick回調數組中的第一個執行的函數就是Watcher數組先去通知更新vm實例更新,以後就按順序執行被收集的nextTick
回調。
調用微任務形式是:Promise.resolve 或 MutationObersever
class EventEmitter {
constructor() {
this.events = {}
}
on(eventName, callback = () => {}, once = false) {
// const name = !!once ? 'onceEvents' : 'events'
if (typeof callback !== 'function')
throw new Error('callback must be a function')
if (typeof eventName !== 'string')
throw new Error('eventName must be a string')
if (this.events[eventName] === undefined) this.events[eventName] = []
this.events[eventName].push({
callback,
once
})
}
once(eventName, callback) {
this.on(eventName, callback, true)
}
off(eventName, callback) {
if (typeof eventName !== 'string')
throw new Error('eventName must be a string')
const nameOfevent = this.events[eventName]
if (Array.isArray(nameOfevent)) {
this.events[eventName] =
typeof callback !== 'function'
? []
: nameOfevent.filter(e => e.callback !== callback)
}
}
emit(eventName, ...args) {
if (typeof eventName !== 'string')
throw new Error('eventName must be a string')
const nameOfevent = this.events[eventName]
if (Array.isArray(nameOfevent)) {
for (const e of nameOfevent) {
e.callback.apply(this, args)
}
this.events[eventName] = nameOfevent.filter(e => !e.once)
}
}
}
複製代碼
節流函數的原理:
複雜版, JavaScript專題之跟着 underscore 學節流
//節流函數
// 簡潔版
function throttle(fn, { interval = 500 } = {}) {
let lock = false
return function (...args) {
if (lock) return false
lock = true
setTimeout(() => {
lock = false
}, interval)
return fn.apply(this, args)
}
}
export function throttle(fn, { interval = 500 } = {}) {
if (typeof fn != "function") return new Error("類型錯誤");
const _self = fn;
let timer,
firstTime = true; // 是否第一次調用
return function(...args) {
const _me = this;
if (firstTime) {
fn.apply(_me, args);
return (firstTime = false);
}
if (timer) {
return false;
}
timer = setTimeout(() => {
clearTimeout(timer);
timer = null;
_self.apply(_me, args);
}, interval);
};
}
複製代碼
防抖函數計時器版原理:
記錄前一次運行的時間
複雜版,JavaScript專題之跟着underscore學防抖
// 開始執行方案 只執行第一次
function debounce(fn, { immediate = 500 } = {}) {
let timestamp = 0
return function (...args) {
const pre = timestamp
timestamp = Date.now()
// if (!pre) return
if (timestamp - pre >= immediate) return fn.apply(this, args)
}
}
// 延遲執行方案 只執行最後一次 且最後一次也延遲
function debounce(fn, delay) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// [JavaScript專題之跟着underscore學防抖] https://github.com/mqyqingfeng/Blog/issues/22
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 若是已經執行過,再也不執行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
複製代碼
先實現apply再實現bind
apply
function myApply(ctx,args = []){
if(typeof this !== 'function') {
throw new TypeError('not a function!')
}
const symbol = Symbol(0)
ctx[symbol] = this
const result = ctx[symbol](...args)
delete ctx[symbol]
return result
}
Fuction.prototype.apply = myApply
複製代碼
bind
function myBind(ctx,...preArgs){
const fn = this
return (...args)=> fn.apply(ctx,[...preArgs,...args])
}
Function.prototype.bind = myBind
複製代碼
curry化的做用:固定函數參數,減小參數的輸入,參數的私有化;提升函數參數適用性,減小通用性;
function curry(fn, ...args) {
// 繼續接受參數而後柯里化
return args.length < fn.length ? (...params) => {
return curry(fn, ...args, ...params)
} : fn(...args)
}
複製代碼
// 簡單版
function deepCopy(obj) {
if (typeof obj == "object") {
const result = obj.constructor === Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) result[key] = deepCopy(obj[key])
}
return result
} else return obj;
}
// 循環引用如何解決 ?
function deepCopy(obj) {
// 使用 map 標記對象避免無限循環
const map = new Map()
function traverse(obj) {
if (typeof obj == 'object' && !map.get(obj)) {
map.set(obj, true)
const result = obj.constructor === Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) result[key] = traverse(obj[key])
}
return result
} else return obj
}
return traverse(obj)
}
複製代碼
思路與深拷貝如出一轍,都是遞歸遍歷
result[key] = deepCopy(obj[key])
替換成了 el.appendChild(createElement(child))
// 假設虛擬dom的結構
// {
// tag:'div', // 元素標籤
// attrs:{ // 屬性
// class:'a',
// id:'b'
// },
// text:'我是內容', // 文本內容
// children:[] // 子元素
// }
function createElement(virtualDom) {
const { tag, attrs, text, children } = virtualDom
const el = document.createElement(tag)
Object.keys(attrs).forEach(key => el.setAttribute(key, attrs[key]))
if (text !== null || text !== undefined) el.innerText = text
for (let child of children) {
el.appendChild(createElement(child))
}
return el
}
複製代碼
// 用於觸發微任務的觸發器類 正常使用setTimeout便可
class MicTaskTrigger {
constructor(callback = () => {}) {
this.counter = 1
this.node = document.createTextNode(String(this.counter))
this.callback = callback
this.observer = new MutationObserver(() => {
this.callback()
})
this.observer.observe(this.node, {
characterData: true
})
}
changeCallback(callback) {
this.callback = callback
}
trigger(callback = () => {}) {
this.callback = callback
this.counter = (this.counter + 1) % 2
this.node.data = String(this.counter)
}
}
const mic = new MicTaskTrigger() // mic 是用於觸發微任務的觸發器 正常使用setTimeout便可
class MyPromise {
constructor(fn) {
// 三個狀態
this.state = 'pending' // fulfilled rejected
this.value = undefined
this.reason = undefined
this.ResolvedCallbacks = []
this.RejectedCallbacks = []
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
mic.trigger(() =>
this.ResolvedCallbacks.forEach(callback =>
callback.call(this, this.value)
)
)
}
}
let reject = value => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = value
if (this.RejectedCallbacks.length)
mic.trigger(() => {
this.RejectedCallbacks.forEach(callback =>
callback.call(this, this.reason)
)
})
else throw this.reason
}
}
// 自動執行函數
try {
fn.call(this, resolve, reject)
} catch (e) {
reject(e)
}
}
// then
then(onFulfilled, onRejected) {
if (typeof onFulfilled === 'function') {
// 若是狀態已經肯定了,調用then時直接執行回調
if (this.state === 'fulfilled')
return mic.trigger(() => onFulfilled.call(this))
this.ResolvedCallbacks.push(onFulfilled)
}
if (typeof onRejected === 'function') {
if (this.state === 'rejected')
return mic.trigger(() => onRejected.call(this))
this.RejectedCallbacks.push(onRejected)
}
}
catch(onRejected) {
this.then(null, onRejected)
}
}
const promise = new MyPromise((resolve, reject) => {
console.log(111)
resolve({ data: 100 })
})
promise.then(res => console.log(++res.data))
promise.then(res => console.log(++res.data))
複製代碼
完成了 異步執行回調,then 和 catch 單次調用傳入回調,狀態固定後調用 then 直接回調;
沒有鏈式調用,沒有處理返回值是 Promise 的狀況,沒有錯誤冒泡 和 then 內的錯誤處理
狀態
構造函數內
定義 resolved 和 rejected,做用是在 pending 狀態下,改變狀態並將回調函數放入事件隊列中
在 try-catch 中執行用戶傳入的函數,並傳入以上兩個函數,交予用戶改變狀態權力
catch 中調用 reject
then 函數中收集傳入的成功和失敗回調;若是是狀態已定,直接將傳入的回調函數放到隊列中,無需收集
catch 執行 this.then(null, onRejected)
// 用於觸發微任務的觸發器類 正常使用setTimeout便可
class MicTaskTrigger {
constructor(callback = () => {}) {
this.counter = 1
this.node = document.createTextNode(String(this.counter))
this.callback = callback
this.observer = new MutationObserver(() => {
this.callback()
})
this.observer.observe(this.node, {
characterData: true
})
}
changeCallback(callback) {
this.callback = callback
}
trigger(callback = () => {}) {
this.callback = callback
this.counter = (this.counter + 1) % 2
this.node.data = String(this.counter)
}
}
const mic = new MicTaskTrigger() // mic 是用於觸發微任務的觸發器 正常使用setTimeout便可
class MyPromise {
constructor(fn) {
// 三個狀態
this.state = 'pending' // fulfilled rejected
this.value = undefined
this.reason = undefined
this.ResolvedCallbacks = []
this.RejectedCallbacks = []
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
mic.trigger(() =>
this.ResolvedCallbacks.forEach(callback =>
callback.call(this, this.value)
)
)
}
}
let reject = value => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = value
if (this.RejectedCallbacks.length)
mic.trigger(() => {
this.RejectedCallbacks.forEach(callback =>
callback.call(this, this.reason)
)
})
else throw this.reason
}
}
// 自動執行函數
try {
fn.call(this, resolve, reject)
} catch (e) {
reject(e)
}
}
// then
then(onFulfilled, onRejected) {
if (typeof onFulfilled === 'function') {
// 若是狀態已經肯定了,調用then時直接執行回調
if (this.state === 'fulfilled')
return mic.trigger(() => onFulfilled.call(this, this.value))
this.ResolvedCallbacks.push(onFulfilled)
}
if (typeof onRejected === 'function') {
if (this.state === 'rejected')
return mic.trigger(() => onRejected.call(this, this.reason))
this.RejectedCallbacks.push(onRejected)
}
}
catch(onRejected) {
this.then(null, onRejected)
}
}
const promise1 = new MyPromise((resolve, reject) => {
console.log(111)
setTimeout(() => resolve({ data: 100 }), 5000)
})
promise1.then(res => console.log(++res.data))
promise1.then(res => console.log(++res.data))
複製代碼
let arr = [[1,2,2], [6,7,8, [11,12, [12,13,[14]]], 10]]
// 原生
arr = arr.flat(infinity)
// 遞歸法
function flatten(arr){
let res = []
arr.forEach(item => {
// 判斷item是否爲數組
if(Array.isArray(item)) res = res.concat(flatten(item))
else res.push(item)
})
return res
}
複製代碼
由於事件循環的機制,setInterval 可能會出現兩次或屢次任務執行間隔遠小於設置的間隔時間的狀況
好比,在設置 setInterval 執行後,執行一個密集計算的任務;第一個時間點,setInterval的一個回調推入宏任務隊列,此時密集計算任務仍未完成;到第二個時間點, setInterval的第二個回調推入宏任務隊列,此時宏任務隊列中,兩個任務是連着的,最終致使兩個任務連續執行而遠小於設置間隔的狀況。
setTimeout實現setInterval原理是setTimeout的回調內遞歸調用,能夠保證兩個任務的執行間隔至少大於設置的間隔。
詳細能夠看 《JavaScript高級程序設計》22.3 高級定時器
// 簡單實現
function mySetInterval(fn, millisec){
function interval(){
setTimeout(interval, millisec);
fn();
}
setTimeout(interval, millisec)
}
// 加上執行次數和取消定時器,類寫法
class MySetInterval {
constructor(fn, { interval, count = Infinity } = {}) {
this.fn = fn
this.interval = interval
this.count = count
this._count = 0 // 使用計數
this._isOn = false
this._timer = null
}
_interval() {
this._timer = setTimeout(() => this._interval(), this.interval)
this.fn()
if (++this._count === this.count) this.off()
}
on() {
if (this._isOn) return
this._isOn = true
this._timer = setTimeout(() => this._interval(), this.interval)
}
off() {
if (!this._isOn) return
this._isOn = false
this._count = 0
clearTimeout(this._timer)
this._timer = null
}
}
const itt = new MySetInterval(()=>console.log(111),{interval:1000,count:5 })
itt.on() // 111 * 5
itt.on() // 111 * 3
itt.off() // 中止後續
複製代碼
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 數據劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('獲取數據了')
},
set(newVal) {
console.log('數據更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 輸入監聽
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
複製代碼
看到題目不要慌了,考察的仍是上面的響應式
<button onClick="model.isShow = true">顯示</button>
<button onClick="model.isShow = false">隱藏</button>
<div v-show="isShow">Hello World!</div>
<script> // 第 1 步: 定義數據和視圖 var model = { isShow: false } var view = document.querySelector('div') // 第 2 步: 定義視圖刷新方法 var updateView = function(value) { view.style.display = value ? '' : 'none' } // 第 3 步: 設置初始視圖表現 var directiveKey = view.getAttribute('v-show') updateView(model[directiveKey]) // 第 4 步: 監聽數據變化,而後刷新視圖,達到數據驅動的目的 Object.defineProperty(model, 'isShow', { set: function(val) { updateView(val) } }) </script>
複製代碼
選擇器 | 表達式或示例 | 說明 | 權重 |
---|---|---|---|
ID選擇器 | #aaa | 100 | |
類選擇器 | .aaa | 10 | |
標籤選擇器 | h1 | 元素的tagName | 1 |
屬性選擇器 | [title] | 詳見這裏 | 10 |
相鄰選擇器 | selecter + selecter | 拆分爲兩個選擇器再計算 | |
兄長選擇器 | selecter ~ selecter | 拆分爲兩個選擇器再計算 | |
親子選擇器 | selecter > selecter | 拆分爲兩個選擇器再計算 | |
後代選擇器 | selecter selecter | 拆分爲兩個選擇器再計算 | |
通配符選擇器 | * | 0 | |
各類僞類選擇器 | 如:link, :visited, :hover, :active, :target, :root, :not等 | 10 | |
各類僞元素 | 如::first-letter,::first-line,::after,::before,::selection | 1 |
offsetWidth = border + padding + width
當設置 box-sizing:border-box 時,offserWidth = width = border + padding + content,content是剩餘下來的空間
IE盒子模型默認 border-box
兩個垂直外邊距相遇時,他們將合爲一個外邊距
兄弟節點 margin-top 和 margin-bottom 會疊加
父子節點 margin-top 疊加 或者 margin-bottom 疊加
一個元素沒有內容,內邊距和邊框,它的margin-top 和 margin-bottom 會疊加
margin-top:20px
margin-bottom:20px
疊加後 20px
複製代碼
上一種狀況下,疊加後的垂直邊距與其餘元素的邊距相遇後也一樣會發生疊加
如:多個空內容的p標籤發生疊加的狀況
<p><p>
<p>1<p>
<p><p>
這幾個段落最終效果是隻顯示 <p>1<p>
由於其餘p標籤無內容,疊加後就消失了
複製代碼
接第二種狀況,父子節點垂直邊距疊加完後,仍會與父節點的兄弟節點疊加
解決:使用 BFC 包裹兄弟節點中的一個能夠消除疊加的狀況
BFC(block format content)塊級格式化上下文,盒模型佈局的CSS渲染模式,指一個獨立的渲染區域或者說是一個隔離的獨立容器。
BFC生成了新的渲染層,它能夠解決同級邊距摺疊的問題,由於他們根本再也不一個層面上。
特色:
屬於同一個BFC的兩個相鄰容器的上下margin會重疊
元素的margin-left與其包含塊的border-left相接觸
bfc區域不會與float元素重疊
計算bfc高度時,float元素也會被計入其中
bfc區域內的子元素不會影響外部元素
relative依據自身
absolute依據最近的已定位(postion:relative,absolute,fixed)的祖先元素
flex-start
:交叉軸的起點對齊。flex-end
:交叉軸的終點對齊。center
:交叉軸的中點對齊。baseline
: 項目的第一行文字的基線對齊。stretch
(默認值):若是項目未設置高度或設爲auto,將佔滿整個容器的高度。css彈性盒子-------桃園三兄弟之:flex-grow、flex-shrink、flex-basis詳解
flex: 1
分配父盒子的主軸大小,它實際上是三種屬性的簡寫
如下默認 flex-direaction : row,主軸上的大小爲 寬度
語義是盒子基礎的寬度,
肯定一個子盒子的寬度,優先級級比 width
高,好比 flex-basis:200px;width:100px
,優先 flex-basis 的 200px 生效
語義是盒子如何 增大
當父元素的寬度大於子元素寬度之和時,子元素如何分配父元素的剩餘寬度,也就是 會比 basis(基礎) 的大小grow up(增大)
flex:1 能均分父盒子就是 flex-grow 在起做用
公式:
剩餘寬度 = 父級的寬度 - 各個子元素的 flex—basis之和
自身在flex - grow 的佔比 = 自身的 flex-grow /各個子元素 flex-grow 之和
寬度 = flex-basis + 剩餘寬度 * flex-grow佔比
語義是盒子如何 收縮
當父元素的寬度小於於子元素寬度之和時,子元素如何縮小超出父元素的多餘寬度,也就是 會比 basis(基礎) 的大小shrink(縮小)
多餘寬度 = 各個子元素的 flex—basis之和 - 父級的寬度
flex-shrink 加權 佔比 = 自身的 flex-shrink 的加權 / 各個子元素的 flex-shrink 的加權之和
權重就是 flex-basis,flex-shrink加權 = flex-shrink * flex-basis
公式:寬度 = flex-basis - 多餘寬度 * flex-shrink加權佔比
position + margin(適用於有對應的寬高)
.h{
position:absolute;
left:50%;
margin-left:-25px; /* 盒子寬度的一半 */
}
.v{
position:absolute;
top:50%;
margin-top:-25px; /* 盒子高度寬度的一半 */
}
.vh{
position:absolute;
top:50%;
left:50%
margin-top:-25px;
margin-left:-25px;
}
複製代碼
position + tansform
.h{
position:absolute;
left:50%;
transform:translate(-50%,0);
}
.v{
position:absolute;
top:50%;
transform:translate(0,-50%);
}
.vh{
position:absolute;
top:50%;
left:50%
transform:translate(-50%,-50%);
}
複製代碼
flex
.f{
/* 父盒子 */
display:flex
}
.h{
justify-content:center
}
.v{
align-items:center
}
.vh{
justify-content:center;
align-items:center;
}
複製代碼
水平居中獨有的兩種
margin: 0 auto; 適用於寬度肯定的子盒子
轉換爲行內塊元素
.h{
display:inline-block;
text-align:center;
}
複製代碼
垂直居中獨有的
水平垂直居中獨有的
.vh{
/* 能夠保證瀏覽器兼容性 */
position:absolute;
left:0;
right:0;
top:0;
bottom:0;
margin:auto;
}
複製代碼
給後面元素加上
.clear{
clear:all
}
複製代碼
給父盒子加上
.clear:after{
clear:all
}
複製代碼
給父盒子加上overflow 觸發bfc(計算bfc高度時,float元素也會被計入其中)
.box{
overflow:hidden
}
複製代碼
直接寫大小如:18px或20px,直接繼承
直接寫比例如:1或者1.5,直接繼承
寫百分比時如200%,先換算成父元素line-height大小再繼承此大小而不是繼承百分比
.f{
font-size:20px;
line-height:200%;
}
.son{
font-size:16px;
}
/* 子元素的line-height是40px */
複製代碼
px:絕對像素
em:根據父元素的font-size肯定
rem:根據根元素html的font-size肯定
/* 根據屏幕寬度在media query 中設置 html的font-size */
@media only screen and (max-width: 320px){
html {
font-size: 5px !important;
}
}
@media only screen and (min-width: 320px){
html {
font-size: 5px !important;
}
}
@media only screen and (min-width: 384px){
html {
font-size: 6px !important;
}
}
@media only screen and (min-width: 480px){
html {
font-size: 7.5px !important;
}
}
/* 後續代碼rem爲單位時,1rem = 5px */
複製代碼
// 提早執行,初始化 resize 事件不會執行
setRem()
// 原始配置
function setRem () {
let doc = document.documentElement
let width = doc.getBoundingClientRect().width
let rem = width / 75
doc.style.fontSize = rem + 'px'
}
// 監聽窗口變化
addEventListener("resize", setRem)
複製代碼
首先理解 屏幕視口高度 [ window.screen.height ] 和 網頁視口高度 [ window.innerHeight ]
前者是整個手機屏幕的高度,後者是去除導航欄等高度以後用於顯示網頁內容的高度;
window.innerHeight = 100vh
小程序編譯後,rpx會作一次px換算。換算是以375個物理像素爲基準,也就是在一個寬度爲375物理像素的屏幕下,1rpx = 1px。
舉個例子:iPhone6屏幕寬度爲375px,共750個物理像素,那麼1rpx = 375 / 750 px = 0.5px。