天天看點

金三銀四,求職季,而你必須要懂的原生JS(面試題)

  1. 原始類型有哪幾種?null 是對象嗎?原始資料類型和複雜資料類型存儲有什麼差別?

原始類型有6種,分别是undefined,null,bool,string,number,symbol(ES6新增)。

雖然 typeof null 傳回的值是 object,但是null不是對象,而是基本資料類型的一種。

原始資料類型存儲在棧記憶體,存儲的是值。

複雜資料類型存儲在堆記憶體,存儲的是位址。當我們把對象指派給另外一個變量的時候,複制的是位址,指向同一塊記憶體空間,當其中一個對象改變時,另一個對象也會變化。

  1. typeof 是否正确判斷類型? instanceof呢? instanceof 的實作原理是什麼?

    首先 typeof 能夠正确的判斷基本資料類型,但是除了 null, typeof null輸出的是對象。

    但是對象來說,typeof 不能正确的判斷其類型, typeof 一個函數可以輸出 ‘function’,而除此之外,輸出的全是 object,這種情況下,我們無法準确的知道對象的類型。

    instanceof可以準确的判斷複雜資料類型,但是不能正确判斷基本資料類型。(正确判斷資料類型請戳:github.com/YvetteLau/B…)

    instanceof 是通過原型鍊判斷的,A instanceof B, 在A的原型鍊中層層查找,是否有原型等于B.prototype,如果一直找到A的原型鍊的頂端(null;即Object.prototype.proto),仍然不等于B.prototype,那麼傳回false,否則傳回true.

    instanceof的實作代碼:

    // L instanceof R

    function instance_of(L, R) {//L 表示左表達式,R 表示右表達式

    var O = R.prototype;// 取 R 的顯式原型

    L = L.proto; // 取 L 的隐式原型

    while (true) {

    if (L === null) //已經找到頂層

    return false;

    if (O === L) //當 O 嚴格等于 L 時,傳回 true

    return true;

    L = L.proto; //繼續向上一層原型鍊查找

    }

    }

    複制代碼

  2. for of , for in 和 forEach,map 的差別。

for…of循環:具有 iterator 接口,就可以用for…of循環周遊它的成員(屬性值)。for…of循環可以使用的範圍包括數組、Set 和 Map 結構、某些類似數組的對象、Generator 對象,以及字元串。for…of循環調用周遊器接口,數組的周遊器接口隻傳回具有數字索引的屬性。對于普通的對象,for…of結構不能直接使用,會報錯,必須部署了 Iterator 接口後才能使用。可以中斷循環。

for…in循環:周遊對象自身的和繼承的可枚舉的屬性, 不能直接擷取屬性值。可以中斷循環。

forEach: 隻能周遊數組,不能中斷,沒有傳回值(或認為傳回值是undefined),不修改原數組。

map: 隻能周遊數組,不能中斷,傳回值是修改後的數組,不修改原數組。

PS: Object.keys():傳回給定對象所有可枚舉屬性的字元串數組。

如還不了解 iterator 接口或 for…of, 請先閱讀ES6文檔: Iterator 和 for…of 循環

更多細節請戳: github.com/YvetteLau/B…

  1. 如何判斷一個變量是不是數組?

使用 Array.isArray 判斷,如果傳回 true, 說明是數組

使用 instanceof Array 判斷,如果傳回true, 說明是數組

使用 Object.prototype.toString.call 判斷,如果值是 [object Array], 說明是數組

通過 constructor 來判斷,如果是數組,那麼 arr.constructor === Array. (不準确,因為我們可以指定 obj.constructor = Array)

function fn() {

console.log(Array.isArray(arguments)); //false; 因為arguments是類數組,但不是數組

console.log(Array.isArray([1,2,3,4])); //true

console.log(arguments instanceof Array); //fasle

console.log([1,2,3,4] instanceof Array); //true

console.log(Object.prototype.toString.call(arguments)); //[object Arguments]

console.log(Object.prototype.toString.call([1,2,3,4])); //[object Array]

console.log(arguments.constructor === Array); //false

arguments.constructor = Array;

console.log(arguments.constructor === Array); //true

console.log(Array.isArray(arguments)); //false

}

fn(1,2,3,4);

複制代碼

5. 類數組和數組的差別是什麼?

類數組:

1)擁有length屬性,其它屬性(索引)為非負整數(對象中的索引會被當做字元串來處理);

2)不具有數組所具有的方法;

類數組是一個普通對象,而真實的數組是Array類型。

常見的類數組有: 函數的參數 arugments, DOM 對象清單(比如通過 document.querySelectorAll 得到的清單), jQuery 對象 (比如 $(“div”)).

類數組可以轉換為數組:

//第一種方法

Array.prototype.slice.call(arrayLike, start);

//第二種方法

[…arrayLike];

//第三種方法:

Array.from(arrayLike);

複制代碼PS: 任何定義了周遊器(Iterator)接口的對象,都可以用擴充運算符轉為真正的數組。

