前兩天電話面試了一個公司的前端,差不多問題都能回答出一點,但是一旦向下深挖,就不會了,還是自身基礎打得不夠啊,怕以后面試還是會遇到這些個問題,所以就覺得把面試官問我的,我回答不上來的,且現在還記得問題記錄一下,方便以后鞏固復習,順便分享給面試的小伙伴
1. 深度拷貝和淺度拷貝了解過嗎,是否可以說一下
當時回答:了解過,淺度拷貝的話,拷貝的如果是基本數據類型的話,他就會原樣拷貝,如果拷貝的時引用數據類型,比如說對象,數組,函數這類的,他只能拷貝他的應用地址,所以拷貝完成,修改引用數據類型里面的數據時,原來的也會發生修改(當時有點緊張所以說的不是很清楚,再解釋一下,a對象里面又有基本數據類型和引用數據類型,拷貝a得到b,修改b里面的引用數據類型的數據時,a也會發生變化),深度拷貝的話,他會拷貝的更加徹底,當他拷貝的時引用數據類型的數據,他會遞歸向下繼續拷貝,直到拷貝的數據都是基本數據類型。
面試官繼續問:你剛才提到了遞歸,那你能說一下,深度拷貝的遞歸出口是什么?
問道這個問題時,心里暗喜,我可是敲過深度拷貝的代碼的,但是還是因為緊張,想不起來了,哎,果然面試總歸會緊張,想了一會后終於想起來了,判斷這個數據是不是引用數據類型,如果不是,結束遞歸,✌✌✌,面試官繼續問:當深度拷貝中出現兩個對象相互引用,該如何處理?你懂的,我傻了,相互引用????????我咋從來沒有想過這個問題呢,想了一會,告訴面試官,我們是不是可以判斷這兩個數據的地址是不是相等的是不是就好了,面試官:可是我不知道,哪兩個對象相互引用,你怎么去判斷地址是否相等呢 ?,完了完了,我已經徹底傻了...
面試完查的資料:可以使用一個WeakMap結構存儲已經被拷貝的對象,每一次進行拷貝的時候就先向WeakMap查詢該對象是否已經被拷貝,如果已經被拷貝則取出該對象並返回。
//判斷是數組還是對象
function getType(target){
// console.log(Object.prototype.toString.call(target).slice(8,-1))
return Object.prototype.toString.call(target).slice(8,-1)
}
function cloneUtil(target,hasObj = new WeakMap()){
let result;
// 判斷數據類型
if(getType(target) === 'Array'){
result = []
} else if(getType(target) === 'Object'){
result = {}
} else {
return result
}
for (let i in target) {
let item = target[i]
//當遍歷的數據中仍然有數組/對象時,重新按照之前的步驟 進行拷貝,直到所有拷貝數據的類型都是基本數據類型
if(getType(item) === 'Object' || getType(item) === 'Array'){
//判斷hasObj中是否已經拷貝過該對象,如果已經被拷貝則取出該對象並返回。
if(hasObj.has(item)) return hasObj.get(item)
hasObj.set(item,{})
let cloneitem = cloneUtil(item,hasObj)
result[i] = cloneitem
} else{
result[i] = item
}
}
return result
}
let obj = {
name:'Bob',
age:22,
sex:{
option1:'男',
option2:'女'
}
}
let cloneObj = cloneUtil(obj)
cloneObj.sex.option1 = 'xxx'
obj.obj = obj
console.log(obj)
console.log(cloneObj)
2. vue項目中你用了ui框架,比方說element-ui,你要修改ui框架提供的組件樣式的時候,你會怎么去操作
當時回答:vue的style標簽中,會添加一個scoped屬性,防止樣式的污染,但是你要修改element-ui組件的話,再帶有scoped屬性的style標簽中去修改,是不會達到想要的結果,vue組件是可以有多個style標簽的,所以當你要修改element-ui組件的樣式時,可以添加一個沒有scoped的style,就可以修改組件樣式。
面試官繼續追問:那我就是想要在有scoped屬性的style標簽里面去修改,那要怎么處理?
面試官這個問的時候,我頓時emmm傻了,仔細想來了想,記得如果項目用來stylus,less這樣的預處理器,可以用樣式穿透來實現,我就這么回答了,但是本人沒有實踐過,所以還是有點慌慌的,面試完之后就去查了資料。資料顯示,我的回答是對的,舉個例子把,看一下到底怎么穿透。
<style lang="stylus" scoped>
.a >>> .b {
/* ... */
}
</style>
//或者
<style lang="scss" scoped>
.homePage /deep/ .el-main {
padding: 0;
}
3. call,apply了解過嗎,能自己實現一個call,apply嗎,還有其他什么立即綁定的函數嗎
當時回答:了解過,call,apply都是更改this指向的,然后其他的我都沒回答出來,后來面試完之后想想其他立即綁定還有bind啊,哎,傻了傻了
補完知識:
let person ={
name: 'bob',
age: 18
}
function student (){
console.log(this.name,this.age,sex,like)
}
student.call(person,'nan','eat')
//call,apply的原理相當於,把上面的兩個合並成立這個下面一個
let person = {
name: 'bob',
age: 18,
fn:function student (){
console.log(this.name,this.age,sex,like)
}
}
person.fn('nan','like')
Function.prototype.myCall = function (context = window){//如果傳入context沒有賦值,就賦默認值window
if(Object.prototype.toString.call(context) !== '[object Object]'){
console.log('不是對象')
return
}
let fn = Symbol('fn')//產生一個唯一不重復的值,以免沖突
context[fn] = this//這里的this,是調用了myCall的函數,比方說a.mayCall(),那a就是這里的this
let args = [...arguments].slice(1)//這里是傳值
let result = context[fn](...args)//運行這個函數
delete context[fn] //刪除context上的方法
return result
}
Function.prototype.myApply = function (context = window){
if(Object.prototype.toString.call(context) !== '[object Object]'){
console.log('不是對象')
return
}
let fn = Symbol('fn')
context[fn] = this
let args = arguments[1]
let result = context[fn](...args)
delete context[fn]
return result
}
let person = {
name: 'bob',
age: 18
}
function student(sex,like){
console.log(this.name,this.age,sex,like)
}
student.myCall(person,'nan','eat')
student.myApply(person,['nan','eat'])
4.因為之前問了事件循環機制是怎么樣子操作的,當時回答的時候,說了js單線程的,然后就有了這個問題,js如果不是單線程會怎么樣?后面再回答es6里面除了let,const新增的,es6里面還有什么你記得的內容嗎,當時中間有回答了async 和 promise,所以,js單線程,又出了一道問題,promise在js單線程是怎么運行過程(突然描述不清楚了,大概就是這個意思)?
當時回答:js如果不是單線程的話,對dom會有影響,但是具體什么影響我記不清楚了,然后面試官就把答案告訴我了:如果js被設計了多線程,多個線程都要操作一個dom元素,如果有一個線程要修改一個dom元素,另一個線程要刪除這個dom元素,這時候就會產生問題。
下面的那個promise 在js上運行過程我又沒說出來,哎,面試完之后躺在床上好好的想了下,promise里面不就是裝異步函數的么,啥ajax請求不是都是放里面的么,那不就是js運行到這邊的時候,會交給對應的管理模塊管理,滿足條件之后,進入回調隊列,然后,js主線程上的同步代碼執行完成之后,通過事件輪詢機制,看回調隊列里是否又東西,有的話就把他鈎到主線程上去運行,嗯,當時人傻已經實錘了,換種問法,問事件循環機制就不知道怎么說了,所以啊,小伙伴們,記得面試的時候要冷靜,冷靜,冷靜,雖然我面試的時候也這么跟我自己說的🙃🙃🙃
這兩天在家好好的看了一下關於promise的內容,針對於這個問題,做出相應的補充
我們知道js運行代碼時,都是從上到下執行,當遇到異步任務的操作就會交給相應的處理異步任務的模塊,然后,異步任務符合結果才會進入回調隊列,但是回調隊列又分為宏隊列和微隊列(這是我之前不知道的),像ajax,定時器等滿足要求會進入宏隊列,但是像promise滿足要求后是會進入到微隊列當中,當要執行宏隊列中的異步任務時,他會先詢問微隊列中的異步任務是否清空,如果清空,他就會執行宏隊列中的異步任務,舉個例子把,比如說宏隊列中有1,2,3三個異步任務,微隊列中此時只有4,5兩個異步任務,當執行1時,會先把4,5兩個執行掉,再執行1,執行1的過程中,又有6,7進入微隊列,那么等1執行完想執行2時,他又會去問微隊列中是否還有要執行的異步任務,此時6,7再微隊列中,所以他會先把6,7執行掉,然后在執行2,2執行完之后,他又會繼續去問微隊列是否有任務,此時微隊列中沒有新的任務,所以他回去執行宏隊列中的3
5. 隱式類型轉換,2 == {a:2} 會出現什么結果,判斷過程是怎么樣的
當時回答:emmmmmmmmm了半天,還是沒回答出來🤦🤦🤦
補完知識的答案:左邊是數字右邊是對象進行比較,會將不是數字的一方轉化為數字然后進行比較,但是像對象,數組這樣引用類型的數據的轉換比較復雜,他會先將對象通過valueOf獲取原始值,然后在對原始值進行toString轉化成字符串,然后對字符串轉化為Number,然后在與數組進行比較,所以{a:2}通過valueof之后就會變成[object Object],在經過toString的處理就變成"[object Object]",在Number("[object Object]") 就會變成NaN,就是不是數字的數字,最后就變成 2==NaN 的比較了,顯然是false
https://blog.csdn.net/itcast_cn/article/details/82887895 這篇博客寫隱式類型轉換的坑,寫的很棒,大家感興趣的話,可以去了解一下
6. 圖片懶加載原理
這個之前沒有去了解過,所以當時沒有回答出來,面試完后補了知識之后的答案:將img標簽中的src指向一個loading圖片地址,然后定義一個data-src屬性(他有個data-屬性 - 后面可以隨便取名字,我這取的是data-src)指向真實的圖片地址,根據頁面的可視高度和滑輪滾動的高度與圖片的高度進行比較,如果圖片快要進入可視區域,就將src中的地址換成data-src中的地址,從而達到圖片懶加載的效果
//窗口可視大小兼容性方法
function getViewportSize(){
if(window.innerHeight){
return {
height:window.innerHeight,
width:window.innerWidth
}
}else{
if(document.compatMode === 'BackCompat'){
return {
height: document.documentElement.clientHeight,
width: document.documentElement.clientLeft,
}
}else{
return {
height: document.body.clientHeight,
width: document.body.clientLeft
}
}
}
}
//滾動大小兼容性方法
function getScrollOffset(){
if(window.pageXOffset){
return {
top: window.pageYOffset,
left: window.pageXOffset
}
}else{
return {
top: document.body.scrollTop + document.documentElement.scrollTop,
left: document.body.scrollLeft + document.documentElement.scrollLeft
}
}
}
//獲取當前所有的img
let imgs = document.getElementsByTagName('img')
let len = imgs.length
//獲取窗口高度
let windowHeight = getViewportSize().height
//滾動事件
function lazyload(){
//獲取滾動高度
let scrollHeight = getScrollOffset().top
for(let i = 0; i<len; i++){
//判斷照片是不是快要顯示
if(imgs[i].offsetTop < windowHeight + scrollHeight){
//判斷是不是默認圖片
if(imgs[i].getAttribute('src') == "./img/1.jpg"){
//將真實圖片地址替代默認圖片地址
imgs[i].setAttribute('src',imgs[1].getAttribute('data-src'))
}
}
}
}
//頁面剛打開的時候,沒有滾動,所以不會執行lazyload方法,所以要提前執行一次
lazyload()
//綁定滾動事件
window.onscroll = lazyload
7. 用const定義常量是否可以修改
當時回答:應該不可以吧(虛虛的),面試完補了知識之后的答案:const定義的是基本類型修改會報錯,但是定義的是引用數據類型就不會報錯,比如說對象,數組之類的,原因是因為,基本數據類型他都是存在棧中,const不能修改棧上的內容,但是引用數據類型,棧上只是保存了指向對象或者數組的指針,其他的屬性啊或者是數組里面的數據都是放在堆上的,所以const可以修改堆上的內容,也就是const是可以修改對象的屬性、數組的內容,記得const定義的常量是不可以重復定義的哦
8. 可以描述一下瀏覽器渲染網頁嗎
當時回答:瀏覽器渲染html,會先將html渲染成dom樹,css被渲染成css樹,dom樹和css樹合並到一起之后就會變成一個render樹,之后就會計算dom的大小和位置稱之為回流,之后會將計算后的dom位置大小樣式渲染到屏幕上,這稱之為重繪。
面試官追問:怎么樣會導致回流與重繪?emmm之前看過,所以就只有簡單的回答出來改變樣式,dom節點會導致,但是時間有點久遠詳細的就說不出來了,查完資料之后的回答:回流一定會造成重繪,但是重繪不一定回流。頁面的首次渲染,瀏覽器窗口大小的改變,dom元素的變化,就是操作會導致文檔流發生改變的,比如說新增加dom元素,刪除dom元素,或者某個dom元素的大小發生改變,文本節點發生改變,字體大小發生改變等等都會導致回流。當你修改的樣式不會對文檔流發生改變,就不會發生回流,比如說,顏色改變,visibility,圓角,陰影什么的都是不會造成回流只會重繪。
http://www.imooc.com/article/45936這個對於回流重繪寫的挺好的,大家感興趣的化可以看一下
9.NaN知道是什么嗎,是否可以說一下Number.isNaN和isNaN的區別
當時回答:NaN是判斷是不是個數字,NaN === NaN返回false
資料補充:NaN是判斷是不是數字,但是因為NaN不等於NaN,所以需要有一個判斷這個東西是不是NaN,所以出現了一個isNaN()來判斷,isNaN(NaN)返回為true,但是isNaN()會將字符串,或者數組,對象都會都認為是不是數字,所以返回true,Number.isNaN()是用來判斷是不是真的為NaN的,其他的字符串,數組,數字都會返回為false只有Number.isNaN(NaN)才會返回true
10.base64與md5的區別以及應用場景
當時回答:不知道具體區別,應用場景:base64可以用於將圖片轉化為base64字符串進行上傳,md5主要用於登錄,對密碼按照一定的規則進行加密
資料補充:base64主要是通過encode進行加密,所以他是可以用decode解密,一般用於當圖片過大時,可以采用base64加密,從而壓縮圖片的大小,進行上傳,md5是一種被廣泛使用的密碼散列函數,可以產生出一個128位(16字節)的散列值(hash value),用於確保信息傳輸完整一致,一旦加密后不能解開,所以一般用於密碼,或者向服務器進行通訊時,雙方可以按照相同的規則,對一個東西進行加密,對比兩者是否一致,從而防止內容篡改
11.什么是閉包以及他的應用場景
js中內部函數通過作用域鏈可以獲取外部函數的變量,例如:
function f1() {
var a = 1
console.log('f1:',a)
}
f1()
console.log(a) //報錯,因為a是f1的私有變量,外部不能獲取
但是外部函數想訪問內部函數的變量,是會報錯的,所以閉包相當於是外部函數和內部函數的一個橋梁
function f1() {
var a = 1
return function (){
console.log(a) //f2通過作用域鏈可以訪問f1中的變量
}
}
var b = f1()
b() //外部訪問到了變量a
形成閉包的條件:1.函數嵌套 2.內部函數使用外部函數的局部變量 3.執行外部函數
主要作用:延長外部局部變量的生命周期,使得從外部能訪問到函數內部的變量
應用場景:
- 返回值:外部需要訪問函數內部的變量
- 函數參數
function f2(num){
console.log(num)
}
function f1(){
var a = 1
function f3(){
return a
}
var b = f3()
f2(b)
}
f1()
- 函數賦值
var num;
function f1(){
var a = 1
function f2(){
return a
}
num = f2()
}
f1()
console.log(num)
- 立即執行函數(IIFE)
- 循環賦值
function f(){
var arr = []
for(var i = 0; i < 5; i++){
(function(i){
arr[i] = function(){
return i
}
})(i);
}
return arr
}
var a = f()
console.log(a[1]())
- 對私有變量進行修改
var getName, setName;
function fn(){
var name = 'aaa'
setName = function(newName){
name = newName
}
getName = function (){
return name
}
}
fn()
setName('bbb')
console.log(getName())
- 計數器
//計數器
var add = function (){
var num = 0
return function (){
return num++
}
}()
console.log(add())
console.log(add())
12.webpack的原理
webpack的配置文件我自己寫過,但是讓我去說他的原理還真是說不太出來,所以我這邊找了一個寫的挺好的文章,可供大家閱讀
https://www.cnblogs.com/chengxs/p/11022842.html
13. scoped的原理
Vue中的scoped屬性的效果主要通過PostCSS轉譯實現,轉譯前的Vue代碼:
<div class="box">
hello world!!!
</div>
<style scoped>
.box{
height: 100px;
width: 100px;
background-color: rgb(35, 85, 85);
}
</style>
轉譯后的代碼:
<div data-v-431182d6 class="box">
hello world!!!
</div>
.box[data-v-431182d6] {
height: 100px;
width: 100px;
background-color: rgb(35, 85, 85);
}
說明:從上面的代碼,我們不難看出postcss給所有的都沒添加了一個隨機且不重復的data-屬性,css選擇器添加了一個屬性選擇器來選擇這個dom,從而達到每個dom節點的樣式私有化不會相互污染
14.數組扁平化(除了用flat方法)
方法1
let arr = [1,[2,3,[4,5,[6],7,8],9],0]
let newArr = []
//可以將多層數組嵌套數組的數組扁平化
function flatOne(array){
array.map(item => {
//判斷遍歷的每一項是否是數組,是的話遞歸,不是的話就把數組push進入newArr
Array.isArray(item) ? flatOne(item) : newArr.push(item)
})
return newArr
}
console.log(flatOne(arr))
方法2
function flatTwo(array){
return [].concat(
...array.map(item => {
return (Array.isArray(item) ? flatTwo(item) : item)
})
)
}
console.log(flatTwo(arr))
方法三
function flatThree(array){
let newArr = []
//將數組轉化成字符串
//於數組對象,toString 方法連接數組並返回一個字符串,其中包含用逗號分隔的每個數組元素
array.toString().split(',').forEach(element => {
//將字符串形式的數字轉化為數字類型的數字
newArr.push(parseInt(element))
});
return newArr
}
console.log(flatThree(arr))
15.restful標准特點
資源、統一接口、URI和無狀態
詳細的介紹可以看這里https://www.jianshu.com/p/65ab865a5e9f
16.用es6標准實現將999999999轉變成999,999,999
我也沒理解什么叫es6標准轉化成9,999,999,999所以我就按照我自己的想法來寫了這題
思路:把數字變成字符串,然后每三個加一個逗號
let a = 9999999999,
b = '';
a = a.toString()
//9,999,999,999 算出第一個分號前有幾個數組
let flag = a.length % 3
//a是偽數組所以不能通過forEach遍歷
//需要通過slice傳一個數組/集合,回這個集合的原理,拿到的也是數組,這樣就可以使用數組的所有方法了
Array.prototype.slice.call(a).forEach((element,index) => {
b = b + element
//(a.length-1) != index 如果數字的位數湊巧會導致數字最后也加上一個逗號,所以我們要把這種情況考慮到
if((index+1-flag) % 3 == 0 && (a.length-1) != index){
b = `${b},`
}
});
console.log(b)
17. 數據結構,鏈表出現環要怎么處理
還有17這個問題先放在這怕忘記,知識點還沒補上,所以暫時就不寫答案了,或者大家有什么好的方法也可以告訴我,理解之后會寫上去的哦
所有的面試都已經結束了,大大小小加上電話面試下來也有十幾家公司,其中有自己喜歡的公司,自己很幸運也被喜歡的公司發了offer,上面的一些問題都是一些面試中我回答不出來的一些問題,還有些記不住了,就沒有寫上去,到時候想起來的話,還會寫上去的,上面如果寫的有問題的,歡迎大家來指正✌✌✌🎉🎉🎉🤞🤞🤞🌹🌹🌹