前端筆試題(基礎20題)

第一題:瀏覽器控制檯上會打印什麼?

var a = 10;
function foo() {
    console.log(a); // ??
    var a = 20;
}
foo();

如果我們使用 let 或 const 代替 var,輸出是否相同?

var a = 10;
function foo() {
    console.log(a); // ??
    let a = 20;
}
foo();   

答案解析:
let和const聲明可以讓變量在其作用域上受限於它所使用的塊、語句或表達式。與var不同的是,這些變量沒有被提升,並且有一個所謂的暫時死區(TDZ)。試圖訪問TDZ中的這些變量將引發ReferenceError,因爲只有在執行到達聲明時才能訪問它們。最終答案是:
undefined
ReferenceError

第二題:瀏覽器控制檯上會打印什麼?

var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, {c: 3});
Object.defineProperty(obj, 'd', { value: 4, enumerable: false });

// what properties will be printed when we run the for-in loop?
for(let prop in obj) {
    console.log(prop);
}  

答案解析:
for-in循環遍歷對象本身的可枚舉屬性以及對象從其原型繼承的屬性。可枚舉屬性是可以在for-in循環期間包含和訪問的屬性。

var obj = { a: 1, b: 2 }; //a,b 都是 enumerables 屬性
// 將{c:3}設置爲'obj'的原型,並且我們知道
// for-in 循環也迭代 obj 繼承的屬性
// 從它的原型,'c'也可以被訪問。
Object.setPrototypeOf(obj, { c: 3 });
// 我們在'obj'中定義了另外一個屬性'd',但是 
// 將'enumerable'設置爲false。 這意味着'd'將被忽略。
Object.defineProperty(obj, "d", { value: 4, enumerable: false });
for (let prop in obj) {
  console.log(prop);
}
// 打印
// a
// b
// c

第三題:看下面的代碼,並說出"newArray"中有哪些元素?

