一、面向?qū)ο?/h2>
1.1 普通函數(shù)與構(gòu)造函數(shù)
函數(shù)還是之前的函數(shù),唯一的區(qū)別就是首字母大寫
function Foo(m, n) {
let ret = m + n
this.m = m
this.n = n
return ret
}
// 01 普通函數(shù)調(diào)用
let ret = Foo(10, 20)
console.log(ret)
// 02 構(gòu)造函數(shù)執(zhí)行
let res = new Foo(20, 20)
console.log(res)
1.1.1 普通函數(shù)
- 正常調(diào)用,不需要 new 關(guān)鍵字
- 執(zhí)行過程遵循堆棧 + 作用域及作用域鏈查找的運(yùn)行機(jī)制
1.1.2 構(gòu)造函數(shù)
- 使用 new 關(guān)鍵字調(diào)用
- 與普通函數(shù)類似,同樣會(huì)創(chuàng)建私有上下文,然后進(jìn)棧執(zhí)行
- 執(zhí)行 new 操作時(shí),瀏覽器會(huì)創(chuàng)建一個(gè)空間表示空以象與 this 關(guān)聯(lián)
- 函數(shù)體內(nèi)如果沒有 return 或者說 return 的是基本數(shù)據(jù)類型,默認(rèn)返回對象實(shí)例
- 函數(shù)體內(nèi)如果返回引用類型,那么就以自己返回為主
- 函數(shù)此時(shí)叫做類, 返回的結(jié)果叫做對象實(shí)例
1.1.3 new 操作符
- 正常情況下使用 new 完成對象實(shí)例創(chuàng)建,如果當(dāng)前類不需要傳遞參數(shù),則可以不加括號運(yùn)行
- new Foo , 沒有加小括號的時(shí)候說明 Foo 不需要傳參,稱之為無參列表
- new Foo 與 new Foo() 的優(yōu)先級不同,前者為 19, 后者為 20
- 每一次 new 都會(huì)將函數(shù)重新執(zhí)行,生成一個(gè)新的執(zhí)行上下文,創(chuàng)建一個(gè)新的實(shí)例對象,因此兩個(gè)實(shí)例對象不一樣
1.2 原型及原型鏈
函數(shù)還是之前的函數(shù),唯一的區(qū)別就是首字母大寫
function Foo(m, n) {
let ret = m + n
this.m = m
this.n = n
return ret
}
// 01 普通函數(shù)調(diào)用
let ret = Foo(10, 20)
console.log(ret)
// 02 構(gòu)造函數(shù)執(zhí)行
let res = new Foo(20, 20)
console.log(res)
1.2.1 名詞說明
- prototype 屬性
- 每一個(gè)函數(shù)(除了箭頭函數(shù))數(shù)據(jù)類型, 都自帶一個(gè) prototype 屬性,指向原型對象(Function 除外)
- 每個(gè)原型對象自帶一個(gè) constructor 屬性,指向當(dāng)前構(gòu)造函數(shù)本身
- 函數(shù)數(shù)據(jù)類型
- 普通函數(shù)、箭頭函數(shù)、生成器函數(shù)
- 構(gòu)造函數(shù)(自定義類)
- 內(nèi)置函數(shù)(內(nèi)置構(gòu)造函數(shù))
-
proto 屬性
- 每一個(gè)對象數(shù)據(jù)類型,都自帶一個(gè) proto 屬性(隱式原型)
- 該屬性的值指向所屬類的原型對象 prototype
- 對象數(shù)據(jù)類型
- 普通對象,數(shù)組對象,正則對象,日期對象
- prototype 原型對象
- 實(shí)例對象
- 函數(shù)本身也是對象
- Object 類
- 所有對象都是 Object 內(nèi)置類的實(shí)例
- Object 也是一個(gè)函數(shù), 同樣具有 prototype 屬性,指向自己的原型對象
- 它的原型也是一個(gè)對象, 因此具有 proto 屬性
- Object 原型對象的 proto 指向 Null (內(nèi)部設(shè)計(jì))
1.2.2 原型鏈查找機(jī)制
- 首先查找自己的私有屬性,私有中如果存在則使用私有
- 私有中如果不存在,則默認(rèn)基于 proto 找所屬類的原型對象
- 如果原型對象上沒有,則基于原型對象的 proto 繼續(xù)向上查找,直到找到 Object.prototype 為止
1.2.3 示例代碼
function Foo() {
this.m = 10
this.n = 24
this.getM = function () {
console.log(this.m)
}
}
Foo.prototype.getM = function () {
console.log(this.m)
}
Foo.prototype.getN = function () {
console.log(this.n)
}
let foo1 = new Foo
let foo2 = new Foo
console.log(foo1.getM === foo2.getM)
console.log(foo1.getN === foo2.getN)
console.log(foo1.__proto__.getN === Foo.prototype.getN)
console.log(foo1.__proto__.getM === foo2.getM)
console.log(foo1.getM === Foo.prototype.getM)
console.log(foo1.constructor)
console.log(Foo.prototype.__proto__.constructor)
foo1.getM()
foo1.__proto__.getM()
foo2.getN()
Foo.prototype.getN()
1.3 重寫 new 方法
1.3.1 new 做了什么
- 創(chuàng)建實(shí)例對象
- 執(zhí)行構(gòu)造函數(shù), 將 this 指向?qū)嵗龑ο?/li>
- 處理返回值
1.3.2 模擬 new 實(shí)現(xiàn)
function Person(name) {
this.name = name
}
Person.prototype.slogan = function () {
console.log('前端開發(fā)')
}
Person.prototype.sayName = function () {
console.log(`我們的名稱是${this.name}`)
}
//* 原生的寫法
// let p1 = new Person('syy')
// p1.slogan()
// p1.sayName()
function _new(Ctor, ...params) {
//01 創(chuàng)建實(shí)例對象
// let obj = {}
// obj.__proto__ = Ctor.prototype
let obj = Object.create(Ctor.prototype)
// 02 調(diào)用構(gòu)造函數(shù), 改變this 指向
let ret = Ctor.call(obj, ...params)
// 03 處理返回結(jié)果
if (ret !== null && /^(object|function)$/.test(typeof ret)) return ret
return obj
}
let p2 = _new(Person, 'abc')
p2.slogan()
p2.sayName()
console.log(p2 instanceof Person)
1.4 Function 與 Object
1.4.1 函數(shù)多種角色
- 函數(shù)
- 普通函數(shù)調(diào)用(堆棧執(zhí)行及作用域)
- 構(gòu)造函數(shù)實(shí)例(原型及原型鏈)
- 對象
- 鍵值對
- 三種角色之間沒有必然的聯(lián)系,但是最核心的理念是函數(shù)就是函數(shù),函數(shù)是一等公民
1.4.2 經(jīng)典語錄
- Function 是一等公民,在 JS 中存在多種角色,普通函數(shù), 構(gòu)造函數(shù),對象
- 每一個(gè)對象都存在 --proto-- 屬性, 指向所屬類的原型對象(隱式原型,原型鏈屬性)
- 每一個(gè)函數(shù)都存在 prototype 屬性, 指向它的原型對象
- 所有函數(shù)都是 Function 內(nèi)置類的實(shí)例,且 Function 本身也是一個(gè)函數(shù)
- 所有對象都是 Object 的實(shí)例,且 Object 本身也是一個(gè)函數(shù)
- Function 與 Object 是二大并行的基類,雖然最終查找落腳點(diǎn)都是 Object 身上
- Function.prototype 原型對象是一個(gè)匿名函數(shù),但雖然它是一個(gè)函數(shù),它的處理機(jī)制和原型對象是一樣的,它的 --proto-- 屬性指向所屬類的原型對象,也就是 Object.prototype
1.4.3 無 prototype 屬性
- Function.prototype 原型是一個(gè)匿名函數(shù),但是它沒有 prototype 屬性
- 對象中使用 ES6 語法定義函數(shù)
const obj = {say(){}}, say方法不具備 - 箭頭函數(shù)
- 不具備 prototype 屬性的函數(shù)是不能執(zhí)行 new 操作的
1.5 This 規(guī)律
在瀏覽器平臺(tái)下運(yùn)行 JS , 非函數(shù)當(dāng)中的 this 一般都指向 window
因此這里討論的是函數(shù)執(zhí)行過程中的 this
需要注意:ES6+ 的箭頭函數(shù)中是沒有自己 this 的,處理機(jī)制就是使用上下文當(dāng)中的 this
1.5.1 this 是什么
- this 就是當(dāng)前函數(shù)執(zhí)行的主體(誰執(zhí)行了函數(shù)), 不等于執(zhí)行上下文或者當(dāng)前作用域
- syy 在拉勾教育講前端
- 講前端是一個(gè)動(dòng)作(函數(shù))
- 拉勾教育(執(zhí)行上下文)
- syy 主體,本次函數(shù)在當(dāng)前執(zhí)行上下文中執(zhí)行的 this 指向
1.5.2 常見 this 場景
- 事件綁定
- 普通函數(shù)
- 構(gòu)造函數(shù)
- 箭頭函數(shù)
- 基于 call bind apply 強(qiáng)制改變 this 指向
1.5.3 規(guī)律
- 事件綁定
- 不論是 DOM2 還是 DOM0 級事件綁定,事件觸發(fā)時(shí) this 一般都是被操作的元素
- 普通函數(shù)
- 函數(shù)執(zhí)行時(shí)查看前端是否有點(diǎn),如果有點(diǎn),則點(diǎn)前面的就是執(zhí)行主體, 沒有點(diǎn)就是 window , 嚴(yán)格模式下是 undefined
- 特殊情況
- 匿名函數(shù)中的 this 是 window 或者 undefined
- 回調(diào)函數(shù)中的 this 一般也是 window 或者 undefined
- 小括號語法
- 如果小括號只有一項(xiàng),則相當(dāng)于沒加
- 如果小括號當(dāng)中有多項(xiàng),則取出最后一項(xiàng),此時(shí)相當(dāng)于拷貝函數(shù),所以調(diào)用時(shí)主體是 window
1.5.4 練習(xí)
(function () {
console.log(this)
})()
let arr = [1, 3, 5, 7]
obj = {
name: '拉勾教育'
}
arr.map(function (item, index) {
console.log(this)
}, obj)
------------------------------------------------------
//? 普通函數(shù)調(diào)用
let obj = {
fn: function () {
console.log(this, 111)
}
}
let fn = obj.fn;
fn() // window
obj.fn(); // obj
(10, fn, obj.fn)();
------------------------------------------------------
var a = 3,
obj = { a: 5 }
obj.fn = (function () {
this.a *= ++a
return function (b) {
this.a *= (++a) + b
console.log(a)
}
})();
var fn = obj.fn
obj.fn(6)
fn(4)
console.log(obj.a, a)
二、異步編程與事件環(huán)
2.1 名詞說明
2.1.1 進(jìn)程與線程
- 進(jìn)程可以看做是一個(gè)應(yīng)用程序(例如打開瀏覽器或者瀏覽器打開一個(gè)界面)
- 線程是程序當(dāng)中具體做事情的人, 每個(gè)線程同一時(shí)刻只能做一件事情
- 一個(gè)進(jìn)程當(dāng)中可以包含多個(gè)線程
2.1.2 同步與異步
- 同步編程:一件一件的做事情,上一件沒有做完,下一件事情不會(huì)被處理(單線程)
- 異步編程:上一件事情沒有處理完,可以直接處理下一件事情(多線程)
- JS 基于單線程的 EventLoop 機(jī)制也可以實(shí)現(xiàn)異步編程
2.1.3 JS 中異步操作
- promise(then)
- aync await (generator)
- requestAnimationFrame
- 定時(shí)器操作
- ajax 網(wǎng)絡(luò)請求
- 事件綁定
2.1.4 JS 單線程
瀏覽器平臺(tái)下的 JS 代碼是由 JS 引擎執(zhí)行的, 所以它是單線程的
瀏覽器是多線程: GUI 渲染線程、JS引擎線程、事件觸發(fā)線程、定時(shí)器觸發(fā)線程、異步 http請求線程
- JS 中大部分代碼都是同步編程
- JS 可以基于單線程的 EventLoop (事件循環(huán)機(jī)制) 實(shí)現(xiàn)出異步效果
2.2 EventLoop 模型
2.2.1 代碼執(zhí)行順序
- 瀏覽器加載界面之后會(huì)開啟一個(gè)線程來執(zhí)行 JS,稱之叫 JS引擎(主線程)
- JS引擎會(huì)自上而下的執(zhí)行 JS 代碼,此過程會(huì)遇到(定時(shí)器,網(wǎng)絡(luò)請求,事件綁定,promise....)
- 遇到上述代碼執(zhí)行之后,瀏覽器會(huì)開啟一個(gè) Event Queue(任務(wù)|事件)隊(duì)列 優(yōu)先級隊(duì)列結(jié)構(gòu)
- 在隊(duì)列當(dāng)中存在二個(gè)任務(wù)隊(duì)列:微任務(wù) microtask | 宏任務(wù) macrotask
- 最終會(huì)將遇到的異步任務(wù)存放到 Event Queue 隊(duì)列當(dāng)中(未執(zhí)行)
- 主線程會(huì)繼續(xù)向下執(zhí)行同步代碼,直到所有同步執(zhí)行完就會(huì)處理異步任務(wù)
- 進(jìn)入 Event Queue 當(dāng)中查找異步任務(wù),找到之后放入主線程中執(zhí)行(此時(shí)主線程又被占用)
- 執(zhí)行完一個(gè)異步任務(wù)之后,主線程再次空閑,此時(shí)再進(jìn)入 Event Queue 查找余下的異步任務(wù)
2.2.2 異步任務(wù)執(zhí)行順序
- 先執(zhí)行微任務(wù)(只要有微任務(wù)就不會(huì)處理宏任務(wù))
- 微任務(wù)(一般是誰先放置誰先執(zhí)行)
- 宏任務(wù)(一般是誰先到達(dá)的誰先執(zhí)行)
2.2.3 整體順序
- 同步任務(wù)
- 異步微任務(wù)
- 異步宏任務(wù)
- 如果同步任務(wù)執(zhí)行過程中遇到可執(zhí)行的異步任務(wù),此時(shí)依然需要等到同步任務(wù)執(zhí)行完
- 如果同步任務(wù)執(zhí)行完,還沒有可執(zhí)行的異步任務(wù),此時(shí)也只能等待
- 不論何時(shí)放入的微任務(wù),只要異步微任務(wù)存在,就永遠(yuǎn)不會(huì)執(zhí)行異步宏任務(wù)
2.2.4 setTimeout
- 設(shè)置定時(shí)器這個(gè)操作是同步的
- 放置在 Event Queue 中的任務(wù)是異步宏任務(wù)
- 函數(shù)調(diào)用返回?cái)?shù)字,表示當(dāng)前是第幾個(gè)定時(shí)器
- 等待時(shí)間設(shè)置為 0 時(shí),也不是立即執(zhí)行,瀏覽器存在最快的反應(yīng)時(shí)間
- 定時(shí)器的等待時(shí)間到了之后,它的代碼可能還不會(huì)執(zhí)行(處于異步隊(duì)列中,同步任務(wù)還未完成執(zhí)行)
2.2.5 練習(xí)
setTimeout(() => {
console.log('1')
}, 30)
console.log(2)
setTimeout(() => {
console.log(3)
}, 20)
console.log(4)
console.time('AA')
// 消耗95ms
for (let i = 0; i < 88888888; i++) { }
console.timeEnd('AA')
console.log(5)
setTimeout(() => {
console.log(6)
}, 18)
console.log(7)
setTimeout(() => {
console.log(8)
}, 25)
console.log(9)
async function async1() {
console.log('async1 執(zhí)行了')
await async2()
console.log('async1 結(jié)束了')
}
async function async2() {
console.log('async2')
}
console.log('同步代碼執(zhí)行了')
setTimeout(() => {
console.log('setTimeout')
}, 0)
async1()
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('同步代碼結(jié)束了')
function fn1() {
console.log('fn1 start')
return new Promise((resolve) => {
resolve('zcegg1')
})
}
function fn2() {
console.log('fn2 start')
return new Promise((resolve) => {
setTimeout(() => {
resolve('zcegg2')
}, 10)
})
}
console.log('a')
setTimeout(async () => {
console.log('b')
await fn1()
console.log('c')
}, 20)
for (let i = 0; i < 88888888; i++) { } // 90ms
console.log('d')
fn1().then((result) => {
console.log('f')
})
fn2().then((result) => {
console.log('g')
})
setTimeout(() => {
console.log('h')
}, 0)
console.log('end')