Array.from方法用于将兩類對象轉為真正的數組:類似數組的對象(array-like object)和可周遊(iterable)的對象。

  1. == 和 === 有什麼差別?

    === 不需要進行類型轉換,隻有類型相同并且值相等時,才傳回 true.

    == 如果兩者類型不同,首先需要進行類型轉換。具體流程如下:

首先判斷兩者類型是否相同,如果相等,判斷值是否相等.

如果類型不同,進行類型轉換

判斷比較的是否是 null 或者是 undefined, 如果是, 傳回 true .

判斷兩者類型是否為 string 和 number, 如果是, 将字元串轉換成 number

判斷其中一方是否為 boolean, 如果是, 将 boolean 轉為 number 再進行判斷

判斷其中一方是否為 object 且另一方為 string、number 或者 symbol , 如果是, 将 object 轉為原始類型再進行判斷

let person1 = {

age: 25

}

let person2 = person1;

person2.gae = 20;

console.log(person1 === person2); //true,注意複雜資料類型,比較的是引用位址

複制代碼思考: [] == ![]

我們來分析一下: [] == ![] 是true還是false?

首先,我們需要知道 ! 優先級是高于 == (更多運算符優先級可檢視: 運算符優先級)

![] 引用類型轉換成布爾值都是true,是以![]的是false

根據上面的比較步驟中的第五條,其中一方是 boolean,将 boolean 轉為 number 再進行判斷,false轉換成 number,對應的值是 0.

根據上面比較步驟中的第六條,有一方是 number,那麼将object也轉換成Number,空數組轉換成數字,對應的值是0.(空數組轉換成數字,對應的值是0,如果數組中隻有一個數字,那麼轉成number就是這個數字,其它情況,均為NaN)

0 == 0; 為true

  1. ES6中的class和ES5的類有什麼差別?

ES6 class 内部所有定義的方法都是不可枚舉的;

ES6 class 必須使用 new 調用;

ES6 class 不存在變量提升;

ES6 class 預設即是嚴格模式;

ES6 class 子類必須在父類的構造函數中調用super(),這樣才有this對象;ES5中類繼承的關系是相反的,先有子類的this,然後用父類的方法應用在this上。

  1. 數組的哪些API會改變原數組?

修改原數組的API有:

splice/reverse/fill/copyWithin/sort/push/pop/unshift/shift

不修改原數組的API有:

slice/map/forEach/every/filter/reduce/entry/entries/find

  1. let、const 以及 var 的差別是什麼?

let 和 const 定義的變量不會出現變量提升,而 var 定義的變量會提升。

let 和 const 是JS中的塊級作用域

let 和 const 不允許重複聲明(會抛出錯誤)

let 和 const 定義的變量在定義語句之前,如果使用會抛出錯誤(形成了暫時性死區),而 var 不會。

const 聲明一個隻讀的常量。一旦聲明,常量的值就不能改變(如果聲明是一個對象,那麼不能改變的是對象的引用位址)

  1. 在JS中什麼是變量提升?什麼是暫時性死區?

    變量提升就是變量在聲明之前就可以使用,值為undefined。

    在代碼塊内,使用 let/const 指令聲明變量之前,該變量都是不可用的(會抛出錯誤)。這在文法上,稱為“暫時性死區”。暫時性死區也意味着 typeof 不再是一個百分百安全的操作。

    typeof x; // ReferenceError(暫時性死區,抛錯)

    let x;

    複制代碼typeof y; // 值是undefined,不會報錯

    複制代碼暫時性死區的本質就是,隻要一進入目前作用域,所要使用的變量就已經存在了,但是不可擷取,隻有等到聲明變量的那一行代碼出現,才可以擷取和使用該變量。

  2. 如何正确的判斷this? 箭頭函數的this是什麼?

    this的綁定規則有四種:預設綁定,隐式綁定,顯式綁定,new綁定.

函數是否在 new 中調用(new綁定),如果是,那麼 this 綁定的是新建立的對象。

函數是否通過 call,apply 調用,或者使用了 bind (即硬綁定),如果是,那麼this綁定的就是指定的對象。

函數是否在某個上下文對象中調用(隐式綁定),如果是的話,this 綁定的是那個上下文對象。一般是 obj.foo()

如果以上都不是,那麼使用預設綁定。如果在嚴格模式下,則綁定到 undefined,否則綁定到全局對象。

如果把 null 或者 undefined 作為 this 的綁定對象傳入 call、apply 或者 bind, 這些值在調用時會被忽略,實際應用的是預設綁定規則。

箭頭函數沒有自己的 this, 它的this繼承于上一層代碼塊的this。

測試下是否已經成功Get了此知識點(浏覽器執行環境):

var number = 5;

var obj = {

number: 3,

fn1: (function () {

var number;

this.number *= 2;

number = number * 2;

number = 3;

return function () {

var num = this.number;

this.number *= 2;

console.log(num);

number *= 3;

console.log(number);

}

})();

}

var fn1 = obj.fn1;

fn1.call(null);

obj.fn1();

console.log(window.number);

複制代碼如果this的知識點,您還不太懂,請戳: 嗨,你真的懂this嗎?

  1. 詞法作用域和this的差別。