var array = [];
for(var i = 0; i <3; i++) {
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??      

答案解析:
在for循環的頭部聲明帶有var關鍵字的變量會爲該變量創建單個綁定(存儲空間),接下來在{}中分別創建了閉包,但是這些閉包中三個箭頭函數體中的每個'i'都指向相同的綁定,讓我們通過代碼再看一次for循環。

// 誤解作用域:認爲存在塊級作用域
var array = [];
for (var i = 0; i < 3; i++) {
  // 三個箭頭函數體中的每個`'i'`都指向相同的綁定,
  // 這就是爲什麼它們在循環結束時返回相同的值'3'。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

如果使用 let 聲明一個具有塊級作用域的變量,則爲每個循環迭代創建一個新的綁定。

var array = [];
for (let i = 0; i < 3; i++) {
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

使用ES6塊級作用域,這一次,每個'i'指的是一個新的的綁定,並保留當前的值,因此,每個箭頭函數返回一個不同的值。
解決這個問題的另一種方法是使用閉包。

let array = [];
for (var i = 0; i < 3; i++) {
  array[i] = (function(x) {
    return function() {
      return x;
    };
  })(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2] 

第四題:如果我們在瀏覽器控制檯中運行'foo'函數,是否會導致堆棧溢出錯誤?

function foo() {
  setTimeout(foo, 0); // 是否存在堆棧溢出錯誤?
};    

答案解析:
JavaScript併發模型基於“事件循環”。
瀏覽器的主要組件包括調用堆棧,事件循環,任務隊列和Web API。像setTimeout,setInterval和Promise這樣的全局函數不是JavaScript的一部分,而是 Web API 的一部分。
JS調用棧是後進先出(LIFO)的。引擎每次從堆棧中取出一個函數,然後從上到下依次運行代碼。每當它遇到一些異步代碼,如setTimeout,它就把它交給Web API。因此,每當事件被觸發時,callback 都會被髮送到任務隊列。
事件循環(Event loop)不斷地監視任務隊列(Task Queue),並按它們排隊的順序一次處理一個回調。每當調用堆棧(call stack)爲空時,Event loop獲取回調並將其放入堆棧(stack )中進行處理。請記住
,如果調用堆棧不是空的,則事件循環不會將任何回調推入堆棧。
問題的代碼執行步驟:

  • 調用 foo()會將foo函數放入調用堆棧(call stack)。
  • 在處理內部代碼時,JS引擎遇到setTimeout。
  • 然後將foo回調函數傳遞給WebAPIs(箭頭1)並從函數返回,調用堆棧再次爲空
  • 計時器被設置爲0,因此foo將被髮送到任務隊列<Task Queue>(箭頭2)。
  • 由於調用堆棧是空的,事件循環將選擇foo回調並將其推入調用堆棧進行處理。
  • 進程再次重複,堆棧不會溢出。

第五題:如果在控制檯中運行以下函數,頁面(選項卡)的 UI 是否仍然響應?

function foo() {
  return Promise.resolve().then(foo);
}

答案解析:
大多數時候,開發人員假設在事件循環<event loop>中只有一個任務隊列。但事實並非如此,我們可以有多個任務隊列。由瀏覽器選擇其中的一個隊列並在該隊列中處理回調<callbacks>。
在底層來看,JavaScript中有宏任務和微任務。setTimeout回調是宏任務,而Promise回調是微任務。
主要的區別在於他們的執行方式。宏任務在單個循環週期中一次一個地推入堆棧,但是微任務隊列總是在執行後返回到事件循環之前清空。因此,如果你以處理條目的速度向這個隊列添加條目,那麼你就永遠在處理微任務。只有當微任務隊列爲空時,事件循環纔會重新渲染頁面、
現在,當你在控制檯中運行問題的代碼片段時,
每次調用'foo'都會繼續在微任務隊列上添加另一個'foo'回調,因此事件循環無法繼續處理其他事件(滾動,單擊等),直到該隊列完全清空爲止。因此,它會阻止渲染。

第六題:下面的語句使用展開運算會導致類型錯誤嗎?

var obj = { x: 1, y: 2, z: 3 };
[...obj]

答案解析:
展開語法 和 for-of 語句遍歷iterable對象定義要遍歷的數據。Array 或Map 是具有默認迭代行爲的內置迭代器。對象不是可迭代的,但是可以通過使用iterable和iterator協議使它們可迭代,所以題目中的代碼會導致TypeError錯誤。

第七題:xGetter() 會打印什麼值?

var x = 10;
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

答案解析:
在全局範圍內初始化x時,它成爲window對象的屬性(不是嚴格的模式)。看看下面的代碼:

var x = 10; // global scope
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
let xGetter = foo.getX;
xGetter(); // prints 10

咱們可以斷言:

window.x === 10; // true

this 始終指向調用方法的對象。因此,在foo.getx()的例子中,它指向foo對象,返回90的值。而在xGetter()的情況下,this指向 window對象, 返回 window 中的x的值,即10。

要獲取 foo.x的值,可以通過使用Function.prototype.bind將this的值綁定到foo對象來創建新函數。

let getFooX = foo.getX.bind(foo);
getFooX(); // 90

第八題:讀代碼,並寫出打印結果。

var a = 1
function foo(){
  if(!a){
    var a = 2
  }
  alert(a)
}
console.log(foo())

答案解析:
涉及到的知識點有作用域,變量提升。
因爲var是函數級作用域,foo函數中出現var a=2 的存在,
就默認在函數內頂端 聲明var a;此時這個a沒有被賦值所以是undefined;
然後執行if(!a)等價於!undefined肯定是true。然後給a賦值爲2.
所以打印的是2。

第九題:讀代碼,並寫出打印結果。

function test(person) {
    person.age = 26
    person = {
        name: 'hzj',
        age: '18'
    }
    return person
}
const p1 = {
    name: 'fyq',
    age: 19
}
const p2 = test(p1)
console.log(p1)
console.log(p2)

答案解析:
在函數傳參的時候傳遞的是對象在堆中的內存地址值,test函數中的實參person是p1對象的內存地址,通過調用person.age = 26確實改變了p1的值,但隨後person變成了另一塊內存空間的地址,並且在最後將這另外一份內存空間的地址返回,賦給了p2。所以打印結果是
{name: "fyq", age: 26}
index.html:46 {name: "hzj", age: "18"}

第十題:讀代碼,並寫出打印結果。

const box = {
    x: 10,
    y: 20
};
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape)

答案解析:
Object.freeze使得無法添加、刪除或修改對象的屬性(除非屬性的值是另一個對象)。
當我們創建變量 shape並將其設置爲等於凍結對象 box時, shape指向的也是凍結對象。你可以使用 Object.isFrozen檢查一個對象是否被凍結,上述情況, Object.isFrozen(shape)將返回 true。
由於 shape被凍結,並且 x的值不是對象,所以我們不能修改屬性 x。x仍然等於 10, {x:10,y:20}被打印。
注意,題目中的代碼對屬性 x進行修改,在嚴格模式下,會導致拋出TypeError異常。
index.html:24 Uncaught TypeError: Cannot assign to read only property 'x' of object '#<Object>'

第十一題:讀代碼,並寫出打印結果。

var number = 50
var obj = {
    number: 60,
    getNum: function () {
        var number = 70
        return this.number
    }
}
console.log(obj.getNum())
console.log(obj.getNum.call(undefined))
console.log(obj.getNum.call({ number: 20 }))

答案解析:
第一個結果:60
第二個結果:50
什麼參數都不傳,或者第一個參數傳null或者undefined的時候,執行上下文都會指向window
第三個結果:20

第十二題:讀代碼,並寫出打印結果。

function checkAge(age) {
    if (age < 18) {
        const message = 'too young'
    } else {
        const message = 'too simple'
    }
    return message
}
console.log(checkAge(21))

答案解析:
const和 let聲明的變量是具有塊級作用域的,塊是大括號( {})之間的任何東西, 即上述情況 if/else語句的花括號。由於塊級作用域,我們無法在聲明的塊之外引用變量,因此拋出 Uncaught ReferenceError: message is not defined

第十三題:讀代碼,並寫出打印結果。

const { cname: myName } = { cname: "Lydia" };
console.log(cname);

答案解析:
當我們從右側的對象解構屬性 cname時,我們將其值 Lydia分配給名爲 myName的變量。
使用 {cname:myName},我們是在告訴JavaScript我們要創建一個名爲 myName的新變量,並且其值是右側對象的 cname屬性的值。
當我們嘗試打印 cname,一個未定義的變量時,就會引發 index.html:18 Uncaught ReferenceError: cname is not defined

第十四題:讀代碼,並寫出打印結果。

let newList = [1,2,3].push(4)
console.log(newList.push(5))

答案解析:
push方法返回數組的長度,而不是數組本身!通過將 newList設置爲 [1,2,3].push(4),實際上 newList等於數組的新長度:4。
然後,嘗試在 newList上使用 .push方法。由於 newList是數值 4,拋出index.html:18 Uncaught TypeError: newList.push is not a function

第十五題:讀代碼,並寫出打印結果。

function giveLady() {
    return 'HHHHHHH'
}
const giveGentMan = () => 'MMMMMMMMMMMMM'
console.log(giveLady.prototype)
console.log(giveGentMan.prototype)

答案解析:
常規函數,例如 giveLydiaPizza函數,有一個 prototype屬性,它是一個帶有 constructor屬性的對象(原型對象)。然而,箭頭函數,例如 giveLydiaChocolate函數,沒有這個 prototype屬性。嘗試使用 giveLydiaChocolate.prototype訪問 prototype屬性時會返回 undefined。

第十六題:讀代碼,並寫出打印結果。

function getItems(fruitList, ...args, favoriteFruit) {
    return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")

答案解析:
...args是剩餘參數,剩餘參數的值是一個包含所有剩餘參數的數組,並且只能作爲最後一個參數。上述示例中,剩餘參數是第二個參數,這是不可能的,並會拋出語法錯誤。

function getItems(fruitList, favoriteFruit, ...args) {
    return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")

上述例子是有效的,將會返回數組:['banana','apple','orange','pear']

第十七題:讀代碼,並寫出打印結果。

function nums(a, b) {
    if (a > b)
        console.log("a is bigger")
    else
        console.log("b is bigger")
    return
    a + b
}
console.log(nums(4, 2))
console.log(nums(1, 2))

答案解析:
在JavaScript中,我們不必顯式地編寫分號( ;),但是JavaScript引擎仍然在語句之後自動添加分號。這稱爲自動分號插入。例如,一個語句可以是變量,或者像 throw、 return、 break這樣的關鍵字。

在這裏,我們在新的一行上寫了一個 return語句和另一個值 a+b。然而,由於它是一個新行,引擎並不知道它實際上是我們想要返回的值。相反,它會在 return後面自動添加分號。你可以這樣看:

return;
a + b

這意味着永遠不會到達 a+b,因爲函數在 return關鍵字之後停止運行。如果沒有返回值,就像這裏,函數返回 undefined。注意,在 if/else關鍵詞之後不會自動插入分號
所以輸出結果爲
a is bigger
undefined
b is bigger
undefined

第十八題:讀代碼,並寫出打印結果。

const info = {
    [Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))

答案解析:
Symbol類型是不可枚舉的。Object.keys方法返回對象上的所有可枚舉的鍵屬性。Symbol類型是不可見的,並返回一個空數組。記錄整個對象時,所有屬性都是可見的,甚至是不可枚舉的屬性。
Symbol可以用來表示表示完全唯一的值,在對象中使用Symbol作爲屬性,可以防止對象意外名稱衝突。但是可以通過Object.getOwnPropertySymbols(info)來訪問info的所有屬性。[Symbol(a)]

第十九題:讀代碼,並寫出打印結果。

const geetList = ([x, ...y]) => [x, y]
const getUser = user => ({ name: user.nameage: user.age }
const list = [1, 2, 3, 4]
const user = { name: 'Lydia', age: 21 }
console.log(geetList(list))
console.log(getUser(user))

答案解析:
Uncaught SyntaxError: Unexpected token :
原因:getUser函數接收一個對象。對於箭頭函數,如果只返回一個值,我們不必編寫花括號。但是,如果您想從一個箭頭函數返回一個對象,您必須在圓括號之間編寫它,否則不會返回任何值!
正確的寫法應該是這樣

const geetList = ([x, ...y]) => [x, y]
const getUser = user => ({ name: user.name, age: user.age })
const list = [1, 2, 3, 4]
const user = { name: 'Lydia', age: 21 }
console.log(geetList(list))
console.log(getUser(user))

輸出內容

[1, Array(3)]
{name: "Lydia", age: 21}

第二十題:讀代碼,並寫出打印結果。

var arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr = arr.map(function (x) {
    return x * x
}).reduce(function (acc, x, i) {
        acc.push(x)
        acc.push(i)
        return acc
    }, []).filter(function (x) {
        return x % 2
    }).join(",")
console.log(arr)

答案解析:

var arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr = arr.map(function (x) {
    return x * x
})
    // [1, 4, 9, 16, 25, 36, 49, 64]
    .reduce(function (acc, x, i) {
        acc.push(x)
        acc.push(i)
        return acc
    }, [])
    // [1, 0, 4, 1, 9, 2, 16, 3, 25, 4, 36, 5, 49, 6, 64, 7]
    .filter(function (x) {
        return x % 2
    })
    // [1, 1, 9, 3, 25, 5, 49, 7]
    .join(",")
console.log(arr)
// 1,1,9,3,25,5,49,7

acc不包括當前的x,如果把reduce的第二個參數改成0,然後第一個函數參數內的代碼改爲return acc+x,則返回值是數組的合。
0%2,結果是0,2%2,結果是0,-2%2,結果是-0,這三個轉換爲布爾值,結果都是false

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章