那些陌生又熟悉的前端面試題
過完年需要跳槽的小伙伴還是挺多的,又要開始刷前端面試題了!會(huì)不會(huì)有一種錯(cuò)覺,看著這道面試題很熟,但是不知道該如何做?或者有答案又不知道是否正確?或者使用編輯器可以運(yùn)行出來正確的答案,但是不知道怎么得來的,這些你都中招了嗎?
1、嚴(yán)格模式與非嚴(yán)格模式的區(qū)別,你了解多少?
JavaScript 語言是一門弱類型語言,存在許多類型錯(cuò)誤,因此 ES6 引入了嚴(yán)格模式概念。
如果不加 ‘use strict’ 常規(guī)模式下就是屬于非嚴(yán)格模式。
嚴(yán)格模式
在 js 文件頂部添加 ‘use strict’ 就屬于嚴(yán)格模式,嚴(yán)格模式也可以指定在函數(shù)內(nèi)部。
<script>
'use strict'
//或者函數(shù)內(nèi)部
(function(){
'use strict'
})()
</script>
嚴(yán)格模式,是為 js 定義來了一種不同的解析與執(zhí)行模型,在嚴(yán)格模式下,ECMAScipt 3 中一些不解和不確定的行為將得到處理,而且會(huì)對不安全的操作會(huì)拋出異常?!畊se strict’ 會(huì)告訴瀏覽器引擎可以切換到嚴(yán)格模式執(zhí)行。
嚴(yán)格模式與非嚴(yán)格模式區(qū)別
2、深淺拷貝的區(qū)別有哪些?
常見筆試題:
var person = {
name:"前端人",
hobby:['學(xué)習(xí)','敲代碼','潛水']
}
function copy(source){
var newObj = new Object()
for(var i in source){
if(source.hasOwnProperty(i)){
newObj[i] = source[i]
}
}
return newObj
}
var p1 = copy(person);
p1.name = "Web Person"
console.log(person.name)
console.log(p1.name)
p1.hobby = ["內(nèi)卷"]
console.info(person.hobby)
console.info(p1.hobby)
/*運(yùn)行結(jié)果:
前端人
Web Person
["學(xué)習(xí)", "敲代碼", "潛水"]
["內(nèi)卷"]
*/
試試這道筆試題你會(huì)做嗎?
要說 js 的深淺拷貝,就不得不提 js 的兩大數(shù)據(jù)類型:基本數(shù)據(jù)類型和引用類型。
基本數(shù)據(jù)類型的變量名和值都存儲(chǔ)在棧中,對于引用類型的變量名存儲(chǔ)在棧中,而值存儲(chǔ)在堆中。由于存儲(chǔ)方式不同,所以導(dǎo)致了他們復(fù)制的時(shí)候方式不同。
賦值
基本數(shù)據(jù)類型賦值的時(shí)候,創(chuàng)建的基本數(shù)據(jù)類型會(huì)在內(nèi)存中開辟一個(gè)新空間把值復(fù)制過來,而引用類型采用的是地址存儲(chǔ),如果直接把一個(gè)引用數(shù)據(jù)直接賦值給另外一個(gè)數(shù)據(jù),就相當(dāng)于直接把自己存儲(chǔ)值的地址給了另外一個(gè)變量,所以改變一個(gè)的值,也會(huì)改變另外一個(gè)的值。
var a = 1;
var b = a;
b=2;
console.log(a) //1
console.log(b)//2
var p1 = {name:"前端人"}
var p2 = p1
p2.name = "打工仔"
console.log(p1.name) // '打工仔'
console.log(p2.name) // '打工仔'
深淺拷貝是如何定義的?
假設(shè)有 p 和 copyP 兩個(gè)變量,如果copyP 是拷貝了 p 的,我們通過修改 copyP 來觀察 p 是否發(fā)生改變,如果跟著改變,就是淺拷貝,如果是不改變,就說明是深拷貝。
基本類型復(fù)制的時(shí)候會(huì)開辟新的內(nèi)存空間,所以兩個(gè)值是相互獨(dú)立的,引用類型復(fù)制的時(shí)候就要看是復(fù)制的內(nèi)存地址還是復(fù)制一個(gè)新的堆。所以深拷貝主要針對的是引用類型的數(shù)據(jù)。
淺拷貝的常見的方式:
1、直接賦值
var arr1 = [1,2,3,4];
var arr2 = arr1;
2、Object.assign
var obj = {
a:1,
b:2
}
var o = Object.assign(obj)
深拷貝的常見方式:
引用數(shù)據(jù)類型最常用的就是 Object 和 Array ,引用數(shù)據(jù)內(nèi)部的數(shù)據(jù)也可以是多樣化的,進(jìn)行深拷貝時(shí),也要適當(dāng)?shù)馗鶕?jù)數(shù)據(jù)結(jié)構(gòu)進(jìn)行合適的復(fù)制方式,具體的深拷貝方法分別有:
1、數(shù)組中只包含基本數(shù)據(jù)類型
- 循環(huán)數(shù)組,將數(shù)組的值復(fù)制出來放入另一個(gè)新數(shù)組中
- 利用 slice 方法
- 借助 concat 方法
- 利用 from 方法
- 使用擴(kuò)展符 ...
2、對象中只包含基本數(shù)據(jù)類型
- 利用 for in 循環(huán),將對象的值拿出來
- 使用 Object 復(fù)制給一個(gè)新的空對象
- 使用 ... 擴(kuò)展運(yùn)算符
- 手動(dòng)復(fù)制,將屬性值一個(gè)一個(gè)單獨(dú)復(fù)制
3、對象或數(shù)組里含有一層或多層引用數(shù)據(jù)類型時(shí)
- 使用 jQuery 的 extend 方法
- JSON.parse(JSON.stringify())
- 使用遞歸自己寫一個(gè)深拷貝的方法
深淺拷貝的常見應(yīng)用主要是數(shù)據(jù)的增刪改操作。
3、this 的指向
大廠筆試題:
var name = 'window name'
var p1 = {
name:'p1 name',
showName:function(){
console.info(this.name)
}
}
var fn = p1.showName
fn()
p1.showName()
var p2 = {
name:'p2 name',
showName:function(fun){
fun()
}
}
p2.showName(p1.showName)
p2.showName = p1.showName
p2.showName()
/*
運(yùn)行結(jié)果:
window name
p1 name
window name
p2 name
*/
這是一道關(guān)于 this 指向的面試題,接下來我們就說說 this 是如何指向的?
this 對象是運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定的:
- 在全局函數(shù)中,this 等于 window 。
- 函數(shù)上下文調(diào)用,嚴(yán)格模式下 this 為 undefined ,非嚴(yán)格模式下,this 指向 window 。
- 當(dāng)函數(shù)被作為某個(gè)對象的方法被調(diào)用時(shí),this 等于那個(gè)對象。如果使用 call apply 改變當(dāng)前 this 時(shí),將會(huì)指向?yàn)閭鬟f過來的那個(gè) this 。
- 匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此 this 指向 window。
- 構(gòu)造函數(shù)內(nèi)的 this 指向創(chuàng)建的實(shí)例對象。
- dom 事件處理函數(shù),this 指向觸發(fā)該事件的元素。
- setTimeout 和 setInterval 中的 this 指向全局變量 window
看完上述 this 指向解釋,你就可以做上邊的那道面試題了。
如何改變 this 的指向?
call 、bind 和 apply 這三個(gè)函數(shù)都是用來改變 this 指向的,就是改變函數(shù)執(zhí)行時(shí)的上下文。
修改上述面試題:
var name = 'window name'
var p1 = {
name:'p1 name',
showName:function(){
console.info(this.name) // p2 name
}
}
var p2 = {
name:'p2 name',
}
p1.showName.call(p2)
p1.showName.apply(p2)
var bind = p1.showName.bind(p2)
bind()
call 、bind 和 apply 改變 this 指向,最大作用就是實(shí)現(xiàn)代碼復(fù)用。
至于 call、bind 和 apply 的區(qū)別,可以自行去了解下。
4、隱式轉(zhuǎn)化
console.log( '2'>10 ) //false
console.log( '2'>'10' ) //true
console.log( 'abc'>'b' ) //false
console.log( 'abc'>'aab' ) //true
console.log( undefined == null ) //true
console.log( NaN == NaN )//false
console.log( [] == 0 ) //true
console.log( ![] == 0 ) //true
console.log( [] == [] ) //false
console.log( {} == {} ) //false
console.log( {} == !{} ) //false
對象的類型轉(zhuǎn)換表
有了上邊那個(gè)表,事情就變得簡單了!
關(guān)系運(yùn)算符進(jìn)行運(yùn)算時(shí),不同類型的值會(huì)自動(dòng)轉(zhuǎn)化為相同類型值,然后進(jìn)行
1、兩邊有一個(gè)是字符串一個(gè)是是數(shù)字時(shí),字符串調(diào)用 Number 方法,將字符串轉(zhuǎn)為數(shù)字,所以:
console.log( '2'>10 ) => console.log( 2>10 )
2、如果兩邊都是字符串時(shí),按照字符串的 unicode 編碼來轉(zhuǎn)換的,所以:
'2'.charCodeAt() = 50
'10'.charCodeAt() = 49
console.log( '2'>'10' ) => console.log( 50 >49 )
3、字符串進(jìn)項(xiàng)比較時(shí),先比較第一位,如果不相等直接得出結(jié)果,如果第一項(xiàng)相等,會(huì)繼續(xù)使用第二項(xiàng)進(jìn)行比較。
console.log( 'abc'>'b' ) // a < b 所以為 false
console.log( 'abc'>'aab' ) // a=a 第二位 b>a 所以為 true
4、轉(zhuǎn)為布爾值都為 false 的類型分別有:undefined 、null 、0、NaN、false、‘’
console.log( undefined == null ) //true
5、NaN表示的是非數(shù)字,但是這個(gè)非數(shù)字也是不同的,因此 NaN 不等于 NaN,兩個(gè)NaN永遠(yuǎn)不可能相等。
console.log( NaN == NaN )//false
6、關(guān)系運(yùn)算有一個(gè)數(shù)值,將另外一個(gè)值也轉(zhuǎn)為 number 類型。
Number([].valueOf().toString()) = 0
console.log( [] == 0 ) => console.log( 0 == 0 ) //true
7、有邏輯運(yùn)算的,將其他數(shù)據(jù)類型轉(zhuǎn)為 boolean 值。
Boolean([]) = true => 取反 true
console.log( ![] == 0 ) => console.log( false == false ) //true
8、直接使用兩個(gè)空數(shù)組比較,數(shù)組地址不同,所以不相等。
console.log( [] == [] ) //false
// 對象地址不一樣
console.log( {} == {} ) //false
{}.valueOf().toString() ="[object Object]"
console.log( {} ==