八月面試題(3)

楊季布發表於2018-09-06

1、請說說你對執行 JS 程式碼時產生的執行上下文的理解

答:當執行 JS 程式碼時,會產生三種執行上下文 全域性執行上下文 函式執行上下文 eval 執行上下文

每個執行上下文中都有三個重要的屬性 變數物件(VO),包含變數、函式宣告和函式的形參,該屬性只能在全域性上下文中訪問 作用域鏈(JS 採用詞法作用域,也就是說變數的作用域是在定義時就決定了) this

    var a = 10
    function foo(i) {
        var b = 20
    }
    foo()
複製程式碼

對於上述程式碼,執行棧中有兩個上下文:全域性上下文和函式 foo 上下文。

    stack = [
        globalContext,
        fooContext
    ]
複製程式碼

對於全域性上下文來說,VO 大概是這樣的

    globalContext.VO === globe
    globalContext.VO = {
        a: undefined,
        foo: <Function>,
    }
複製程式碼

對於函式 foo 來說,VO 不能訪問,只能訪問到活動物件(AO)

    fooContext.VO === foo.AO
    fooContext.AO {
        i: undefined,
        b: undefined,
        arguments: <>
    }
// arguments 是函式獨有的物件(箭頭函式沒有)
// 該物件是一個偽陣列,有 `length` 屬性且可以通過下標訪問元素
// 該物件中的 `callee` 屬性代表函式本身
// `caller` 屬性代表函式的呼叫者

對於作用域鏈,可以把它理解成包含自身變數物件和上級變數物件的列表,通過 [[Scope]] 屬性查詢上級變數

fooContext.[[Scope]] = [
    globalContext.VO
]
fooContext.Scope = fooContext.[[Scope]] + fooContext.VO
fooContext.Scope = [
    fooContext.VO,
    globalContext.VO
]
接下來讓我們看一個老生常談的例子,var

b() // call b
console.log(a) // undefined

var a = 'Hello world'

function b() {
    console.log('call b')
}

想必以上的輸出大家肯定都已經明白了,這是因為函式和變數提升的原因。通常提升的解釋是說將宣告的程式碼移動到了頂部,這其實沒有什麼錯誤,便於大家理解。但是更準確的解釋應該是:在生成執行上下文時,會有兩個階段。第一個階段是建立的階段(具體步驟是建立 VO),JS 直譯器會找出需要提升的變數和函式,並且給他們提前在記憶體中開闢好空間,函式的話會將整個函式存入記憶體中,變數只宣告並且賦值為 undefined,所以在第二個階段,也就是程式碼執行階段,我們可以直接提前使用。
複製程式碼

在提升的過程中,相同的函式會覆蓋上一個函式,並且函式優先於變數提升

b() // call b second

function b() {
    console.log('call b fist')
}
function b() {
	console.log('call b second')
}
var b = 'Hello world'

var 會產生很多錯誤,所以在 ES6中引入了 let。let 不能在宣告前使用,但是這並不是常說的 let 不會提升,let 提升了宣告但沒有賦值,因為臨時死區導致了並不能在宣告前使用。
複製程式碼

對於非匿名的立即執行函式需要注意以下一點

var foo = 1
(function foo() {
    foo = 10
    console.log(foo)
}()) // -> ƒ foo() { foo = 10 ; console.log(foo) }
複製程式碼

因為當 JS 直譯器在遇到非匿名的立即執行函式時,會建立一個輔助的特定物件,然後將函式名稱作為這個物件的屬性,因此函式內部才可以訪問到 foo,但是這又個值是隻讀的,所以對它的賦值並不生效,所以列印的結果還是這個函式,並且外部的值也沒有發生更改。

specialObject = {};

Scope = specialObject + Scope;

foo = new FunctionExpression;
foo.[[Scope]] = Scope;
specialObject.foo = foo; // {DontDelete}, {ReadOnly}

delete Scope[0]; // remove specialObject from the front of scope chain

2、在一個陣列中大部分都是奇數(或偶數),只有1個可能是偶數(或奇數),寫一個函式special找到這個不一樣的值。
複製程式碼

special( [2, 4, 0, 100, 4, 11, 2602, 36] ) // 11 唯一的奇數 special( [160, 3, 1719, 19, 11, 13, -21] ) // 160 唯一的偶數

答:

function special(A){
    var evens = A.filter(a=>a%2==0)
    var odds = A.filter(a=>a%2!==0)
    return evens.length==1? evens[0] : odds[0]
}

3、請說說你對Event loop的理解?瀏覽器中的Event loop和Node中的Event loop的異同?

4、為什麼 0.1 + 0.2 != 0.3

5、什麼是Service Worker?如何使用?

答:Service workers 本質上充當Web應用程式與瀏覽器之間的代理伺服器,也可以在網路可用時作為瀏覽器和網路間的代理。它們旨在(除其他之外)使得能夠建立有效的離線體驗,攔截網路請求並基於網路是否可用以及更新的資源是否駐留在伺服器上來採取適當的動作。他們還允許訪問推送通知和後臺同步API。
複製程式碼

目前該技術通常用來做快取檔案,提高首屏速度,可以試著來實現這個功能。

// // index.js
if 
if (f (navigator.serviceWorkerrker) {

navigator.serviceWorkerrker
    .register("r("sw.js")
 ")
    .then(function(registration) {
     console.log("se("service worker 註冊成功");
    })
    .catch(function(err) {
     console.log("se("servcie worker 註冊失敗");
    });
}
// // sw.js
// 
// 監聽 `install` 事件,回撥中快取所需檔案
檔案
self.addEventListeistener("install", e => {

 e.waitUntil(
(
     caches.open("my("my-cache").then(function(cache) {
     return rn cache.addAll([".(["./"./index.html", "", "./"./index.js"]);"]);
    })
 );
});
複製程式碼

// 攔截所有請求事件 // 如果快取中已經有請求的資料就直接用快取,否則去請求資料 資料 self.addEventListeistener("fetch", e => {

e.respondWith(
th(
     caches.match(e.r(ch(e.request).th).then(function(response) {
     if (response) {
      return response;
     }
       console.log("fe("fetch source");
 })
 );
});
複製程式碼

開啟頁面,可以在開發者工具中的 Application 看到 Service Worker 已經啟動了 在 Cache 中也可以發現我們所需的檔案已被快取

相關文章