一桐腌、寫在前面
趁著做畢設(shè)的空閑時(shí)間拄显,打算梳理一下 Javascript
中幾個(gè)比較重要的函數(shù),并手動(dòng)來實(shí)現(xiàn)一下案站」螅可以加深自己對(duì)這些函數(shù)的理解,從而更方便在平時(shí)項(xiàng)目中使用蟆盐,所謂知其然承边,亦知其所以然也。梳理了一下主要有以下幾個(gè)函數(shù)舱禽,往后會(huì)不斷更新炒刁。
- 節(jié)流,防抖函數(shù)
- call誊稚,apply 和 bind 方法
- new方法
二翔始、節(jié)流防抖(throttle & debounce)
1)、什么是節(jié)流里伯、防抖城瞎。
函數(shù)的節(jié)流和防抖是前端對(duì)于前端代碼性能優(yōu)化的一個(gè)重要的組成部分,節(jié)流疾瓮,顧名思義脖镀,就是當(dāng)我們執(zhí)行一個(gè)函數(shù)多次時(shí),我們只讓它在一定時(shí)間段執(zhí)行一次狼电,達(dá)到節(jié)制的目的蜒灰。防抖就是一段時(shí)間內(nèi)一個(gè)函數(shù)多次執(zhí)行時(shí)弦蹂,我們讓它只執(zhí)行一次。這里引出別人的概念强窖。
函數(shù)防抖(debounce):
在事件被觸發(fā)n秒后再執(zhí)行回調(diào)凸椿,如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)翅溺;典型的案例就是輸入搜索:輸入結(jié)束后n秒才進(jìn)行搜索請(qǐng)求脑漫,n秒內(nèi)又輸入的內(nèi)容,就重新計(jì)時(shí)咙崎。
函數(shù)節(jié)流(throttle):
規(guī)定在一個(gè)單位時(shí)間內(nèi)优幸,只能觸發(fā)一次函數(shù),如果這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù)褪猛,只有一次生效网杆; 典型的案例就是鼠標(biāo)不斷點(diǎn)擊觸發(fā),規(guī)定在n秒內(nèi)多次點(diǎn)擊只有一次生效握爷。
上面的概念其實(shí)也給了我們實(shí)現(xiàn)函數(shù)的思路跛璧,下面就來實(shí)現(xiàn)一下。
// 節(jié)流函數(shù)
function throttle(fn) {
// 先設(shè)置標(biāo)識(shí)
let flag = true
return function () {
// 觸發(fā)時(shí)間段還沒過去
if (!flag) return
// 改變狀態(tài)
flag = false
// 這里我沒有寫在setTimeout里新啼,因?yàn)槲蚁朦c(diǎn)擊立即觸發(fā)追城。
fn.apply(this, arguments)
setTimeout(() => {
// 一秒后變?yōu)閠rue標(biāo)識(shí)該函數(shù)可以再次觸發(fā)了
flag = true
}, 1000)
}
}
// 防抖函數(shù)
function debounce(fn) {
// 開始定時(shí)器為null
let time = null
return function () {
// 清除之前的 time,永遠(yuǎn)保留這段時(shí)間內(nèi)的最后一個(gè)執(zhí)行
clearTimeout(time)
time = setTimeout(() => {
fn.apply(this, arguments)
}, 500)
}
}
三燥撞、call, apply, bind 方法
首先座柱,這三者都是用來調(diào)用函數(shù)的,我們 Javascript
中的函數(shù)除了可以直接調(diào)用外物舒,還可以用這三個(gè)函數(shù)來手動(dòng)調(diào)用色洞。那么問題就來了,為什么要手動(dòng)去調(diào)用呢冠胯?
其實(shí)當(dāng)我們調(diào)用一個(gè)函數(shù)時(shí)候火诸,是在全局對(duì)象window
上調(diào)用的,這是時(shí)候 this
自然就指向 window
荠察,所以調(diào)用結(jié)果是一樣的置蜀。關(guān)于 this指向的問題
后面會(huì)出一篇文章詳細(xì)講講。
// 瀏覽器中(因?yàn)樵趎ode中this默認(rèn)指向一個(gè)空對(duì)象)
let name = 'rinvay'
function getName() {
console.log(this.name);
}
window.getName() // rinvay
this.getName() // rinvay
那想想當(dāng)我們需要在另一個(gè)對(duì)象而不是window
對(duì)象上調(diào)用這個(gè)函數(shù)悉盆,是不是就沒辦法了盯荤,這個(gè)時(shí)候就需要我們的這三個(gè)函數(shù)出場(chǎng)了。下面來介紹這三個(gè)函數(shù)的基本用法焕盟。首先介紹 aplly
和 call
的區(qū)別秋秤,這兩個(gè)函數(shù)都是立即調(diào)用函數(shù) 第一個(gè)參數(shù)就是函數(shù)this的指向,其余參數(shù)在函數(shù)執(zhí)行的時(shí)候傳給函數(shù)。兩個(gè)函數(shù)唯一的不同就是其余參數(shù)的傳遞形式灼卢,call個(gè)是一個(gè)個(gè)傳
绍哎,apply是放在一個(gè)數(shù)組中
let person = {
name: '樹街貓'
}
let name = 'rinvay'
function getInfo(age, height) {
console.log(this.name, age, height);
}
getInfo(21, 178) // rinvay 21 178
// call(this,arg1,arg2,...) 函數(shù)
getInfo.call(person, 21, 178) // 樹街貓 21 178
// apply(this,[arg1,arg2,...]) 函數(shù)
getInfo.apply(person, [21, 178]) // 樹街貓 21 178
bind
這個(gè)函數(shù)比較特殊,它不同于上面兩個(gè)芥玉。她改變了this指向
之后不會(huì)立即執(zhí)行該函數(shù)而是返回一個(gè)新的綁定函數(shù)
let person = {
name: '樹街貓'
}
let getInfoBind = getInfo.bind(person, 21, 178)
// 得到后執(zhí)行
getInfoBind() // 樹街貓 21 178
這樣三個(gè)函數(shù)的基本使用搞清楚了蛇摸,還有就是也可以不傳參,不改變this指向
灿巧,就和直接調(diào)用函數(shù)的效果一樣。
搞清楚了用法揽涮,我們趁熱打鐵來實(shí)現(xiàn)一下抠藕。先寫步驟,再寫代碼蒋困。
1)call 和 apply
- 將函數(shù)的
this指向
指向obj
- 傳入?yún)?shù)并執(zhí)行函數(shù)
實(shí)現(xiàn)代碼
// call 函數(shù)實(shí)現(xiàn)
Function.prototype._call = function (obj,...rest) {
if (typeof this !== 'function') return
// 這里this指向調(diào)用 _call 的函數(shù),也就是函數(shù)本身盾似。所以將函數(shù)賦值給對(duì)象的fn屬性。對(duì)象調(diào)用函數(shù)時(shí)候函數(shù)的this自然指向?qū)ο蟆? // 這里用了Symbol.for API 大概就是在沒有fn的時(shí)候運(yùn)行Symbol.for('fn')會(huì)創(chuàng)建一個(gè)fn放入注冊(cè)表,有了之后會(huì)返回這個(gè)值雪标。
obj[Symbol.for('fn')] = this;
// 取到fn這個(gè)值,對(duì)象調(diào)用函數(shù)零院。
obj[Symbol.for('fn')](...rest);
// 這里需要?jiǎng)h除這個(gè)屬性,否則越來越多村刨。
delete obj[Symbol.for('fn')]
}
// apply 函數(shù) 其實(shí)只是參數(shù)的處理變了而已,
Function.prototype._apply = function (obj) {
if (typeof this !== 'function') return
obj[Symbol.for('fn')] = this;
obj[Symbol.for('fn')](...arguments[1]);
delete obj[Symbol.for('fn')]
}
2)bind函數(shù)
下面來說說bind函數(shù)告抄,這個(gè)和上面的區(qū)別就是它在綁定后不執(zhí)行,返回一個(gè)綁定函數(shù)
- 將函數(shù)的
this指向
指向obj
- 傳入?yún)?shù)并返回函數(shù)
但是這里bind有點(diǎn)不一樣嵌牺,它支持函數(shù)柯里化打洼,就是如果有兩個(gè)參數(shù),可以先傳一個(gè)參數(shù)逆粹,執(zhí)行返回函數(shù)的時(shí)候再傳入這里需要除了剩余參數(shù) 募疮。
Function.prototype._bind = function (obj) {
if (typeof this !== 'function') {
return;
}
var _this = this;
//從第二個(gè)參數(shù)截取
var args = Array.prototype.slice.call(arguments, 1)
return function () {
// 剩余參數(shù)的處理合并再傳入
return _this.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
}
};
這里三個(gè)函數(shù)都實(shí)現(xiàn)完畢了,當(dāng)然可能有點(diǎn)小瑕疵僻弹,希望發(fā)現(xiàn)問題的評(píng)論區(qū)留言阿浓。
四、new方法
平時(shí)我們都是怎么用的 new
呢蹋绽,無非就是new 一個(gè)類嘛芭毙,我們 Javascript
叫做構(gòu)造函數(shù),其實(shí)就是普通函數(shù)蟋字。我們ES中的calss關(guān)鍵字
其實(shí)只是語法糖稿蹲,實(shí)質(zhì)還是一個(gè)函數(shù),可以打印類型看一看鹊奖。下面演示一下
function Person(name, age) {
this.name = name
this.age = age
}
let p1 = new Person('rinvay', 21)
console.log(p1.name); // rinvay
感覺它做的事情就是創(chuàng)建了一個(gè)新的對(duì)象把Person
里面的屬性給了這個(gè)對(duì)象苛聘,再返回。哈哈哈,這只是感覺设哗,其實(shí)過程是這樣的唱捣。
- 創(chuàng)建一個(gè)空對(duì)象,將它的引用賦給 this网梢,繼承函數(shù)的原型震缭。
- 通過 this 將屬性和方法添加至這個(gè)對(duì)象
- 最后返回 this 指向的新對(duì)象,也就是實(shí)例(如果沒有手動(dòng)返回其他的對(duì)象)
這里只描述了如何將構(gòu)造器中的屬性方法給實(shí)例战虏,那么原型上的實(shí)例呢拣宰?這里我們就用以構(gòu)造器原型去完成創(chuàng)建第一步那個(gè)空對(duì)象,下面代碼實(shí)現(xiàn)一下烦感。這里給出實(shí)現(xiàn)步驟
- 以構(gòu)造器的prototype屬性為原型巡社,創(chuàng)建新對(duì)象;
- 將this(也就是上一句中的新對(duì)象)和調(diào)用參數(shù)傳給構(gòu)造器手趣,執(zhí)行晌该;
- 如果構(gòu)造器沒有手動(dòng)返回對(duì)象,則返回第一步創(chuàng)建的新對(duì)象绿渣,如果有朝群,則舍棄掉第一步創(chuàng)建的新對(duì)象,返回手動(dòng)return的對(duì)象中符。
function _new(constructor, ...rest) {
let obj = Object.create(constructor.prototype)
let res = constructor.apply(obj, rest)
return typeof res === 'object' ? res : obj
}
好了這里常用的api函數(shù)
就差不多實(shí)現(xiàn)完成了姜胖,凌晨了也該睡覺了。
喜歡的話點(diǎn)個(gè)關(guān)注吧舟茶,你的點(diǎn)贊關(guān)注是我最大的動(dòng)力谭期。