詞法作用域是由你在寫代碼時将變量和塊作用域寫在哪裡來決定的

this 是在調用時被綁定的,this 指向什麼,完全取決于函數的調用位置(關于this的指向問題,本文已經有說明)

  1. 談談你對JS執行上下文棧和作用域鍊的了解。

    執行上下文就是目前 JavaScript 代碼被解析和執行時所在環境, JS執行上下文棧可以認為是一個存儲函數調用的棧結構,遵循先進後出的原則。

JavaScript執行在單線程上,所有的代碼都是排隊執行。

一開始浏覽器執行全局的代碼時,首先建立全局的執行上下文,壓入執行棧的頂部。

每當進入一個函數的執行就會建立函數的執行上下文,并且把它壓入執行棧的頂部。目前函數執行-完成後,目前函數的執行上下文出棧,并等待垃圾回收。

浏覽器的JS執行引擎總是通路棧頂的執行上下文。

全局上下文隻有唯一的一個,它在浏覽器關閉時出棧。

作用域鍊: 無論是 LHS 還是 RHS 查詢,都會在目前的作用域開始查找,如果沒有找到,就會向上級作用域繼續查找目标辨別符,每次上升一個作用域,一直到全局作用域為止。

題難不難?不難!繼續挑戰一下!難!知道難,就更要繼續了!

  1. 什麼是閉包?閉包的作用是什麼?閉包有哪些使用場景?

    閉包是指有權通路另一個函數作用域中的變量的函數,建立閉包最常用的方式就是在一個函數内部建立另一個函數。

    閉包的作用有:

封裝私有變量

模仿塊級作用域(ES5中沒有塊級作用域)

實作JS的子產品

  1. call、apply有什麼差別?call,aplly和bind的内部是如何實作的?

    call 和 apply 的功能相同,差別在于傳參的方式不一樣:

fn.call(obj, arg1, arg2, …),調用一個函數, 具有一個指定的this值和分别地提供的參數(參數的清單)。

fn.apply(obj, [argsArray]),調用一個函數,具有一個指定的this值,以及作為一個數組(或類數組對象)提供的參數。

call核心:

将函數設為傳入參數的屬性

指定this到函數并傳入給定參數執行函數

如果不傳入參數或者參數為null,預設指向為 window / global

删除參數上的函數

Function.prototype.call = function (context) {

if (!context) {

//context為null或者是undefined

context = typeof window === ‘undefined’ ? global : window;

}

context.fn = this; //this指向的是目前的函數(Function的執行個體)

let rest = […arguments].slice(1);//擷取除了this指向對象以外的參數, 空數組slice後傳回的仍然是空數組

let result = context.fn(…rest); //隐式綁定,目前函數的this指向了context.

delete context.fn;

return result;

}

//測試代碼

var foo = {

name: ‘Selina’

}

var name = ‘Chirs’;

function bar(job, age) {

console.log(this.name);

console.log(job, age);

}

bar.call(foo, ‘programmer’, 20);

// Selina programmer 20

bar.call(null, ‘teacher’, 25);

// 浏覽器環境: Chirs teacher 25; node 環境: undefined teacher 25

複制代碼

apply:

apply的實作和call很類似,但是需要注意他們的參數是不一樣的,apply的第二個參數是數組或類數組.

Function.prototype.apply = function (context, rest) {

if (!context) {

//context為null或者是undefined時,設定預設值

context = typeof window === ‘undefined’ ? global : window;

}

context.fn = this;

let result = context.fn(…rest);

delete context.fn;

return result;

}

var foo = {

name: ‘Selina’

}

var name = ‘Chirs’;

function bar(job, age) {

console.log(this.name);

console.log(job, age);

}

bar.apply(foo, [‘programmer’, 20]);

// Selina programmer 20

bar.apply(null, [‘teacher’, 25]);

// 浏覽器環境: Chirs programmer 20; node 環境: undefined teacher 25

複制代碼

bind

bind 和 call/apply 有一個很重要的差別,一個函數被 call/apply 的時候,會直接調用,但是 bind 會建立一個新函數。當這個新函數被調用時,bind() 的第一個參數将作為它運作時的 this,之後的一序列參數将會在傳遞的實參前傳入作為它的參數。

Function.prototype.bind = function(context) {

if(typeof this !== “function”){

throw new TypeError(“not a function”);

}

let self = this;

let args = […arguments].slice(1);

function Fn() {};

Fn.prototype = this.prototype;

let bound = function() {

let res = […args, …arguments]; //bind傳遞的參數和函數調用時傳遞的參數拼接

context = this instanceof Fn ? this : context || this;

return self.apply(context, res);

}

//原型鍊

bound.prototype = new Fn();

return bound;

}

var name = ‘Jack’;

function person(age, job, gender){

console.log(this.name , age, job, gender);

}

var Yve = {name : ‘Yvette’};

let result = person.bind(Yve, 22, ‘enginner’)(‘female’);

複制代碼

小編今天就更新到這裡了,如果想要擷取更多的學習資料可以加2239966169這個美麗的小姐姐。