2019中高級前端秘籍之CSS篇
2019中高級前端秘籍之JavaScript篇
2019中高級前端秘籍之瀏覽器篇
2019中高級前端秘籍之服務端與網(wǎng)絡篇
2019中高級前端秘籍之算法篇
1. 原型 / 構造函數(shù) / 實例
原型(prototype
): 一個簡單的對象税灌,用于實現(xiàn)對象的 屬性繼承奏纪÷仍幔可以簡單的理解成對象的爹肢扯。在 Firefox 和 Chrome 中必孤,每個JavaScrip
t對象中都包含一個__proto__
(非標準)的屬性指向它爹(該對象的原型),可obj.__proto__
進行訪問牢贸。
構造函數(shù): 可以通過new來 新建一個對象 的函數(shù)句惯。
實例: 通過構造函數(shù)和new創(chuàng)建出來的對象,便是實例输拇。 實例通過proto指向原型摘符,通過constructor指向構造函數(shù)。
說了一大堆策吠,大家可能有點懵逼逛裤,這里來舉個栗子,以Object為例猴抹,我們常用的Object便是一個構造函數(shù)带族,因此我們可以通過它構建實例。
// 實例
const instance = new Object()
則此時蟀给, 實例為instance, 構造函數(shù)為Object蝙砌,我們知道,構造函數(shù)擁有一個prototype的屬性指向原型跋理,因此原型為:
// 原型
const prototype = Object.prototype
這里我們可以來看出三者的關系:
實例.__proto__ === 原型
原型.constructor === 構造函數(shù)
構造函數(shù).prototype === 原型
// 這條線其實是是基于原型進行獲取的择克,可以理解成一條基于原型的映射線
// 例如:
// const o = new Object()
// o.constructor === Object --> true
// o.__proto__ = null;
// o.constructor === Object --> false
實例.constructor === 構造函數(shù)
放大來看,我畫了張圖供大家徹底理解:
2.原型鏈:
原型鏈是由原型對象組成前普,每個對象都有 proto 屬性肚邢,指向了創(chuàng)建該對象的構造函數(shù)的原型,proto 將對象連接起來組成了原型鏈汁政。是一個用來實現(xiàn)繼承和共享屬性的有限的對象鏈道偷。
屬性查找機制: 當查找對象的屬性時缀旁,如果實例對象自身不存在該屬性,則沿著原型鏈往上一級查找勺鸦,找到時則輸出并巍,不存在時,則繼續(xù)沿著原型鏈往上一級查找换途,直至最頂級的原型對象Object.prototype
如還是沒找到懊渡,則輸出undefined
;
屬性修改機制: 只會修改實例對象本身的屬性军拟,如果不存在剃执,則進行添加該屬性,如果需要修改原型的屬性時懈息,則可以用: b.prototype.x = 2
肾档;但是這樣會造成所有繼承于該對象的實例的屬性發(fā)生改變。
3. 執(zhí)行上下文(EC)
執(zhí)行上下文可以簡單理解為一個對象:
它包含三個部分:
????變量對象(VO)
????作用域鏈(詞法作用域)
????this
指向
它的類型:
????全局執(zhí)行上下文
????函數(shù)執(zhí)行上下文
????eval
執(zhí)行上下文
代碼執(zhí)行過程:
????創(chuàng)建 全局上下文 (global EC)
????全局執(zhí)行上下文 (caller) 逐行 自上而下 執(zhí)行辫继。遇到函數(shù)時怒见,函數(shù)執(zhí)行上下文 (callee) 被push
到執(zhí)行棧頂層
????函數(shù)執(zhí)行上下文被激活,成為 active EC, 開始執(zhí)行函數(shù)中的代碼姑宽,caller 被掛起
????函數(shù)執(zhí)行完后遣耍,callee 被pop
移除出執(zhí)行棧,控制權交還全局上下文 (caller)炮车,繼續(xù)執(zhí)行
4.變量對象
變量對象舵变,是執(zhí)行上下文中的一部分,可以抽象為一種 數(shù)據(jù)作用域瘦穆,其實也可以理解為就是一個簡單的對象纪隙,它存儲著該執(zhí)行上下文中的所有 變量和函數(shù)聲明(不包含函數(shù)表達式)。
活動對象 (AO): 當變量對象所處的上下文為 active EC 時扛或,稱為活動對象瘫拣。
5. 作用域
執(zhí)行上下文中還包含作用域鏈。理解作用域之前告喊,先介紹下作用域麸拄。作用域其實可理解為該上下文中聲明的 變量和聲明的作用范圍∏可分為 塊級作用域 和 函數(shù)作用域
特性:
????聲明提前: 一個聲明在函數(shù)體內(nèi)都是可見的, 函數(shù)優(yōu)先于變量
????非匿名自執(zhí)行函數(shù)拢切,函數(shù)變量為 只讀 狀態(tài),無法修改
let foo = function() { console.log(1) };
(function foo() {
foo = 10 // 由于foo在函數(shù)中只為可讀秆吵,因此賦值無效
console.log(foo)
}())
// 結果打踊匆: ? foo() { foo = 10 ; console.log(foo) }
6. 作用域鏈
我們知道,我們可以在執(zhí)行上下文中訪問到父級甚至全局的變量,這便是作用域鏈的功勞主穗。作用域鏈可以理解為一組對象列表泻拦,包含 父級和自身的變量對象,因此我們便能通過作用域鏈訪問到父級里聲明的變量或者函數(shù)忽媒。
由兩部分組成:
????[[scope]]
屬性: 指向父級變量對象和作用域鏈争拐,也就是包含了父級的[[scope]]
和AO
????AO: 自身活動對象
如此[[scopr]]
包含[[scope]]
,便自上而下形成一條 鏈式作用域晦雨。
7. 閉包
閉包屬于一種特殊的作用域架曹,稱為 靜態(tài)作用域。它的定義可以理解為: 父函數(shù)被銷毀 的情況下闹瞧,返回出的子函數(shù)的[[scope]]
中仍然保留著父級的單變量對象和作用域鏈绑雄,因此可以繼續(xù)訪問到父級的變量對象,這樣的函數(shù)稱為閉包奥邮。
閉包會產(chǎn)生一個很經(jīng)典的問題:
多個子函數(shù)的[[scope]]
都是同時指向父級万牺,是完全共享的。因此當父級的變量對象被修改時洽腺,所有子函數(shù)都受到影響杏愤。
解決:
變量可以通過 函數(shù)參數(shù)的形式 傳入,避免使用默認的[[scope]]
向上查找
使用setTimeout
包裹已脓,通過第三個參數(shù)傳入
使用 塊級作用域,讓變量成為自己上下文的屬性通殃,避免共享
8. script 引入方式
html 靜態(tài)<script>
引入
js 動態(tài)插入<script>
<script defer>
: 延遲加載度液,元素解析完成后執(zhí)行
<script async>
: 異步加載,但執(zhí)行時會阻塞元素渲染
9. 對象的拷貝
淺拷貝: 以賦值的形式拷貝引用對象画舌,仍指向同一個地址堕担,修改時原對象也會受到影響
????Object.assign
????展開運算符(...)
深拷貝: 完全拷貝一個新對象,修改時原對象不再受到任何影響
???? JSON.parse(JSON.stringify(obj))
: 性能最快
????具有循環(huán)引用的對象時曲聂,報錯
????當值為函數(shù)霹购、undefined
、或symbol
時朋腋,無法拷貝
????遞歸進行逐一賦值
10. new運算符的執(zhí)行過程
????新生成一個對象
????鏈接到原型: obj.__proto__ = Con.prototype
????綁定this: apply
????返回新對象(如果構造函數(shù)有自己 retrun 時齐疙,則返回該值)
11. instanceof原理
能在實例的 原型對象鏈 中找到該構造函數(shù)的prototype
屬性所指向的 原型對象,就返回true
旭咽。即:
// __proto__: 代表原型對象鏈
instance.[__proto__...] === instance.constructor.prototype
// return true
12. 代碼的復用
當你發(fā)現(xiàn)任何代碼開始寫第二遍時贞奋,就要開始考慮如何復用。一般有以下的方式:
????函數(shù)封裝
????繼承
????復制extend
????混入mixin
????借用apply/call
13. 繼承
在 JS 中穷绵,繼承通常指的便是 原型鏈繼承轿塔,也就是通過指定原型,并可以通過原型鏈繼承原型上的屬性或者方法。
????最優(yōu)化: 圣杯模式
var inherit = (function(c,p){
var F = function(){};
return function(c,p){
F.prototype = p.prototype;
c.prototype = new F();
c.uber = p.prototype;
c.prototype.constructor = c;
}
})();
使用 ES6 的語法糖 class / extends
14.類型轉(zhuǎn)換
大家都知道 JS 中在使用運算符號或者對比符時勾缭,會自帶隱式轉(zhuǎn)換揍障,規(guī)則如下:
-、*俩由、/毒嫡、% :一律轉(zhuǎn)換成數(shù)值后計算
+:
????數(shù)字 + 字符串 = 字符串, 運算順序是從左到右
????數(shù)字 + 對象采驻, 優(yōu)先調(diào)用對象的valueOf
-> toString
????數(shù)字 + boolean/null
-> 數(shù)字
????數(shù)字 + undefined
-> NaN
[1].toString() === '1'
{}.toString() === '[object object]'
NaN
!== NaN
审胚、+undefined
為NaN
15. 類型判斷
判斷 Target 的類型,單單用 typeof 并無法完全滿足礼旅,這其實并不是 bug膳叨,本質(zhì)原因是 JS 的萬物皆對象的理論。因此要真正完美判斷時痘系,我們需要區(qū)分對待:
????基本類型(null
): 使用 String(null)
????基本類型(string / number / boolean / undefined
) + function
: 直接使用 typeof
即可
????其余引用類型(Array / Date / RegExp Error
): 調(diào)用toString后根據(jù)[object XXX]
進行判斷
很穩(wěn)的判斷封裝:
let class2type = {}
'Array Date RegExp Object Error'.split(' ').forEach(e => class2type[ '[object ' + e + ']' ] = e.toLowerCase())
function type(obj) {
if (obj == null) return String(obj)
return typeof obj === 'object' ? class2type[ Object.prototype.toString.call(obj) ] || 'object' : typeof obj
}
16. 模塊化
模塊化開發(fā)在現(xiàn)代開發(fā)中已是必不可少的一部分菲嘴,它大大提高了項目的可維護、可拓展和可協(xié)作性汰翠。通常龄坪,我們 在瀏覽器中使用 ES6 的模塊化支持,在 Node 中使用 commonjs 的模塊化支持复唤。
分類:
????es6: import / export
????commonjs: require / module.exports / exports
????amd: require / defined
require與import的區(qū)別
????require
支持 動態(tài)導入健田,import
不支持,正在提案 (babel 下可支持)
????require
是 同步 導入佛纫,import
屬于 異步 導入
????require
是 值拷貝妓局,導出值變化不會影響導入值;import指向 內(nèi)存地址呈宇,導入值會隨導出值而變化
17. 防抖與節(jié)流
防抖與節(jié)流函數(shù)是一種最常用的 高頻觸發(fā)優(yōu)化方式好爬,能對性能有較大的幫助。
防抖 (debounce): 將多次高頻操作優(yōu)化為只在最后一次執(zhí)行甥啄,通常使用的場景是:用戶輸入存炮,只需再輸入完成后做一次輸入校驗即可。
function debounce(fn, wait, immediate) {
let timer = null
return function() {
let args = arguments
let context = this
if (immediate && !timer) {
fn.apply(context, args)
}
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
}
}
節(jié)流(throttle): 每隔一段時間后執(zhí)行一次蜈漓,也就是降低頻率穆桂,將高頻操作優(yōu)化成低頻操作,通常使用場景: 滾動條事件 或者 resize 事件融虽,通常每隔 100~500 ms執(zhí)行一次即可充尉。
function throttle(fn, wait, immediate) {
let timer = null
let callNow = immediate
return function() {
let context = this,
args = arguments
if (callNow) {
fn.apply(context, args)
callNow = false
}
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, args)
timer = null
}, wait)
}
}
}
18. 函數(shù)執(zhí)行改變this
由于 JS 的設計原理: 在函數(shù)中,可以引用運行環(huán)境中的變量衣形。因此就需要一個機制來讓我們可以在函數(shù)體內(nèi)部獲取當前的運行環(huán)境驼侠,這便是this姿鸿。
因此要明白 this 指向,其實就是要搞清楚 函數(shù)的運行環(huán)境倒源,說人話就是苛预,誰調(diào)用了函數(shù)。例如:
????obj.fn()
笋熬,便是 obj
調(diào)用了函數(shù)热某,既函數(shù)中的 this === obj
????fn()
,這里可以看成 window.fn()
胳螟,因此 this === window
但這種機制并不完全能滿足我們的業(yè)務需求昔馋,因此提供了三種方式可以手動修改 this
的指向:
????call: fn.call(target, 1, 2)
????apply: fn.apply(target, [1, 2])
????bind: fn.bind(target)(1,2)
19.ES6/ES7
由于 Babel 的強大和普及,現(xiàn)在 ES6/ES7 基本上已經(jīng)是現(xiàn)代化開發(fā)的必備了糖耸。通過新的語法糖秘遏,能讓代碼整體更為簡潔和易讀。
聲明
let / const
: 塊級作用域嘉竟、不存在變量提升邦危、暫時性死區(qū)、不允許重復聲明
const
: 聲明常量舍扰,無法修改
解構賦值
class / extend
: 類聲明與繼承
Set / Map
: 新的數(shù)據(jù)結構
異步解決方案
Promise
的使用與實現(xiàn)
generator
:
????yield
: 暫停代碼
????next()
: 繼續(xù)執(zhí)行代碼
function* helloWorld() {
yield 'hello';
yield 'world';
return 'ending';
}
const generator = helloWorld();
generator.next() // { value: 'hello', done: false }
generator.next() // { value: 'world', done: false }
generator.next() // { value: 'ending', done: true }
generator.next() // { value: undefined, done: true }
await / async
: 是generator
的語法糖倦蚪, babel
中是基于promise
實現(xiàn)。
async function getUserByAsync(){
let user = await fetchUser();
return user;
}
const user = await getUserByAsync()
console.log(user)
20. AST
抽象語法樹 (Abstract Syntax Tree)边苹,是將代碼逐字母解析成 樹狀對象 的形式陵且。這是語言之間的轉(zhuǎn)換、代碼語法檢查个束,代碼風格檢查慕购,代碼格式化,代碼高亮播急,代碼錯誤提示,代碼自動補全等等的基礎售睹。例如:
function square(n){
return n * n
}
通過解析轉(zhuǎn)化成的AST如下圖:
21. babel編譯原理
babylon
將 ES6/ES7
代碼解析成 AST
babel-traverse
對 AST
進行遍歷轉(zhuǎn)譯桩警,得到新的 AST
新 AST
通過 babel-generator
轉(zhuǎn)換成 ES5
22. 函數(shù)柯里化
在一個函數(shù)中,首先填充幾個參數(shù)昌妹,然后再返回一個新的函數(shù)的技術捶枢,稱為函數(shù)的柯里化。通撤裳拢可用于在不侵入函數(shù)的前提下烂叔,為函數(shù) 預置通用參數(shù),供多次重復調(diào)用固歪。
const add = function add(x) {
return function (y) {
return x + y
}
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21
23. 數(shù)組(array)
map
: 遍歷數(shù)組蒜鸡,返回回調(diào)返回值組成的新數(shù)組
forEach
: 無法brea
k胯努,可以用try/catch
中throw new Error
來停止
filter
: 過濾
some
: 有一項返回true,則整體為true
every
: 有一項返回false
逢防,則整體為false
join
: 通過指定連接符生成字符串
push / pop
: 末尾推入和彈出叶沛,改變原數(shù)組, 返回推入/彈出項
unshift / shift
: 頭部推入和彈出忘朝,改變原數(shù)組灰署,返回操作項
sort(fn) / reverse
: 排序與反轉(zhuǎn),改變原數(shù)組
concat
: 連接數(shù)組局嘁,不影響原數(shù)組溉箕, 淺拷貝
slice(start, end)
: 返回截斷后的新數(shù)組,不改變原數(shù)組
splice(start, number, value...)
: 返回刪除元素組成的數(shù)組悦昵,value 為插入項肴茄,改變原數(shù)組
indexOf / lastIndexOf(value, fromIndex)
: 查找數(shù)組項,返回對應的下標
reduce / reduceRight(fn(prev, cur)旱捧, defaultPrev)
: 兩兩執(zhí)行独郎,prev 為上次化簡函數(shù)的return
值,cur 為當前值(從第二項開始)
數(shù)組亂序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
return Math.random() - 0.5;
});
數(shù)組拆解: flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() {
return this.toString().split(',').map(item => +item )
}
作者:郭東東
鏈接:https://juejin.im/post/5c64d15d6fb9a049d37f9c20
來源:掘金
著作權歸作者所有枚赡。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權氓癌,非商業(yè)轉(zhuǎn)載請注明出處。