最近看到有一篇文章總結了一些前端的面試題,面向的對象應該是社招中初、中級的前端,感受有必定的參考價值,所以開一個帖子嘗試解答這些問題,順便當作本身的面試題積累。javascript
原文連接戳這裏css
在網上找到一篇文章,裏面有一道面試題,考察了包括變量定義提高、this指針指向、運算符優先級、原型、繼承、全局變量污染、對象屬性及原型屬性優先級等許多知識點,而就其中聲明提早相關的知識,我以爲也十分有參考價值:html
function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}
// 請寫出如下輸出結果:
Foo.getName();
getName(); // 聲明提早
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
複製代碼
這道題的答案是:二、四、一、一、二、三、3。前端
這裏考察聲明提早的題目在代碼中已經標出,這裏聲明getName方法的兩個語句:vue
var getName = function () { alert (4) };
function getName() { alert (5) }
複製代碼
實際上在解析的時候是這樣的順序:java
function getName() { alert (5) }
var getName;
getName = function () { alert (4) };
複製代碼
若是咱們在代碼中間再加兩個斷點:git
getName(); // 5
var getName = function () { alert (4) };
getName(); // 4
function getName() { alert (5) }
複製代碼
在第一次getName時,function的聲明和var的聲明都被提早到了第一次getName的前面,而getName的賦值操做並不會提早,單純使用var的聲明也不會覆蓋function所定義的變量,所以第一次getName輸出的是function聲明的5; 而第二次getName則是發生在賦值語句的後面,所以輸出的結果是4,因此實際代碼的執行順序是這樣:es6
function getName() { alert (5) }
var getName;
getName(); // 5
getName = function () { alert (4) };
getName(); // 4
複製代碼
共同點:都是保存在瀏覽器端、僅同源可用的存儲方式github
不久我寫了一個帖子,對同源策略及各類跨域的方式進行了總結:什麼是跨域,爲何瀏覽器會禁止跨域,及其引發的發散性學習面試
Promise是ES6加入的新特性,用於更合理的解決異步編程問題,關於用法阮一峯老師在ECMAScript 6 入門中做出了詳細的說明,在此就不重複了。
上面這篇文章則是對Promise的原理進行的詳細的說明,在這裏,我提取最簡單的Promise實現方式來對Promise的原理進行說明:
function Promise(fn) {
var value = null,
callbacks = []; // callbacks爲數組,由於可能同時有不少個回調
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
複製代碼
首先,then
裏面聲明的單個或多個函數,將被推入callbacks
列表,在Promise實例調用resolve
方法時遍歷調用,並傳入resolve
方法中傳入的參數值。
如下,使用一個簡單的例子來對Promise的執行流程進行分析:
functionm func () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve('complete')
}, 3000);
})
}
func().then(function (res) {
console.log(res); // complete
})
複製代碼
func
函數的定義是返回了一個Promise實例,聲明實例時傳入的回調函數加入了一個resolve
參數(這個resolve
參數在Promise中的fn(resolve)
定義中獲取resolve
的函數實體),回調中執行了一個異步操做,在異步操做完成的回調中執行了resolve
函數。
再看執行步驟,func
函數返回了一個Promise實例,實例則能夠執行Promise構造函數中定義的then
方法,then
方法中傳入的回調則會在resolve
(即異步操做完成後)執行,由此實現了經過then
方法執行異步操做完成後回調的功能。
原文中貼出的文章具備很大參考價值,先貼個連接:詳解JavaScript中的Event Loop(事件循環)機制。
JavaScript是一種單線程、非阻塞的語言,這是因爲它當初的設計就是用於和瀏覽器交互的:
JavaScript
設計爲單線程的緣由是,最開始它最大的做用就是和DOM
進行交互,試想一下,若是JavaScript
是多線程的,那麼當兩個線程同時對DOM
進行一項操做,例如一個向其添加事件,而另外一個刪除了這個DOM
,此時該如何處理呢?所以,爲了保證不會 發生相似於這個例子中的情景,JavaScript
選擇只用一個主線程來執行代碼,這樣就保證了程序執行的一致性。pending
)這個任務,而後在異步任務返回結果的時候再根據必定規則去執行相應的回調。而JavaScript
實現異步操做的方法就是使用Event Loop。setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
複製代碼
下面經過一段代碼來分析這個問題,首先setTimeout
和Promise
中的then
回調都是異步方法,而new Promise
則是一個同步操做,因此這段代碼應該首先會當即輸出2
;JavaScript
將異步方法分爲了marco task
(宏任務:包括setTimeout
和setInterval
等)和micro task
(微任務:包括new Promise
等),在JavaScript
的執行棧中,若是同時存在到期的宏任務和微任務,則會將微任務先所有執行,再執行第一個宏任務,所以,兩個異步操做中then
的回調會率先執行,而後才執行setTimeout
的回調,所以會依次輸出三、1,因此最終輸出的結果就是二、三、1。
這個問題阮一峯老師在ECMAScript 6 入門中的let 和 const 命令
章節對這個問題做出了詳細的說明,下面提取一些我認爲關鍵的點進行講解。
ES6引入了使用{}
包裹的代碼區域做爲塊級做用域的聲明方式,其效果與ES5中function
聲明的函數所生成的函數做用域具備相同的效果,做用域外部不能訪問做用域內部聲明的函數或變量,這樣的聲明在ES6中對於包括for () {}
、if () {}
等大括號包裹的代碼塊中都會生效,生成一個單獨的做用域。
ES6新增的let
聲明變量的方式相比var
具備如下幾個重要特色:
let
聲明的變量只在做用域內有效,以下方代碼,if
聲明生成了一個塊級做用域,在這個做用域內聲明的變量在做用域外部沒法訪問,假如訪問會產生錯誤:if (true) {
let me = 'handsome boy';
}
console.log(me); // ReferenceError
複製代碼
let
聲明的變量與var
不一樣,不會產生變量提高,以下方代碼,在聲明以前輸出代碼,會產生錯誤:// var 的狀況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的狀況
console.log(bar); // ReferenceError
let bar = 2;
複製代碼
let
的聲明方式不容許重複聲明,如重複聲明會報錯,而var
聲明變量時,後聲明的語句會對先聲明的語句進行覆蓋:// 報錯
function func() {
let a = 10;
var a = 1;
}
// 報錯
function func() {
let a = 10;
let a = 1;
}
複製代碼
let
命令,它所聲明的變量就「綁定」(binding
)這個區域,再也不受外部的影響,這個特性稱爲暫時性死區
。var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
複製代碼
待補充
待補充
瀏覽器在接收到 html
與 css
後,渲染的步驟是:html
通過渲染生成 DOM
樹, css
通過渲染生成 css
渲染樹,二者再通過結合,生成 render tree
,瀏覽器就能夠根據 render tree
進行畫面繪製。
若是瀏覽器從服務器接收到了新的 css
,須要更新頁面時,須要通過什麼操做呢?這就是迴流 reflow
與重繪 repaint
。因爲瀏覽器在從新渲染頁面時會先進行 reflow
再進行 repaint
,所以,迴流必將引發重繪,而重繪不必定會引發迴流。
重繪:當前元素的樣式(背景顏色、字體顏色等)發生改變的時候,咱們只須要把改變的元素從新的渲染一下便可,重繪對瀏覽器的性能影響較小。發生重繪的情形:改變容器的外觀風格等,好比 background:black
等。改變外觀,不改變佈局,不影響其餘的 DOM
。 迴流:是指瀏覽器爲了從新渲染部分或者所有的文檔而從新計算文檔中元素的位置和幾何構造的過程。
由於迴流可能致使整個 DOM
樹的從新構造,因此是性能的一大殺手,一個元素的迴流致使了其全部子元素以及 DOM
中緊隨其後的祖先元素的隨後的迴流。下面貼出會觸發瀏覽器 reflow
的變化:
table
佈局。DOM
樹的最末端改變class
。position
屬性爲absolute
或fixed
的元素上。CSS
表達式(例如:calc()
)。style
屬性,或者將樣式列表定義爲class
並一次性更改class
屬性。DOM
,建立一個documentFragment
,在它上面應用全部DOM
操做,最後再把它添加到文檔中。display: none
,操做結束後再把它顯示出來。由於在display
屬性爲none
的元素上進行的DOM
操做不會引起迴流和重繪。通常的思路就是遞歸解決,對不一樣的數據類型作不一樣的處理:
function deepCopy (obj) {
let result = {}
for (let key in obj) {
if (obj[key] instanceof Object || obj[key] instanceof Array) {
result[key] = deepCopy(obj[key])
} else {
result[key] = obj[key]
}
}
return result
}
複製代碼
這個只能複製內部有數組、對象或其餘基礎數據類型的對象,假若有一些像RegExp
、Date
這樣的複雜對象複製的結果就是一個{}
,沒法正確進行復制,由於沒有對這些特殊對象進行單獨的處理。若要參考對複雜對象進行復制,能夠參考lodash
中數組深複製方法_.cloneDeep()
的實現方案,下面這篇文章對數組深複製的方法進行了詳細的解析,有必定參考價值: jerryzou.com/posts/dive-…
另外若是要複製的對象數據結構較爲簡單,沒有複雜對象的數據,那麼能夠用最簡便的方法:
let cloneResult = JSON.parse(JSON.stringify(targetObj))
複製代碼
此前轉載了一篇文章,對JavaScript運算精度丟失的緣由及解決方案都有比較詳細的說明: blog.csdn.net/qq_35271556…
<script>
時,會執行並阻塞渲染參考文章:segmentfault.com/a/119000001…
參考文章:blog.csdn.net/na_sama/art…
Vue的官方文檔對組件間的通訊方式作了詳細的說明:cn.vuejs.org
props
接收:// 父組件
<template>
<children name="boy"></children>
</template>
<script> // 子組件:children export default { props: { name: String } } </script>
複製代碼
this.$parent
訪問父組件的實例,不過官方文檔有這樣一段文字,很好的說明了 $parent
的意義:節制地使用 $parent
和 $children
—— 它們的主要目的是做爲訪問組件的應急方法。更推薦用 props
和 events
實現父子組件通訊。this.$emit('eventName', 'data')
,而後在父組件中的子組件標籤上監聽 eventName
事件,並在參數中獲取傳過來的值。// 子組件
export default {
mounted () {
this.$emit('mounted', 'Children is mounted.')
}
}
複製代碼
<template>
<children @mounted="mountedHandle"></children>
</template>
<script> // 父組件 export default { methods: { mountedHandle (data) { console.log(data) // Children is mounted. } } } </script>
複製代碼
$parent
同樣,在父組件中能夠經過訪問 this.$children
來訪問組件的全部子組件實例。對於非父子組件間,且具備複雜組件層級關係的狀況,能夠經過 Vuex
進行組件間數據傳遞: vuex.vuejs.org/zh/
在 Vue 1.0
中經常使用的 event bus
方式進行的全局數據傳遞,在 Vue 2.0
中已經被移除,官方文檔中有說明:$dispatch
和 $broadcast
已經被棄用。請使用更多簡明清晰的組件間通訊和更好的狀態管理方案,如:Vuex
。
blog.seosiwei.com/detail/35 blog.seosiwei.com/detail/36 blog.seosiwei.com/detail/37