前言
近期整理了JavaScript知識體系,50個知識點由淺入深掌握J(rèn)s建議收藏,如有問題,歡迎指正。
1. 說說你對JS的理解
1995年,布萊登·艾奇(美國人)在網(wǎng)景公司,用10天寫的一門語言。
Js是一門:動態(tài)的,弱類型的,解釋型的,基于對象的腳本語言,同時Js又是單線程的。
- 動態(tài)類型語言:
代碼在執(zhí)行過程中,才知道這個變量屬于的類型。 - 弱類型:聲明變量一般用var,數(shù)據(jù)類型不固定,可以隨時改變。可以將字符串’12’和整數(shù)3進(jìn)行連接得到字符串’123’,在相加的時候會進(jìn)行強制類型轉(zhuǎn)換。
- 解釋型:一邊執(zhí)行,一邊編譯,不需要程序在運行之前需要整體先編譯。
- 基于對象:最終所有對象都指向
Object
。 - 腳本語言 :一般都是可以嵌在其它編程語言當(dāng)中執(zhí)行。
- 單線程:依次執(zhí)行,前面代碼執(zhí)行完后面才執(zhí)行。
組成部分:
ECMAscript | DOM | BOM |
---|---|---|
JavaScript的語法部分 | 文檔對象模型 | 瀏覽器對象模型 |
主要包含JavaScript語言語法 | 主要用來操作頁面元素和樣式 | 主要用來操作瀏覽器相關(guān)功能 |
2. JS數(shù)據(jù)類型有哪些?值是如何存儲的?
Js中一共有8種數(shù)據(jù)類型:7個基本數(shù)據(jù)類型和1個對象。
基本數(shù)據(jù)類型:
- Number
- String
- Boolean
- undefined
- null
- Symbol(ES6新增,表示獨一無二的值)
- BigInt(ES6新增,以n結(jié)尾,表示超長數(shù)據(jù))
對象:
- Object
- function
- Array
- Date
- RegExp
基本數(shù)據(jù)類型值是不可變的,多次賦值,只取最后一個。
var name = 'DarkHorse';
name.toUpperCase(); // 'DARKHOURSE'
console.log(name); // 'DarkHorse'
基本數(shù)據(jù)類型存儲在棧中,占據(jù)空間小、屬于被頻繁使用數(shù)據(jù)。
對象值是可變的,可以擁有屬性和方法,并且是可以動態(tài)改變的。
var a={name:'DarkHorse'};
a.name='xiaohong';
console.log(a.name) //xiaohong
引用數(shù)據(jù)類型存儲在堆中。引用數(shù)據(jù)類型占據(jù)空間大,如果存儲在棧中,將會影響程序運行的性能。
引用數(shù)據(jù)類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當(dāng)解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址后從堆中獲得實體。
3. Undefined 與 undeclared 的區(qū)別?
變量聲明未賦值,是 undefined
。
未聲明的變量,是 undeclared
。瀏覽器會報錯a is not defined
,ReferenceError。
4. Null和undefined的區(qū)別
null
和 undefined
都是基本數(shù)據(jù)類型,這兩個數(shù)據(jù)類型只有一個值,null
和 undefined
。
null
表示空的,什么都沒有,不存在的對象,他的數(shù)據(jù)類型是object
。
初始值賦值為null
,表示將要賦值為對象,
不再使用的值設(shè)為null
,瀏覽器會自動回收。
undefined
表示未定義,常見的為undefined
情況:
一是變量聲明未賦值,
二是數(shù)組聲明未賦值;
三是函數(shù)執(zhí)行但沒有明確的返回值;
四是獲取一個對象上不存在的屬性或方法。
5. JS數(shù)據(jù)類型轉(zhuǎn)換
JS的顯式數(shù)據(jù)類型轉(zhuǎn)換一共有三種:
(1)第一種是:轉(zhuǎn)字符串。有.toString()
方法和String()
函數(shù),Sting()
函數(shù)相比于toString()
函數(shù)適用范圍更廣,可以將null
和undefined
轉(zhuǎn)化為字符串,toString()
轉(zhuǎn)化會報錯。
(2)第二種是:轉(zhuǎn)數(shù)值。可以用Number()
函數(shù)轉(zhuǎn)數(shù)值,.parseInt
轉(zhuǎn)整數(shù),parseFloat
函數(shù)轉(zhuǎn)小數(shù)。
Number()
函數(shù)適用于所有類型的轉(zhuǎn)換,比較嚴(yán)格,字符串合法數(shù)字則轉(zhuǎn)化成數(shù)字,不合法則轉(zhuǎn)化為NAN
;空串轉(zhuǎn)化為0,null
和undefined
轉(zhuǎn)0和NAN
;ture
轉(zhuǎn)1,false
轉(zhuǎn)0。
parseInt()
是從左向右獲取一個字符串的合法整數(shù)位,parseFloat()
獲取字符串的所有合法小數(shù)位。
(3)第三種是:轉(zhuǎn)布爾。像false
、0、空串、null
、undefined
和NaN
這6種會轉(zhuǎn)化為false
。
常用的隱式類型轉(zhuǎn)換有:任意值+空串轉(zhuǎn)字符串、+a轉(zhuǎn)數(shù)值、a-0 轉(zhuǎn)數(shù)值等。
var a = 1 + 2 + '3';
// 123
// 任何值和字符串做加法運算,都會先轉(zhuǎn)換為字符串然后進(jìn)行拼串
var a = 10 - '5'
// 5
// 如果對非數(shù)字的值進(jìn)行算數(shù)運算,JS解析器會將值轉(zhuǎn)化為數(shù)值再運算
// 總結(jié):字符相連,數(shù)值相加(- * /)
6. 數(shù)據(jù)類型的判斷
(1)基本類型的判斷——typeof
typeof
的返回值有六種,返回值是字符串,不能判斷數(shù)組和null的數(shù)據(jù)類型,返回object
。
typeof ''; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 無效 這個是一個設(shè)計缺陷,造成的
typeof [] ; //object 無效
(2)引用數(shù)據(jù)類型判斷 —— instanceof
檢查對象原型鏈上有沒有該構(gòu)造函數(shù),可以精準(zhǔn)判斷引用數(shù)據(jù)類型,不能判斷基本數(shù)據(jù)類型。
[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
var arr = [1, 2, 3];
console.log(arr instanceof Array) // true
console.log(arr instanceof Object); // true
function fn(){}
console.log(fn instanceof Function)// true
console.log(fn instanceof Object)// true
(3)類似instanceof of —— constructor
每一個對象實例都可以通過 constrcutor
對象來訪問它的構(gòu)造函數(shù)。既可以檢測基本類型又可以檢測對象,但不能檢測null
和undefined
。
console.log((10).constructor===Number);//true
console.log([].constructor===Array);//true
var reg=/^$/;
console.log(reg.constructor===RegExp);//true
console.log(reg.constructor===Object);//false
需要注意的一點是函數(shù)的 constructor
是不穩(wěn)定,如果把函數(shù)的原型進(jìn)行重寫,這樣檢測出來的結(jié)果會不準(zhǔn)確。
function Fn(){}
Fn.prototype = new Array()
var f = new Fn
console.log(f.constructor)//Array
(4)最準(zhǔn)確方式 —— Object.prototype.toString.call()
獲取Object
原型上的toString
方法,讓方法執(zhí)行,讓toString
方法中的this
指向第一個參數(shù)的值,最準(zhǔn)確方式。
第一個object
:當(dāng)前實例是對象數(shù)據(jù)類型的(object),
第二個Object
:數(shù)據(jù)類型。
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
7. a++ 和 ++a 區(qū)別
無論是前++還是后++,都會使原來變量立刻自增1。
不同在于a++是原值,++a是新值。
var a = 4;
console.log(a++); //4 原值
var b = 4;
console.log(++b) //5 新值
8. 0.1+0.2 === 0.3嗎
在開發(fā)過程中遇到類似這樣的問題:
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2) // 0.30000000000000004
這里得到的不是想要的結(jié)果,要想等于0.3,就要把它進(jìn)行轉(zhuǎn)化:
(n1 + n2).toFixed(2) // 注意,toFixed為四舍五入
toFixed(num)
方法可把 Number 四舍五入為指定小數(shù)位數(shù)的數(shù)字。那為什么會出現(xiàn)這樣的結(jié)果呢?
toFixed(num)
方法可把 Number 四舍五入為指定小數(shù)位數(shù)的數(shù)字。那為什么會出現(xiàn)這樣的結(jié)果呢?
計算機是通過二進(jìn)制的方式存儲數(shù)據(jù)的,所以計算機計算0.1+0.2的時候,實際上是計算的兩個數(shù)的二進(jìn)制的和。0.1的二進(jìn)制是0.0001100110011001100...
(1100循環(huán)),0.2的二進(jìn)制是:0.00110011001100...
(1100循環(huán)),這兩個數(shù)的二進(jìn)制都是無限循環(huán)的數(shù)。那JavaScript是如何處理無限循環(huán)的二進(jìn)制小數(shù)呢?
一般我們認(rèn)為數(shù)字包括整數(shù)和小數(shù),但是在 JavaScript 中只有一種數(shù)字類型:Number,它的實現(xiàn)遵循IEEE 754標(biāo)準(zhǔn),使用64位固定長度來表示,也就是標(biāo)準(zhǔn)的double雙精度浮點數(shù)。在二進(jìn)制科學(xué)表示法中,雙精度浮點數(shù)的小數(shù)部分最多只能保留52位,再加上前面的1,其實就是保留53位有效數(shù)字,剩余的需要舍去,遵從“0舍1入”的原則。
根據(jù)這個原則,0.1和0.2的二進(jìn)制數(shù)相加,再轉(zhuǎn)化為十進(jìn)制數(shù)就是:0.30000000000000004
。
9. JS的作用域和作用域鏈
作用域就變量起作用的范圍和區(qū)域。 作用域的目的是隔離變量,保證不同作用域下同名變量不會沖突。
JS中,作用域分為三種,全局作用域、函數(shù)作用域和塊級作用域。 全局作用域在script
標(biāo)簽對中,無論在哪都能訪問到。在函數(shù)內(nèi)部定義的變量,擁有函數(shù)作用域。塊級作用域則是使用let
和const
聲明的變量,如果被一個大括號括住,那么這個大括號括住的變量區(qū)域就形成了一個塊級作用域。
作用域?qū)訉忧短?,形成的關(guān)系叫做作用域鏈,作用域鏈也就是查找變量的過程。 查找變量的過程:當(dāng)前作用域 --》上一級作用域 --》上一級作用域 … --》直到找到全局作用域 --》還沒有,則會報錯。
作用域鏈?zhǔn)怯脕肀WC——變量和函數(shù)在執(zhí)行環(huán)境中有序訪問。
10. LHS和RHS查詢
LHS和RHS查詢是JS引擎查找變量的兩種方式,這里的“Left”和“Right”,是相對于賦值操作來說,當(dāng)變量出現(xiàn)在賦值操作左側(cè)時,執(zhí)行LHS操作。
LHS 意味著 變量賦值或寫入內(nèi)存,,他強調(diào)是寫入這個動作。
var name = '小明'
當(dāng)變量出現(xiàn)在賦值操作右側(cè)或沒有賦值操作時,是RHS。
var Myname = name
console.log(name)
RHS意味著 變量查找或讀取內(nèi)存,它強調(diào)的是讀這個動作。
11. 詞法作用域和動態(tài)作用域
Js底層遵循的是詞法作用域,從語言的層面來說,作用域模型分兩種:
詞法作用域:也稱靜態(tài)作用域,是最為普遍的一種作用域模型
動態(tài)作用域:相對“冷門”,bash腳本、Perl等語言采納的是動態(tài)作用域
詞法作用域:在代碼書寫時完成劃分,作用域沿著它定義的位置往外延伸。
動態(tài)作用域:在代碼運行時完成劃分,作用域鏈沿著他的調(diào)用棧往外延伸。
12. 什么是匿名函數(shù),有什么作用
匿名函數(shù)也叫一次性函數(shù),沒用名字,且在定義時執(zhí)行,且執(zhí)行一次,不存在預(yù)解析(函數(shù)內(nèi)部執(zhí)行的時候會發(fā)生)。
匿名函數(shù)的基本形式為:
(function(){
...
}());
除此之外,還有常見的以下寫法
(function(){
console.log('我是一個匿名函數(shù)')
})();
var a = function(){
console.log('我是一個匿名函數(shù)')
}()
// 使用多種運算符開頭,一般是用!
!function(){
console.log('我是一個匿名函數(shù)')
}();
匿名函數(shù)的作用有:
(1)對項目的初始化,頁面加載時調(diào)用,保證頁面有效的寫入Js,不會造成全局變量污染
(2)防止外部命名空間污染
(3)隱藏內(nèi)部代碼暴露接口
13. 什么是回調(diào)函數(shù),常見的回調(diào)函數(shù)有哪些
回調(diào)函數(shù)是一段可執(zhí)行的代碼段,它作為一個參數(shù)傳遞給其他的代碼,其作用是在需要的時候方便調(diào)用這段(回調(diào)函數(shù))。
在Js中函數(shù)也是對象的一種,同樣對象可以作為參數(shù)傳遞給函數(shù),因此函數(shù)也可以作為參數(shù)傳遞給另一個函數(shù),這個做為參數(shù)的函數(shù)就是回調(diào)函數(shù)。
簡單來說:回調(diào)函數(shù)即別人調(diào)用了這個函數(shù),即函數(shù)作為參數(shù)傳入另一個函數(shù)。
一般來說回調(diào)函數(shù)滿足三個條件:自己定義的函數(shù)、自己沒有調(diào)用,函數(shù)最終執(zhí)行了。
在開發(fā)中經(jīng)??吹降幕卣{(diào)函數(shù)有:
// 點擊事件的回調(diào)函數(shù)
$('#btn').click(function(){
console.log('click btn');
})
// 異步請求的回調(diào)函數(shù)
$.get('ajax/test.html',function(data){
$('#box').html(data);
})
// 計時器
var timeId = setTimeout(function{
console.log('hello')
},1000)
14. 什么是構(gòu)造函數(shù),與普通函數(shù)的區(qū)別
在ES6之前,我們都是通過構(gòu)造函數(shù)創(chuàng)建類,從而生成對象實例。
構(gòu)造函數(shù)就是一個函數(shù),只不過通常我們把構(gòu)造函數(shù)的名字寫成大駝峰, 構(gòu)造函數(shù)和普通函數(shù)的區(qū)別,構(gòu)造函數(shù)通過new關(guān)鍵字進(jìn)行調(diào)用,而普通函數(shù)直接調(diào)用。
// 創(chuàng)建一個類(函數(shù))
function Person(name,age){
this.name = name;
this.age = age;
this.eat = function(){
console.log('我愛吃');
}
}
// 普通函數(shù)調(diào)用
var result = Person('張三',18);
console.log(result);
// 構(gòu)造函數(shù)調(diào)用
var p1 = new Person('李四',16);
console.log(p1);
var c2 = new Person('王五',14);
console.log(p2);
15. 函數(shù)中arguments 的對象是什么
函數(shù)在調(diào)用時JS引擎會向函數(shù)中傳遞兩個的隱含參數(shù),一個是this
(后面我們會說到),另一個就是arguments
,arguments
是一個偽數(shù)組,主要作用是:獲取函數(shù)中在調(diào)用時傳入的實參。
function add(){
console.log(arguments);
return arguments[0] + arguments[1];
}
add(10,20);
使用arguments.length可以獲取傳遞實參的個數(shù),同時也可以讓我們的函數(shù)具有多重功能。
function addOrSub(a,b,c){
if(arguments.length == 2){
return a - b;
}else if(arguments.length == 3){
return a + b + c;
}
}
console.log(addOrSub(10,20));//傳遞兩個實參就做減法
console.log(addOrSub(10,20,30));//傳遞的是三個實參就做加法
16. 列舉常用字符串方法
方法名 | 功能 | 原字符串是否改變 |
---|---|---|
charAt() | 返回指定索引的字符 | n |
charCodeAt(0) | 返回指定索引的字符編碼 | n |
concat() | 將原字符串和指定字符串拼接,不指定相當(dāng)于復(fù)制一個字符串 | n |
String.fromCharCode() | 返回指定編碼的字符 | n |
indexOf() | 查詢并返回指定子串的索引,不存在返回-1 | n |
lastIndexOf() | 反向查詢并返回指定子串的索引,不存在返回-1 | n |
localeCompare() | 比較原串和指定字符串:原串大返回1,原串小返回-1,相等返回0 | n |
slice() | 截取指定位置的字符串,并返回。包含起始位置但是不包含結(jié)束位置,位置可以是負(fù)數(shù) | n |
substr() | 截取指定起始位置固定長度的字符串 | n |
substring() | 截取指定位置的字符串,類似slice。起始位置和結(jié)束位置可以互換并且不能是負(fù)數(shù) | n |
split() | 將字符串切割轉(zhuǎn)化為數(shù)組返回 | n |
toLowerCase() | 將字符串轉(zhuǎn)化為小寫 | n |
toUpperCase() | 將字符串轉(zhuǎn)化為大寫 | n |
valueOf() | 返回字符串包裝對象的原始值 | n |
toString() | 直接轉(zhuǎn)為字符串并返回 | n |
includes() | 判斷是否包含指定的字符串 | n |
startsWith() | 判斷是否以指定字符串開頭 | n |
endsWith() | 判斷是否以指定字符串結(jié)尾 | n |
repeat() | 重復(fù)指定次數(shù) | n |
17. 列舉常用數(shù)組方法
方法名 | 功能 | 原數(shù)組是否改變 |
---|---|---|
concat() | 合并數(shù)組,并返回合并之后的數(shù)據(jù) | n |
join() | 使用分隔符,將數(shù)組轉(zhuǎn)為字符串并返回 | n |
pop() | 刪除最后一位,并返回刪除的數(shù)據(jù),在原數(shù)組 | y |
shift() | 刪除第一位,并返回刪除的數(shù)據(jù),在原數(shù)組 | y |
unshift() | 在第一位新增一或多個數(shù)據(jù),返回長度,在原數(shù)組 | y |
push() | 在最后一位新增一或多個數(shù)據(jù),返回長度 | y |
reverse() | 反轉(zhuǎn)數(shù)組,返回結(jié)果,在原數(shù)組 | y |
slice() | 截取指定位置的數(shù)組,并返回 | n |
sort() | 排序(字符規(guī)則),返回結(jié)果,在原數(shù)組 | y |
splice() | 刪除指定位置,并替換,返回刪除的數(shù)據(jù) | y |
toString() | 直接轉(zhuǎn)為字符串,并返回 | n |
valueOf() | 返回數(shù)組對象的原始值 | n |
indexOf() | 查詢并返回數(shù)據(jù)的索引 | n |
lastIndexOf() | 反向查詢并返回數(shù)據(jù)的索引 | n |
forEach() | 參數(shù)為回調(diào)函數(shù),會遍歷數(shù)組所有的項,回調(diào)函數(shù)接受三個參數(shù),分別為value,index,self;forEach沒有返回值 | n |
map() | 同forEach,同時回調(diào)函數(shù)返回數(shù)據(jù),組成新數(shù)組由map返回 | n |
filter() | 同forEach,同時回調(diào)函數(shù)返回布爾值,為true的數(shù)據(jù)組成新數(shù)組由filter返回 | n |
Array.from() | 將偽數(shù)組對象或可遍歷對象轉(zhuǎn)換為真數(shù)組 | n |
Array.of() | 將一系列值轉(zhuǎn)換成數(shù)組 | n |
find | 找出第一個滿足條件返回true的元素 | n |
findIndex | 找出第一個滿足條件返回true的元素下標(biāo) | n |
注意:重點關(guān)注方法的:功能、參數(shù)、返回值
18. 什么是DOM和BOM
DOM:文檔對象模型,將文檔看做是一個對象,這個對象主要定義了處理網(wǎng)頁內(nèi)容的方法和接口,通過JS操作頁面元素。
BOM:瀏覽器對象模型,將瀏覽器看做是一個對象,定義了與瀏覽器進(jìn)行交互的方法和接口,通過JS操作瀏覽器。
BOM的核心是window
,window
對象子有 location
navigator
history
。
DOM的最根本的document
對象也是window
對象的子對象。
window對象
-
window
對象是BOM的頂級對象,稱作瀏覽器窗口對象 - 全局變量會成為
window
對象的屬性 - 全局函數(shù)會成為
window
對象的方法
- window.onload
- window.onresize
- window.onscroll
Location對象
- 提供了url相關(guān)的屬性和方法。一些常用的有:
// url相關(guān)屬性
location.href
// 返回當(dāng)前加載頁面的完整URL
location.protocal
// 返回頁面使用的協(xié)議
location.search
// 返回URL的查詢字符串,查詢?開頭的的字符串
location.reload();
// reload():實現(xiàn)的是頁面刷新
location.assign("https://www.baidu.com");
// assign():可以打開新的頁面,并且可以返回,可以產(chǎn)生歷史紀(jì)錄的
location.replace("https://www.baidu.com");
// replace():用新文檔替換當(dāng)前的文檔,但不能返回,沒有產(chǎn)生歷史記錄
history對象
- 提供了對象包含瀏覽器的歷史記錄, 這些歷史記錄以棧的形式保存。頁面前進(jìn)則入棧,頁面返回則出棧。
history.back();//歷史記錄返回上一頁
history.forward();//去到下一頁
history.go(-2);//去到指定的歷史記錄頁 0代表當(dāng)前頁 -1代表之前 1代表之后
navigator對象
- 提供了瀏覽器相關(guān)的信息,比如瀏覽器的名稱、版本、語言、系統(tǒng)平臺等信息。
console.log(window.navigator.appName);//Netscape
console.log(window.navigator.appVersion);//瀏覽器版本
console.log(window.navigator.appCodeName);//瀏覽器內(nèi)核版本,但是打印出來一般
screen對象
- 提供了用戶顯示屏幕的相關(guān)屬性,比如顯示屏幕的寬度、高度。
console.log(window.screen.width);//屏幕的寬 分辨率
console.log(window.screen.height);//屏幕的高
19. DOM樹簡單描述一下
以Html為根節(jié)點,形成的一棵倒立的樹狀結(jié)構(gòu),我們稱作DOM樹。這個樹上所有的東西都叫節(jié)點,節(jié)點有很多類(元素、屬性、文本),通過DOM方法去獲取或者去操作節(jié)點,就叫DOM對象。
Document對象
指這份文件,也就是這份 HTML 檔的開端。當(dāng)瀏覽器載入 HTML 文檔, 它就會成為 Document 對象。
重繪:DOM元素的樣式發(fā)生改變,瀏覽器會重新渲染這個元素。
回流:DOM元素結(jié)構(gòu)或者位置發(fā)生改變(刪除、增加、改變位置大?。?,瀏覽器重新計算渲染整個DOM樹。
20. DOM操作
(1)查找節(jié)點
- getElementById //按照 id 查詢
- getElementsByTagName //按照標(biāo)簽名查詢
- getElementsByClassName //按照類名查詢
- querySelectorAll //按照css 選擇器查詢
(2)創(chuàng)建節(jié)點
document.write()
innerHTML
createElement()和appendChild()
(3)添加、移除、替換、插入
appendChild(node); //插入節(jié)點
removeChild(node); //移除節(jié)點
replaceChild(new,old); //替換節(jié)點
insertBefore(new,old) //追加節(jié)點
(4)屬性操作
getAttribute(key); //獲取自定義屬性
setAttribute(key, value); //設(shè)置自定義屬性
hasAttribute(key); //是否存在該屬性
removeAttribute(key); //移除屬性
(5)內(nèi)容修改
InnerText(); // 無標(biāo)簽效果
InnerHTML(); // 有標(biāo)簽效果
Text-content // IE9以上支持,類似innerText
21. 什么是事件傳播
當(dāng)事件發(fā)生在DOM
對象上,該事件并不完全發(fā)生在這個元素上。
關(guān)于事件傳播,IE和網(wǎng)景公司有不同的理解,IE認(rèn)為,事件應(yīng)該是冒泡階段,網(wǎng)景公司認(rèn)為事件應(yīng)該是捕獲階段,隨后W3C綜合了兩個公司的方案,JS同時支持冒泡和捕獲流,并以此確定事件流標(biāo)準(zhǔn)。這個標(biāo)準(zhǔn)也叫DOM2事件流。
事件傳播的三個階段:
(1)事件捕獲
事件從window
開始,從外向內(nèi),直到到達(dá)目標(biāo)事件或event.target
(1)目標(biāo)階段
事件到達(dá)目標(biāo)元素,觸發(fā)監(jiān)聽事件
(2)事件冒泡
事件從目標(biāo)元素開始冒泡,從內(nèi)向外,直到到達(dá)window
。
當(dāng)事件被觸發(fā)時,首先經(jīng)歷的是一個捕獲過程,事件會從最外層元素開始“穿梭”,逐層“穿梭”到最內(nèi)層元素。這個穿梭過程會持續(xù)到事件抵達(dá)他目標(biāo)的元素(也就是真正觸發(fā)這個事件的元素)為止。此時事件流接切換到了“目標(biāo)階段”——事件被目標(biāo)元素所接收然后事件會會彈,進(jìn)入到冒泡階段——他會沿著來時的路“逆流而上”,一層一層再走回去。
也就是說當(dāng)事件在層層DOM
元素穿梭時,所到之處都會觸發(fā)事件處理函數(shù)。
23. 三種事件模型是什么
DOM0級事件模型
通過對象.onclick
形式綁定,同一元素綁定多個相同事件,后會覆蓋前邊,事件不會傳播,不存在事件流概念。
<body>
<div id="box"></div>
<button>解綁</button>
<script>
window.onload = function () {
var box = document.querySelector('#box');
var btn = document.querySelector('button');
//dom0 綁定
box.onclick = function () {
console.log('我是dom0級事件1')
};//我是dom0級事件1
box.onclick = function () {
console.log('我是dom0級事件2')
};//我是dom0級事件
}
解綁 事件類型 = null
btn.onclick = function () {
box.onclick = null;
}
DOM2級事件模型
通過addEventListener
綁定,三個參數(shù),不帶on的事件類型,回調(diào)函數(shù),事件階段,默認(rèn)是false
,冒泡階段??梢越壎ǘ鄠€相同事件,事件從上到下執(zhí)行。this
指向當(dāng)前綁定事件對象。
box.addEventListener("click",function(){
console.log('今天中午吃多了')
},false)
box.addEventListener('click',fun,false);
function fun() {
console.log('晚上就不吃了')
}
通過removeEventListener
解綁,解綁參數(shù)與綁定參數(shù)一致,且事件需要解綁,那么函數(shù)必須定義成有名函數(shù)。
box.onclick=function(){
box.removeEventListener(fun)
}
IE事件模型(低級瀏覽器)
通過attachEvent
綁定,兩個參數(shù),帶on的事件類型,回調(diào)函數(shù)。this
指向window
。
box.attachEvent("onclick",function(){
console.log("今天晚上又吃了")
})
box.attachEvent("onclick",fun);
function fun(){
console.log("傷心")
}
通過detachEvent
解綁,解綁參數(shù)與綁定參數(shù)一致。
btn.onclick = function(){
box.detachEvent("onclick",fun)
}
24. 事件對象有哪些常用屬性
當(dāng)DOM
接受了一個事件,對應(yīng)的事件處理函數(shù)被觸發(fā)時,就會產(chǎn)生一個事件對象event
作為事件處理函數(shù)的入?yún)?。這個對象中包含著與事件有關(guān)的信息,比如事件是由哪個元素觸發(fā)的,事件類型等。常用屬性有:
- target
事件綁定的元素
- currentTarget
觸發(fā)事件的元素,兩者沒有冒泡的情況下,是一樣的值,但在使用了事件委托的情況下,就不一樣了。
preventDafault
阻止默認(rèn)行為,比如阻止超鏈接跳轉(zhuǎn)、在form中按回車會自動提交表單。
e.preventDefault();
stopPropagation
阻止事件冒泡,將事件處理函數(shù)的影響控制在目標(biāo)元素范圍內(nèi)
e.stopPropagation();
阻止事件冒泡,需要注意一點的是:谷歌火狐的組織行為是:event.stopPropagation()
,而IE:event.cancelBubble=true
25. 什么是事件委托
原理:
如果子元素有很多,且子元素的事件監(jiān)聽邏輯都相同,將事件監(jiān)聽綁定到父元素身上或者共有的祖先元素上 。事件委托原理是利用事件冒泡,子元素觸發(fā),父元素執(zhí)行回調(diào)函數(shù)。
好處:
(1)減少事件的綁定次數(shù)
(2)新增元素不需要單獨綁定
應(yīng)用:
頁面上有多個li,點擊每一個元素,都輸出他的文本內(nèi)容。
<body>
<ul id="poem">
<li>鵝鵝鵝</li>
<li>曲項向天歌</li>
<li>白毛浮綠水</li>
<li>紅掌撥清波</li>
<li>鋤禾日當(dāng)午</li>
<li>汗滴禾下土</li>
<li>誰知盤中餐</li>
<li>粒粒皆辛苦</li>
<li>背不動了</li>
<li>我背不動了</li>
</ul>
</body>
一個直觀的思路是讓每一個li元素都去監(jiān)聽一個點擊動作,但是這樣子并不好,我們可以采用事件委托的方式實現(xiàn)。
var ul = document.getElementById('poem')
ul.addEventListener('click', function(e){
console.log(e.target.innerHTML)
})
點擊任何一個li,點擊事件都會被冒泡到li共同的Ul上,我們通過Ul感知到這個冒泡來的事件,在通過e.target
拿到實際觸發(fā)事件的那個元素,通過事件委托只執(zhí)行一次DOM操作,減少了內(nèi)存開銷,大大提升了開發(fā)效率。
26. ECMAScript 是什么
ES是JS的標(biāo)準(zhǔn),約束條件。廣義的JS=ES+DOM+BOM,狹義的JS就是ES。EC是為了保證JS在瀏覽器運行結(jié)果一致。
是由歐洲計算機協(xié)會(ECMA)這個組織制定的。這個組織的目標(biāo)是制定和發(fā)布腳本語言規(guī)范。組織會定期定期召開會議,會議由一些公司的代表與特邀專家出席。
27. ECMAScript 2015(ES6)有哪些新特性?
在2011年ECMA組織就開始著手制作第6個版本規(guī)范,由于這個版本引入的功能語法太多,最終標(biāo)準(zhǔn)的制作者決定在每年6月份發(fā)布一次,版本號為年號代替,ES6正式發(fā)布于2015 年 6 月。我們現(xiàn)在所說的ES6是一個泛指,泛指ES2015之后的版本。
- 塊級作用域
- 對象數(shù)組解構(gòu)賦值
- 模板字符串
- 箭頭函數(shù)
- 延展運算符
- 剩余參數(shù)
- 聲明類
- set、map集合
- Promise
28. Var,Let和Const的區(qū)別是什么
let
關(guān)鍵字用來聲明變量,使用let
聲明的變量有以下特點:
不允許重復(fù)聲明
let name = '張三'
let name = '李四'
console.log(name);
// SyntaxError
let num = 1;
num = 2;
console.log(num);//2
// 可以重復(fù)賦值
不存在預(yù)解析
預(yù)解析:JS引擎在JS代碼正式執(zhí)行之前會做一些預(yù)解析的工作。
- 先把
var
變量聲明提前 - 再把以
function
開頭的整個函數(shù)提前
console.log(num)
//undefined
var num = 10
console.log(num)
let num = 10
// ReferenceError
具有塊級作用域
塊級作用域:使用let
聲明變量,如果被一個大括號括住,那么這個大括號括住的變量就形成了一個塊級作用域。
塊級作用域定義的變量只在當(dāng)前塊中生效,這和函數(shù)作用域類似。
{
let num = 10;
console.log(num);
}
console.log(num); // 報錯
ES6中規(guī)定,let/const
命令會使區(qū)塊形成封閉作用域,在聲明之前使用變量,就會報錯。這在語法上,稱為“暫時性死區(qū)”
塊級作用域還有的一個好處:防止循環(huán)變量變成全局變量
for(var i=0;i<2;i++){
}
console.log(i);
//2
for(let i=0;i<2;i++){
}
console.log(i);
//i is not defined
const 關(guān)鍵字
和let
類似,不可重復(fù)聲明,不存在預(yù)解析,擁有塊級作用域。同時使用const
聲明的變量值無法改變,常用于聲明常量,常量名一般為大寫,單詞間用下劃線。
const PI = 3.14
PI = 100
// Missing initializer in const declaration
必須要有初始值
const PI
console.log(PI)
// Missing initializer in const declaration
可以修改數(shù)組和對象元素
const obj = {}
obj.age = 10
console.log(obj.age)10
const arr = []
arr.push(10)
console.log(arr)//[10]
29. const對象的屬性可以修改嗎
const
保證的并不是變量的值不能改動,而是指向那個內(nèi)存地址不能改動,對于基本數(shù)據(jù)類型(數(shù)值、字符串、布爾值),其值就保存在變量指向的那個內(nèi)存地址,因此等同于常量。
但對于引用數(shù)據(jù)類型(主要是對象和數(shù)組),變量指向數(shù)據(jù)的內(nèi)存地址,保存的只是一個指針,const
只能保證這個指針是固定不變的,至于他指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的,就不完全能控制了。
30. 什么是解構(gòu)賦值
在es6之前,獲取對象或者數(shù)組中數(shù)據(jù),只能通過屬性訪問的形式并賦值給本地變量,這樣需要寫許多相似的代碼。
const obj = {
name: '張三',
age: 20,
}
const name = obj.name
const age = obj.age
console.log(name,age)// 張三 20
有了解構(gòu)賦值可以方便獲取數(shù)組中的數(shù)據(jù),獲取對象中屬性和方法。
我們可以直接從數(shù)組或?qū)ο笾刑崛?shù)據(jù),并賦值給變量。
數(shù)組解構(gòu)賦值
let arr=[1,2,3];
// 定義變量并接收
let [s1,s2,s3]=arr;// 完全解構(gòu)
let [s1,,s3]=arr; // 不完全解構(gòu)
console.log(s1);// 1
對象的解構(gòu)賦值
const obj = {
name: '張三',
age,
sayHi: function () {
console.log('你好')
}
}
// 定義變量,對應(yīng)屬性,想要什么屬性就寫什么屬性
const { name, sayHi } = obj
console.log(name);// 張三
sayHi();// 你好
// 為屬性取別名
const {name:defaultName,sayhi} = obj;
console.log(defaultName);
// 設(shè)置屬性默認(rèn)值
const {age=20} = obj;
console.log(age);//20
31. 什么是模板字符串
模板字符串是增強版的字符串,用反引號
表示,模板字符串可以當(dāng)普通字符串使用,也可以用來定義多行字符串,作用是簡化字符串的拼接。特點如下:
可以出現(xiàn)換行符
// 可以出現(xiàn)換行符
document.write(`
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
`)
可以輸出變量
// 通過 ${} 形式輸出變量
const age = 100
const text = `今年過年,小明的年齡已經(jīng)是:${age}`;
console.log(text)
32. 箭頭函數(shù)和普通函數(shù)區(qū)別
ES6中允許使用箭頭 => 定義函數(shù),主要作用不僅僅是:簡化function寫法,更重要的是改變this指向。
基本用法
// es6以前寫法
const add =function(a,b){
return a + b
}
const result = add(10,20)
console.log(result) //30
// es6寫法
const add =(a,b)=>{
return a + b
}
const result = add(10,20)
console.log(result) //30
與普通函數(shù)區(qū)別
- 不能作為構(gòu)造函數(shù)實例化
const Person =()=>{
name:'小明'
};
var per = new Person();
console.log(per.name)
// Person is not a constructor
- 不能使用 arguments
const f4 =(arguments)=>{
console.log(arguments.length)
}
f4(1,2,3,4,5)
// undefined
- 箭頭函數(shù)的this是不能改變
window.name = '小明'
const f5 = () => {
console.log(this.name) // 小明
};
const f6 = function () {
console.log(this.name) // 小明
};
f5();
f6();
var obj = {
name: '大明'
}
f5.call(obj) // 小明
f6.call(obj) // 大明
- this指向包裹箭頭函數(shù)的第一個普通函數(shù)
let school = {
name: '小明',
getName(){
let fn7 = () => {
console.log(this); // 小明
}
fn7();
}
};
33. 什么是剩余運算符
剩余運算符中最重要的特點就是代替以前的arguments
,利用剩余運算符可以獲取函數(shù)調(diào)用時傳遞的參數(shù),并且返回值是一個真數(shù)組。
function f1(...args) {
console.log(args);
// [1,2,3]
}
f2(1, 2, 3)
// 形參較多,放在最后位置
function f3(a, b, ...args) {
console.log(a,b);//1,2
console.log(args);// [3,4,5]
//
}
f3(1, 2, 3, 4, 5)
34. 延展運算符使用過嗎
拆包和打包數(shù)組、對象
function f2(...args) {
console.log(args)
}
f2(1, 2, 3, 4, 5);
// [1, 2, 3, 4, 5]
function f3(...args) {
console.log(...args)
}
f3(1, 2, 3, 4, 5);
// 1 2 3 4 5
數(shù)組合并
var arr1=[10,20,30];
var arr2=[40,50,60];
var arr=[...arr1,...arr2];
console.log(arr);
// [10, 20, 30, 40, 50, 60]
對象合并
字面量復(fù)制對象 let obj={ } {…obj}
var obj1={
name:'自來也',
age:45
}
var obj2={
gender:'男',
hobby(){
console.log(console.log('吃飯'))
}
}
var obj={
name:'菲兒',
...obj1,
...obj2
}
console.log(obj);
// {name: "自來也", age: 45, gender: "男", hobby: ?}
數(shù)組的克隆
const arr3 = [10, 20, 30]
const arr4 = [...arr3]
console.log(arr4)
// [10, 20, 30]
偽數(shù)組轉(zhuǎn)真數(shù)組
const arr5 = document.getElementsByTagName('button');
console.log(arr5);
//[button, button, button]
console.log(arr5 instanceof Array);//false;
console.log([...arr5] instanceof Array);//true
console.log(arr5);
35. 什么是類
類(class)是ES6中語法糖,最終還是轉(zhuǎn)化成構(gòu)造函數(shù)去執(zhí)行,使用class創(chuàng)建的類會將方法自動加到原型上。
class Person{
// 通過構(gòu)造函數(shù) -- 初始化實例化對象屬性
constructor(name,age){
this.name=name;
this.age= age;
}
// 添加方法 不需要添加,
eat(){
console.log('哈哈')
}
}
const per = new Person('小明',20);
console.log(per.name);
per.eat();
36. set集合和map集合了解多少
set
- 是一個構(gòu)造函數(shù),用來存儲任意數(shù)據(jù)類型的唯一值;
- 可以存儲數(shù)組、字符串,返回值是一個對象。
定義Set集合
// 定義set集合
const s1 = new Set()
console.log(s1)
// { }
?
//打印長度
console.log(s1.size);// 0
傳入數(shù)據(jù)
const s = new Set([10,20,30,40,40]);
?
console.log(s);
// {10, 20, 30, 40}
// 打印出來是一個集合 需要拆包
console.log(...s);
// 10 20 30 40
set方法
// 添加數(shù)據(jù)
const s = new Set();
// 向Set集合中添加一個數(shù)據(jù)
s.add('a').add('b');
console.log(...s);
// a b
// 移除數(shù)據(jù)
r1 = s.delete('a');
console.log(r1);
// 返回結(jié)果是ture值,代表刪除成功
// 是否存在這個數(shù)據(jù)
r2 = s.has(9);
console.log(r2);//false
// 清空數(shù)據(jù)
r3 = s.clear()
console.log(r2);// undefined
應(yīng)用
- 數(shù)組去重
let arr1=[1,2,3,4,4,5,1];
// {1, 2, 3, 4, 5} 1,2,3,4,5 []
?
let arr2=[...new Set(arr1)];
console.log(arr2);
// [1, 2, 3, 4, 5]
- 交集操作
const arr3 = [1, 2, 3, 4, 5, 6, 7];
const arr4 = [1, 2, 3, 10, 11];
//對數(shù)組進(jìn)行拆包 過濾 判斷4里面是否存在item
const result = [...new Set(arr3)].filter(item => new Set(arr4).has(item));
console.log(result);
- 并集操作
const arr5 = [1, 2, 3, 4, 5];
const arr6 = [1, 2, 3, 6, 7, 8];
const result = [...new Set([...arr5,...arr6])]
console.log(result);
- 差集操作(我有的你沒有,或者你有的我沒有)
const arr7 = [1, 2, 3, 4, 5];
const arr8 = [1, 2, 3, 8, 9];
?
// 判斷arr8中是否含有數(shù)組中每一項數(shù)據(jù)
const result=[...new Set(arr7)].filter(!(item=>new Set(arr8).has(item)));
const result=[...new Set(arr8)].filter()
map集合
類似于對象,存放鍵值對,鍵和值可以是任何數(shù)據(jù)類型。
- 鍵值的方式添加數(shù)據(jù)
var m = new Map();
map.set('name', '強哥')
map.set(obj, function(){console.log('真好')})
- 讀取 刪除 判斷 清空
// 根據(jù)鍵獲取值
console.log(map.get(obj))
// 根據(jù)鍵進(jìn)行刪除
map.delete('name')
// 根據(jù)鍵進(jìn)行判斷
console.log(map.has('name'))
// 清空map
map.clear()
37. Proxy 可以實現(xiàn)什么功能
在 Vue3.0 中通過 Proxy
來替換原本的 Object.defineProperty
來實現(xiàn)數(shù)據(jù)響應(yīng)式。
Proxy 是 ES6 中新增的功能,它可以用來自定義對象中的操作。
let p = new Proxy(target, handler)
target
代表需要添加代理的對象,handler
用來自定義對象中的操作,比如可以用來自定義 set
或者 get
函數(shù)。
下面來通過 Proxy
來實現(xiàn)一個數(shù)據(jù)響應(yīng)式:
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(`監(jiān)聽到屬性${property}改變?yōu)?/span>${v}`)
},
(target, property) => {
console.log(`'${property}' = ${target[property]}`)
}
)
p.a = 2 // 監(jiān)聽到屬性a改變
p.a // 'a' = 2
在上述代碼中,通過自定義 set
和 get
函數(shù)的方式,在原本的邏輯中插入了我們的函數(shù)邏輯,實現(xiàn)了在對對象任何屬性進(jìn)行讀寫時發(fā)出通知。
當(dāng)然這是簡單版的響應(yīng)式實現(xiàn),如果需要實現(xiàn)一個 Vue 中的響應(yīng)式,需要在 get
中收集依賴,在 set
派發(fā)更新,之所以 Vue3.0 要使用 Proxy
替換原本的 API 原因在于 Proxy
無需一層層遞歸為每個屬性添加代理,一次即可完成以上操作,性能上更好,并且原本的實現(xiàn)有一些數(shù)據(jù)更新不能監(jiān)聽到,但是 Proxy
可以完美監(jiān)聽到任何方式的數(shù)據(jù)改變,唯一缺陷就是瀏覽器的兼容性不好。
38. promise
- 產(chǎn)生
ES6中新技術(shù),解決異步回調(diào)地域問題。
回調(diào)地獄: 回調(diào)嵌套或者函數(shù)很亂的調(diào)用,簡單來說,就是:發(fā)四個請求,第四個依賴第三個結(jié)果,第三個依賴第二個的結(jié)果,第二個依賴第一個的結(jié)果。 回調(diào)函數(shù)弊端: 不利于閱讀,不利于捕獲異常,不能直接return
。
setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
},3000)
},2000)
},1000)
常見的回調(diào)函數(shù): 計時器、AJAX、數(shù)據(jù)庫操作、fs,其中,經(jīng)常使用的場景是 ,AJAX請求以及各種數(shù)據(jù)庫操作會產(chǎn)生回調(diào)地獄。 promise解決異步避免回調(diào)地獄
function f1() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(111), 1000);
}).then(data => console.log(data));
}
function f2() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(222), 2000);
}).then(data => console.log(data));;
}
function f3() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(333), 3000);
}).then(data => console.log(data));;
}
?
f1().then(f2).then(f3)
- 基礎(chǔ)
- promise對象表示一個異步操作的最終完成或失敗,及其結(jié)果值 , 是一個代理值。
- 語法上:Promise是一個構(gòu)造函數(shù),用來生成Promise的實例對象。
- 功能上:Promise對象用來包裹一個異步操作,并獲取成功、失敗結(jié)果值。
- 三種狀態(tài)
- pending:初始狀態(tài),既不成功,也不失敗。
- fulfilled:操作成功完成
- rejected:操作失敗 狀態(tài)要不成功要不失敗,此過程不可逆,且只能修改一次。
- 基本使用
promise構(gòu)造函數(shù)有兩個參數(shù)(resolve,reject), 操作成功,調(diào)用resolve函數(shù),將promise對象的狀態(tài)改為fulfilled。 操作成功,調(diào)用rejected函數(shù),將promise對象的狀態(tài)改為rejected。
- resolve函數(shù)
let obj = new Promise((resolve, reject) => {
resolve('ok') ;
});
//1. 如果傳入的是非Promise類型的數(shù)據(jù),則返回成功的promise
let p = Promise.resolve('abc');
//2. 如果是 Promise ,那么該對象的結(jié)果就決定了 resolve 的返回結(jié)果
let p2 = Promise.resolve(obj);
//3. 嵌套使用
let p3 = Promise.resolve(Promise.resolve(Promise.resolve('ABC')));
?
console.log(p3);
- reject函數(shù)
//Promise.prototype.reject 返回的始終是失敗的 Promise
let p = Promise.reject(1231231);
let p2 = Promise.reject('abc');
let p3 = Promise.reject(Promise.resolve('OK'));
console.log(p3);
- API
- than
為當(dāng)前promise指定成功或失敗的回調(diào),返回一個新的promise供我們調(diào)用。成功的參數(shù)一般是value,失敗的參數(shù)reason。 than里的數(shù)據(jù)—>resolve里的數(shù)據(jù) than返回結(jié)果—>than里的回調(diào)函數(shù)決定
let p=new Promise((resolve,reject)=>{
resolve('ok')
})
// value是resolve的參數(shù),第一個箭頭函數(shù)叫resolve
p.then(value=>{
console.log(value)//ok
},reason=>{
console.log('onRejected1', reason)
// (1)如果返回非Promise類型的數(shù)據(jù),則返回成功的promise
return 1000
// (2)返回Promise ,那么該對象的結(jié)果就決定了函數(shù)的返回結(jié)果
return Promise.resolve(300)
// (3)拋出錯誤,失敗的promise
throw 100
// (4)無返回值,返回undefined,也是成功的promise
})
- catch
指定失敗的回調(diào)
let p =new Promise((resolve,reject)=>{
reject('失敗了');
})
p.then(value=>{},reason=>{
console.error(reason);
})
p.catch(reason=>{
console.error(reason)
})
3 .all
Promise.all([promise1,promise2,promise3]) 批量一次性發(fā)送多個異步請求 只有當(dāng)都成功是返回的promise才會成功 返回一個新的promise 問題: 發(fā)3請求成功后再4個請求
function ajax(url) {
return axios.get(url)
}
const p1 = ajax(url1)
const p2 = ajax(url2)
const p3 = ajax(url3)
Promise.all([p1, p2, p3])
// values和數(shù)組中數(shù)據(jù)的順序有關(guān)系
.then(values => {
return ajax(url4)
})
.then(value => {
console.log(value) // 就是第4個請求成功的value
})
.catch(error => {
?
})
4 .race
多個promise任務(wù)同步執(zhí)行,返回最先結(jié)束的promise任務(wù)結(jié)束,不論是成功還是失敗,簡單來說就先到先得。
// race 賽跑
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
?
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Yes');
}, 500);
});
?
let result = Promise.race([p1, p2]);
?
console.log(result);
39. 什么是asyn await
new Promise(resolve,reject)=>{
setTimeout(()=>resolve(111),1000)
}).then(data=>{
console.log(data)
})
es8新增promise語法糖 建立在Promise之上異步編程終極解決方案,使用同步代碼實現(xiàn)異步代碼。 相對于 Promise 和回調(diào),它的可讀性和簡潔度都更高,更好地處理 then 鏈。
- async
異步,函數(shù)前加上async,聲明一個函數(shù)是異步的 那么該函數(shù)就會返回一個 Promise,async返回的promise成功還是失敗,看函數(shù)return
async function main(){
// 1. 如果返回的是非 Promise 對象,所有數(shù)據(jù)類型
// 返回成功的promise
return 'iloveyou';
?
//2. 如果返回的是 Promise 對象
// 看返回的promise是成功還是失敗,成功則成功,失敗則失敗。
return new Promise((resolve ,reject) => {
resolve('123');
reject('失敗');
});
?
//3. 函數(shù)拋出異常 promise對象也是失敗的
throw '有點問題';
}
// let result = main();
// console.log(result);
?
main().then(value => {}, reason=>{
console.error(reason);
});
- await
await 異步等待 等待一個異步方法執(zhí)行完成,后面常放返回promise對象表達(dá)式,必須放在async中。
await相當(dāng)于promise的then
try 放到成功的操作 catch 放失敗的操作
// await必須寫在async函數(shù)中,但async函數(shù)中可以沒有await
async function main() {
// 1、看返回的promise是成功還是失敗,看promise的返回結(jié)果
let result = await Promise.resolve('OK');
console.log(result);
// 2、返回其他值,返回變量值
let one = await 1;
console.log(one);//1
}
40. new關(guān)鍵字做了什么
- 創(chuàng)建一個空對象(實例化對象)
-
this
指向新對象 - 屬性方法賦值
- 將這個新對象返回
41. 談?wù)勀銓υ偷睦斫?/h2>
為什么要有原型?構(gòu)造函數(shù)中的實例每調(diào)用一次方法,就會在內(nèi)存中開辟一塊空間,從而造成內(nèi)存浪費。
在函數(shù)對象中,有一個屬性prototype
,它指向了一個對象,這個對象就是原型對象,這個對象的所有屬性和方法,都會被構(gòu)造函數(shù)所擁有。
function Person(name, age){
}
console.log(Person.prototype)
// {constructor: ?}
普通函數(shù)調(diào)用,prototype
沒有任何作用,構(gòu)造函數(shù)調(diào)用,該類所有實例有隱藏一個屬性(proto)指向函數(shù)的prototype。(實例的隱式原型指向類的顯示原型)
//實例的隱式原型指向構(gòu)造函數(shù)的顯示原型
console.log(p1.__proto__ === Person.prototype);
//true
原型就相當(dāng)于一個公共區(qū)域,可以被類和該類的所有實例訪問到。
所以我們在定義類時,公共屬性定義到構(gòu)造函數(shù)里面,公共的方法定義到構(gòu)造函數(shù)外部的原型對象上。
原型優(yōu)點:資源共享,節(jié)省內(nèi)存;改變原型指向,實現(xiàn)繼承。缺點:查找數(shù)據(jù)的時候有的時候不是在自身對象中查找。
42. 談?wù)勀銓υ玩湹睦斫?/h2>
原型鏈:實際上是指隱式原型鏈,從對象的__proto__
開始,連接所有的對象,就是對象查找屬性或方法的過程。
- 當(dāng)訪問一個對象屬性時,先往實例化對象在自身中尋找,找到則是使用。
- 找不到(通過
_proto_
屬性)去它的原型對象中找,找到則是使用。 - 沒有找到再去原型對象的原型(
Object
原型對象)中尋找,直到找到Object
為止,如果依然沒有找到,則返回undefined
。
43. 談?wù)勀銓his,call,apply,bind理解
當(dāng)一個函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行上下文,其中this
就是執(zhí)行上下文的一個屬性,this
是函數(shù)在調(diào)用時JS引擎向函數(shù)內(nèi)部傳遞的一個隱含參數(shù)。
this
指向完全是由它的調(diào)用位置決定,而不是聲明位置。除箭頭函數(shù)外,this指向最后調(diào)用它的那個對象。
- 全局作用域中,無論是否嚴(yán)格模式都指向
window
; - 普通函數(shù)調(diào)用,指向
window
;嚴(yán)格模式下指向undefined
; - 對象方法使用,該方法所屬對象;
- 構(gòu)造函數(shù)調(diào)用,指向?qū)嵗瘜ο螅?/li>
- 匿名函數(shù)中,指向
window
; - 計時器中,指向
window
; - 事件綁定方法,指向事件源;
- 箭頭函數(shù)指向其上下文中
this
;
call
、apply
和bind
,都是用來改變this
指向的,三者是屬于大寫 Function
原型上的方法,只要是函數(shù)都可以使用。
call
和apply
的區(qū)別,體現(xiàn)在對入?yún)⒌囊蟛煌?code>call的實參是一個一個傳遞,apply
的實參需要封裝到一個數(shù)組中傳遞。
call
、apply
相比bind
方法,函數(shù)不會執(zhí)行,所以我們需要定義一個變量去接收執(zhí)行。
更多詳細(xì)的內(nèi)容可看我之前文章:《this指向詳解及自定義call、apply、bind》
44. 什么是閉包,哪些地方用到過
很多編程語言都支持閉包,閉包不是語言特性,而是一種編程習(xí)慣。閉包(Closure)是指具有一個封閉對外不公開的包裹結(jié)構(gòu),或空間。
在JS中,我們可以理解為閉包是函數(shù)在特定情況下執(zhí)行產(chǎn)生的一種現(xiàn)象。
所謂閉包,是一種引用關(guān)系,該引用關(guān)系存在內(nèi)部函數(shù)中,內(nèi)部函數(shù)引用外部函數(shù)數(shù)據(jù),引用的數(shù)據(jù)可以在函數(shù)詞法作用域(函數(shù)外部)之外使用。
產(chǎn)生閉包必滿足三個條件:函數(shù)嵌套、內(nèi)部函數(shù)引用外部函數(shù)數(shù)據(jù)、外部函數(shù)調(diào)用,凡是所有的閉包都滿足以上三個條件,否則不構(gòu)成閉包。
閉包本質(zhì):內(nèi)部函數(shù)里的一個對象,這個對象非Js對象(有屬性有方法的對象),這個對象是函數(shù)在運行時,本該釋放的活動對象,這個活動對象里包含著我們引用的變量。
閉包的作用:模擬私有變量、柯里化、偏函數(shù)、防抖、節(jié)流、實現(xiàn)緩存。
模擬私有變量:將私有變量放在外在的立即執(zhí)行函數(shù)中,并通過立即執(zhí)行這個函數(shù),創(chuàng)造一個閉包環(huán)境(私有變量:只允許函數(shù)內(nèi)部,或?qū)ο蠓椒ㄔL問的變量)。
柯里化:把接受n個參數(shù)的一個函數(shù)轉(zhuǎn)化成只接受一個參數(shù)n個函數(shù)互相嵌套的函數(shù)過程,目標(biāo)是把函數(shù)拆解為精準(zhǔn)的n部分,也就是將fn(a,b,c)轉(zhuǎn)化成fn(a)(b)(c)
的過程。
偏函數(shù):固定函數(shù)中的某一個或幾個參數(shù),然后返回一個新的函數(shù)。
防抖:只執(zhí)行最后一次。
節(jié)流:隔一段時間執(zhí)行一次。
閉包與內(nèi)存泄露:閉包造成內(nèi)存泄漏是誤傳,誤傳由于早期IE垃圾回收機機制是基于基于引用計數(shù)法,閉包當(dāng)中如果包含循環(huán)引用,那么IE瀏覽器無法回收閉包中引用的變量,但這內(nèi)存泄漏和閉包沒有關(guān)系,而是IE的bug。
更多詳細(xì)的內(nèi)容可看我之前文章:《這次把閉包給你講的明明白白》和《 閉包典型應(yīng)用用及性能問題》
45. 常見的內(nèi)存泄漏有哪些
- “手滑”導(dǎo)致的全局變量
function f1() {
name = '小明'
}
在非嚴(yán)格模式下引用未聲明的變量,會在全局對象中創(chuàng)建一個新變量,在瀏覽器中,全局對象是window
,這就意味著name
這個變量將泄漏到全局。全局變量是在網(wǎng)頁關(guān)閉時才會釋放,這樣的變量一多,內(nèi)存壓力也會隨之增高。
- 遺忘清理的計時器
程序中我們經(jīng)常會用到計時器,也就是setInterval
和setTimeout
var timeId = setInterval(function(){
// 函數(shù)體
},1000)
- 遺忘清理的dom元素引用
var divObj = document.getElementById('mydiv')
// dom刪除myDiv
document.body.removeChild(divObj);
console.log(divObj);
// 能console出整個div 說明沒有被回收,引用存在
// 移出引用
divObj = null;
console.log(divObj)
// null
46. JS微任務(wù)紅任務(wù)執(zhí)行順序
(1)JS引擎首先執(zhí)行所有同步代碼
(2)宏隊列:保存待執(zhí)行宏任務(wù)
(3)微隊列:保存待執(zhí)行的微任務(wù)
47. 簡單介紹一下JS的垃圾回收機制
每隔一段時間,JS的垃圾收集器就會對變量做“巡檢”。當(dāng)它判斷一個變量不再被需要之后,它就會把這個變量所占的內(nèi)存空間給釋放掉,這個過程叫做垃圾回收。
常用的垃圾回收算法有兩種——引用計數(shù)法和標(biāo)記清除法。
- 引用計數(shù)法
這是最初級的垃圾回收算法,在現(xiàn)代瀏覽器里幾乎被淘汰的干干凈凈。
當(dāng)我們創(chuàng)建一個變量,對應(yīng)的也就創(chuàng)建了一個針對這個值的引用。
const students = ['小紅','小明']
在引用這塊計數(shù)法的機制下,內(nèi)存中每一個值都會對應(yīng)一個引用計數(shù)。當(dāng)垃圾收集器感知到某個值的引用計數(shù)為0時,就判斷它“沒用”了,隨即這塊內(nèi)存就會被釋放。
比如我們此時如果把student指向一個null:
students = null
那么這個數(shù)組所應(yīng)用的引用計數(shù)就會變成0(如下圖),它就變成一塊沒用的內(nèi)存,即將面臨著作為垃圾,被回收的命運。
引用計數(shù)法弊端
大家現(xiàn)在來看這樣一個例子:
function badCycle() {
var cycleObj1 = {}
var cycleObj2 = {}
cycleObj1.target = cycleObj2
cycleObj2.target = cycleObj1
}
badCycle()
當(dāng)執(zhí)行了badCycle這個函數(shù),作用域內(nèi)的變量也會全部被視為“垃圾”進(jìn)而移除。
但如果咱們用了引用計數(shù)法,那么即使 badCycle 執(zhí)行完畢,cycleObj1 和 cycleObj2 還是會活得好好的 —— 因為 cycleObj2 的引用計數(shù)為 1(cycleObj1.target),而 cycleObj1 的引用計數(shù)也為 1 (cycleObj2.target)(如下圖)。
這就是引用計數(shù)法的弊端,無法甄別循環(huán)引用場景下的“垃圾”。
- 標(biāo)記清除法
引用計數(shù)法無法甄別“循環(huán)引用”場景下的“垃圾”,自 2012年起,所有瀏覽器都使用了標(biāo)記清除算法??梢哉f,標(biāo)記清除法是現(xiàn)代瀏覽器的標(biāo)準(zhǔn)垃圾回收算法。
在標(biāo)記清除算法中,一個變量是否被需要的判斷標(biāo)準(zhǔn),是它是否可抵達(dá) 。
這個算法有兩個階段,分別是標(biāo)記階段和清除階段:
- 標(biāo)記階段:垃圾收集器會先找到根對象,在瀏覽器里,根對象是 Window;在 Node 里,根對象是 Global。從根對象出發(fā),垃圾收集器會掃描所有可以通過根對象觸及的變量,這些對象會被標(biāo)記為“可抵達(dá) ”。
- 清除階段: 沒有被標(biāo)記為“可抵達(dá)” 的變量,就會被認(rèn)為是不需要的變量,這波變量會被清除
現(xiàn)在大家按照標(biāo)記清除法的思路,再來看這段代碼:
function badCycle() {
var cycleObj1 = {}
var cycleObj2 = {}
cycleObj1.target = cycleObj2
cycleObj2.target = cycleObj1
}
badCycle()
badCycle 執(zhí)行完畢后,從根對象 Window 出發(fā),cycleObj1 和 cycleObj2 都會被識別為不可達(dá)的對象,它們會按照預(yù)期被清除掉。這樣一來,循環(huán)引用的問題,就被標(biāo)記清除干脆地解決掉了。
48. JS的深淺拷貝
JS基本數(shù)據(jù)類型不存在深淺拷貝問題,深拷貝和淺拷貝主要針對引用數(shù)據(jù)類型(數(shù)組、函數(shù)、對象)
淺拷貝:
拷貝對象的時候,如果屬性是基本數(shù)據(jù)類型,拷貝就是基本數(shù)據(jù)類型的值,如果屬性是引用數(shù)據(jù)類型,拷貝的就是內(nèi)存地址,因此修改新拷貝對象屬性會影響原對象。
深拷貝:將一個對象從內(nèi)存中完整的拷貝出來,從堆內(nèi)存中開辟一個新的區(qū)域存放新對象,且修改新對象不影響原對象。
區(qū)別:深拷貝修改拷貝對象影響原對象,淺拷貝不影響。
淺拷貝數(shù)組
(1)concat方法
var arr =[1,2,3,{name:'小明',age:20}];
var newArr=arr.concat();
arr[3].name="小紅";
console.log(arr);
//{name:"小紅",age:20}
console.log(newArr);
//{name:"小紅",age:20}
(2)slice方法
var arr =[1,2,3,{name:'小明',age:20}];
var newArr = arr.slice(0);
newArr[3].name = '小紅';
console.log(arr);
console.log(newArr);
(3)延展運算符
var arr =[1,2,3,{name:'小明',age:20}];
var newArr = [...arr];
arr[3].name = '小紅';
console.log(arr);
console.log(newArr);
淺拷貝對象
(1)直接拷貝
var obj1={
name:'小明',
cars:[
'奔馳',
‘寶馬’,
]
}
var obj2=obj1
(2)assign
對象的合并,將源對象的所有可枚舉屬性,復(fù)制到目標(biāo)對象
var obj1={
name:'小明',
cars:[
'奔馳',
‘寶馬’,
]
}
// 目標(biāo)對象 源對象
var obj2=object.assign({},obj1)
深拷貝
(1)JSON
先使用JSON.stringify
將JS對象轉(zhuǎn)化成JSON串,再使用JSON.parse
將JSON字符串轉(zhuǎn)化為對象。
不足:忽略對象中的函數(shù)、undefiend
、RegExp
、Date
。
對象中的函數(shù)、undefined
屬性會直接忽略,對象中的RegExp
,拷貝后會為空,對象中的Date
會轉(zhuǎn)化為字符串。
const school={
name :'慕課網(wǎng)',
type:['前端','java','go'],
fn(){
console.log("我愛學(xué)習(xí)");
}
}
//將對象轉(zhuǎn)化為字符串
let str =JSON.stringify(school);
//{"name":"慕課網(wǎng)","type":["前端","java","go"]}
//將字符串轉(zhuǎn)化為JS對象
let newSchool=JSON.parse(str);
//修改新對象屬性
newSchool.type[0]="c++";
console.log(school);
// ["前端", "java", "go"]
console.log(newSchool);
// ["c++", "java", "go"]
- 手寫深拷貝
// 遞歸實現(xiàn)深拷貝
let school = {
name: '慕課網(wǎng)',
type: ['前端', '后端', '大數(shù)據(jù)'],
subtype: {
name: '前端',
type: 'vue'
},
fn() {
console.log('我愛學(xué)習(xí)')
}
}
// 獲取數(shù)據(jù)類型
function getType(data) {
return Object.prototype.toString.call(data).slice(8, -1)
}
console.log(getType(school))
// 遞歸實現(xiàn)深拷貝
function deepClone(data) {
// 1、判斷數(shù)據(jù)類型
let type = getType(data);
let container;
if (type === 'Array') {
container = [];
}
if (type === 'Object') {
container = {}
}
// 2、遍歷
for (let i in data) {
let t = getType(data[i])
if (t === 'Array' || t === 'Objcet') {
container[i] = deepClone(data[i])
} else {
container[i] = data[i]
}
}
return container;
}
const newSchool = deepClone(school);
newSchool.type[0] = '前端端'
console.log(school)
console.log(newSchool)
49. 手寫防抖節(jié)流
防抖
// fn是我們需要包裝的事件回調(diào), delay是每次推遲執(zhí)行的等待時間
function debounce(fn, delay) {
// 定時器
let timer = null
// 將debounce處理結(jié)果當(dāng)作函數(shù)返回
return function () {
// 保留調(diào)用時的this上下文
let context = this
// 保留調(diào)用時傳入的參數(shù)
let args = arguments
// 每次事件被觸發(fā)時,都去清除之前的舊定時器
if(timer) {
clearTimeout(timer)
}
// 設(shè)立新定時器
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
// 用debounce來包裝scroll的回調(diào)
const better_scroll = debounce(() => console.log('觸發(fā)了滾動事件'), 1000)
document.addEventListener('scroll', better_scroll)
節(jié)流
// fn是我們需要包裝的事件回調(diào), delay是每次推遲執(zhí)行的等待時間
function debounce(fn, delay) {
// 定時器
let timer = null
// 將debounce處理結(jié)果當(dāng)作函數(shù)返回
return function () {
// 保留調(diào)用時的this上下文
let context = this
// 保留調(diào)用時傳入的參數(shù)
let args = arguments
// 每次事件被觸發(fā)時,都去清除之前的舊定時器
if(timer) {
clearTimeout(timer)
}
// 設(shè)立新定時器
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
// 用debounce來包裝scroll的回調(diào)
const better_scroll = debounce(() => console.log('觸發(fā)了滾動事件'), 1000)
document.addEventListener('scroll', better_scroll)
50. 手寫call、apply、bind
- 自定義call
在實現(xiàn) call
方法之前,我們先來看一個 call
的調(diào)用示范:
var me = {
name: '張三'
}
function showName() {
console.log(this.name)
}
showName.call(me) // 張三
前面我們說過call
方法是大寫Function
中方法,所有的函數(shù)都可以繼承使用,所以我們自定義call
方法應(yīng)該定義在 Function.prototype
上,這里我們定義一個myCall
。
Function.prototype.myCall=function(){
}
我們想,如果用myCall
方法進(jìn)行綁定,就相當(dāng)于在傳入的對象(這里是me)里面添加了一個原本的函數(shù),然后在使用對象.函數(shù)調(diào)用,也就是:
var me ={
name :'張三',
person:function(){
console.log(this.name)
}
}
me.person()
根據(jù)這個思路,我們往原型對象中添加內(nèi)容:
// context:我們傳入的對象
Function.prototype.newCall = function(context){
// person.newcall調(diào)用,也就是函數(shù).方法調(diào)用,JS中函數(shù)也是對象,所以對象方法調(diào)用,指向該方法所屬對象,也就是person。
// 注意!這里的this是person,我們還沒開始綁定呢
console.log(this)
// 1、我們?yōu)閭魅氲膶ο筇砑訉傩?/span>
context.fnkey = this;
// 2、調(diào)用函數(shù)
context.fnkey();
// 3、執(zhí)行完,方法刪除,我們不能改寫對象
delete context.fnkey
}
person.newCall(me)
復(fù)制代碼
當(dāng)我們?yōu)樾螀⒆兞刻砑訉傩詴r,此時的代碼就如下,然后在調(diào)用這個函數(shù),因為是對象方法調(diào)用所以this
指向了me
,也就是obj
。
function person(){
console.log(this.name);
}
var me = {
name:'張三',
fnkey:function(){
console.log(this.name);
}
}
現(xiàn)在我們的mycall
就實現(xiàn)了call
的基本能力——改變this
指向,第二步讓我們的mycall
具備讀取函數(shù)入?yún)⒛芰?,也就是讀取call
方法第二個到最后一個入?yún)?,這里我們用到ES6中的剩余參數(shù)...args
。
剩余參數(shù)可以幫助我們將不定數(shù)量的入?yún)⒆兂蓴?shù)組,具體用法如下:
function readArr(...args) {
console.log(args)
}
readArr(1,2,3) // [1,2,3]
我們通過args
這個數(shù)組拿到我們想要的入?yún)?,再?args
數(shù)組代表目標(biāo)入?yún)⒄归_,傳入目標(biāo)方法,一個call
方法就實現(xiàn)了。
Function.prototype.myCall = function(context, ...args) {
context.fnkey = this;
context.fnkey(...args);
delete context.fnkey;
}
以上,就實現(xiàn)了mycall
的基本框架~~
但是上面的mycall
還并不完善,比如說第一個參數(shù)傳了null
怎么辦?是不是默認(rèn)給他指到window
或global
上去;第一個參數(shù)不是對象怎么辦?我們改如何保證為對象?如果context
里面有這個屬性怎么辦?我們怎樣保證屬性的唯一性?
我們進(jìn)行以下補充優(yōu)化:
Function.prototype.myCall = function (context, ...args) {
// 補充1 如果第一個參數(shù)沒傳,默認(rèn)指向window / Global
// globalThis瀏覽器環(huán)境中指window,node.js環(huán)境中指向global
if (context == null) context = globalThis
// 補充2:如果第一個參數(shù)傳的值類型,數(shù)字類型,或者布爾類型
// 我們通過new Object 生成一個值類型對象,數(shù)字類型對象,布爾類型對象
if (typeof context !== 'objext') context = new Object(context)
// 補充3:防止傳入對象作為屬性,與context重名屬性覆蓋
// symbol類型不會出現(xiàn)屬性名稱覆蓋
const fnkey = Symbol();
context[fnkey] = this
globalThis // window/global
console.log(new Object('哈哈'));// String {"哈哈"}
console.log(new Object(1)); // Number { 1 }
console.log(new Object(true)); //Boolean { true }
console.log(new Object(undefined));// {}
let symbol1 = Symbol(); //Symbol()
let symbol2 = Symbol(); //Symbol()
consoele.log(symbol1 === symbol2);//false
這樣,我們就實現(xiàn)了完整mycall
方法,使用mycall
調(diào)用時,就相當(dāng)于在傳入的對象里面添加了一個原本的函數(shù),這是實現(xiàn)mycall
的核心,一定要理解。完整版mycall
方法如下:
Function.prototype.myCall = function (context, ...args) {
// 補充1 如果第一個參數(shù)沒傳,默認(rèn)指向window / Global
// globalThis瀏覽器環(huán)境中指window,node.js環(huán)境中指向global
if (context == null) context = globalThis
// 補充2:如果第一個參數(shù)傳的值類型,數(shù)字類型,或者布爾類型
// 我們通過new Object 生成一個值類型對象,數(shù)字類型對象,布爾類型對象
if (typeof context !== 'objext') context = new Object(context)
// 補充3:防止傳入對象作為屬性,與context重名屬性覆蓋
// symbol類型不會出現(xiàn)屬性名稱覆蓋
const fnkey = Symbol();
// step1: 給傳入對象添加原函數(shù)(this就是我們要改造的原函數(shù))
context[fnkey] = this
// step2: 執(zhí)行函數(shù),并傳遞參數(shù)
context[fnkey](...args)
// step3: 刪除 step1 中掛到目標(biāo)對象上的函數(shù)
delete context[fnkey].
}
// 測試如下:
function showFullName(secondName) {
console.log(`${this.name} ${secondName}`)
}
var me = {
name: '張三'
}
showFullName.myCall(me, '李四') // 張三 李四
showFullName.myCall(null, '李四') // 李四
showFullName.myCall(1, '李四') // undefined 李四
理解了call
,那么實現(xiàn)apply
和bind
方法就小菜一碟了,apply
方法關(guān)鍵在于更改參數(shù)的讀取方式,bind
方法關(guān)鍵在于延遲目標(biāo)函數(shù)的執(zhí)行時機。
- 自定義apply
Function.prototype.myCall = function (context, ...args) {
if (context == null) context = globalThis
if (typeof context !== 'objext') context = new Object(context)
const fnkey = Symbol();
context[fnkey] = this;
// 此時,傳入的數(shù)組,不需要對數(shù)組進(jìn)行拆包
context.fnkey(args);
delete context[fnkey];
}
// 測試如下:
function showFullName(secondName) {
console.log(`${this.name} ${secondName}`)
}
var me = {
name: '張三'
}
showFullName.myCall(me, ['李四','王五']) // 張三 李四 王五
- 自定義bind
前面我們說過,bind
方法不會立即執(zhí)行函數(shù),實際上bind
方法是返回了一個原函數(shù)的拷貝,函數(shù)體內(nèi)的參數(shù)會和bind
方法第一個以外的其他參數(shù)合并。
在實現(xiàn) bind
方法之前,我們先來看一個 bind
的調(diào)用示范:文章來源:http://www.zghlxwxcb.cn/news/detail-513265.html
var me = {
value: 1
}
function person(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
var bar = person.bind(me, '張三', 18);
console.log(bar);
// 這里將會輸出person函數(shù)
console.log(bar());
// {value: 1, name: "張三", age: 18}
var bar2 = person.bind(me, '張三');
console.log(bar2(18));
// {value: 1, name: "張三", age: 18}
完整版myBind
如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-513265.html
Function.prototype.myBind = function (context, ...args) {
// step1: 保存下當(dāng)前 this(這里的 this 就是我們要改造的的那個函數(shù))
const self = this;
// step2: 返回一個函數(shù)
// bind整體上會return一個函數(shù),并還可以接受參數(shù)
return function (...argus) {
// step3: 拼接完整參數(shù),將bind執(zhí)行參數(shù)和函數(shù)調(diào)用時傳入?yún)?shù)拼接
const fullArgs = args.concat(argus)
// step4: 調(diào)用函數(shù)
return self.apply(context,fullArgs)
}
}
// 測試如下:
function showFullName(secondName) {
console.log(`${this.name} ${secondName}`)
}
var me = {
name: '張三'
}
var result = showFullName.myBind(me, '李四')
result() // 張三 李四
到了這里,關(guān)于50個知識點由淺入深掌握J(rèn)avascript的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!