2019年常見面試題

1 . 原始類型有哪幾種?null 是對象嗎?原始數據類型和複雜數據類型存儲有什麼區別?node

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

  • 雖然 typeof null 返回的值是 object, 可是 null 不是對象,而是基本數據類型的一種。es6

  • 原始數據類型存儲在棧內存,存儲的是值。github

  • 複雜數據類型的地址存儲再佔內存,指向存儲在堆內存的值。當咱們把對象賦值給另一個變量的時候,複製的是地址,指向同一塊內存空間,當其中一個對象改變時,另外一個對象也會變化。數組

 2.typeof 是否正確判斷類型? instanceof 呢? instanceof 的實現原理是什麼?promise

首先 typeof 可以正確的判斷基本數據類型,可是除了 null, typeof null 輸出的是對象。瀏覽器

可是對象來講,typeof 不能正確的判斷其類型, typeof 一個函數能夠輸出 'function', 而除此以外,輸出的全是 object, 這種狀況下,咱們沒法準確的知道對象的類型。緩存

instanceof 能夠準確的判斷複雜數據類型,可是不能正確判斷基本數據類型。正確判斷數據類型請戳:安全

https://github.com/YvetteLau/Blog/blob/master/JS/data-type.js數據結構

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__; // 繼續向上一層原型鏈查找
}
}
 3.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():返回給定對象全部可枚舉屬性的字符串數組。

關於 forEach 是否會改變原數組的問題,有些小夥伴提出了異議,爲此我寫了代碼測試了下 (注意數組項是複雜數據類型的狀況)。除了 forEach 以外,map 等 API,也有一樣的問題。

let arry = [1, 2, 3, 4];

arry.forEach((item) => {
item *= 10;
});
console.log(arry); //[1, 2, 3, 4]

arry.forEach((item) => {
arry[1] = 10; // 直接操做數組
});
console.log(arry); //[ 1, 10, 3, 4 ]

let arry2 = [
    { name: "Yve" },
    { age: 20 }
];
arry2.forEach((item) => {
item.name = 10;
});
console.log(arry2);//[ { name: 10 }, { age: 20, name: 10 } ]

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

http://es6.ruanyifeng.com/#docs/iterator

更多細節請戳: 

https://github.com/YvetteLau/Blog/blob/master/JS/for.js

 4 . 如何判斷一個變量是否是數組?

  • 使用 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)的對象。

 6.== 和 === 有什麼區別?

=== 不須要進行類型轉換,只有類型相同而且值相等時,才返回 true.

== 若是二者類型不一樣,首先須要進行類型轉換。具體流程以下:

  1. 首先判斷二者類型是否相同,若是相等,判斷值是否相等;

  2. 若是類型不一樣,進行類型轉換;

  3. 判斷比較的是不是 null 或者是 undefined, 若是是, 返回 true;

  4. 判斷二者類型是否爲 string 和 number, 若是是, 將字符串轉換成 number;

  5. 判斷其中一方是否爲 boolean, 若是是, 將 boolean 轉爲 number 再進行判斷;

  6. 判斷其中一方是否爲 object 且另外一方爲 string、number 或者 symbol , 若是是, 將 object 轉爲原始類型再進行判斷。

let person1 = {
    age: 25
}
let person2 = person1;
person2.gae = 20;
console.log(person1 === person2); //true, 注意複雜數據類型,比較的是引用地址

 思考: [] == ![]

咱們來分析一下: [] == ![] 是 true 仍是 false?

1. 首先,咱們須要知道 ! 優先級是高於 == (更多運算符優先級可查看: 運算符優先級):

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

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

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

4. 根據上面比較步驟中的第六條,有一方是 number,那麼將 object 也轉換成 Number, 空數組轉換成數字,對應的值是 0.(空數組轉換成數字,對應的值是 0,若是數組中只有一個數字,那麼轉成 number 就是這個數字,其它狀況,均爲 NaN)

5.0 == 0; 爲 true

 7.ES6 中的 class 和 ES5 的類有什麼區別?

1.ES6 class 內部全部定義的方法都是不可枚舉的;

2.ES6 class 必須使用 new 調用;

3.ES6 class 不存在變量提高;

4.ES6 class 默認便是嚴格模式;

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

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

修改原數組的 API 有:

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

不修改原數組的 API 有:

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

注: 數組的每一項是簡單數據類型,且未直接操做數組的狀況下。

 9.let、const 以及 var 的區別是什麼?

  • let 和 const 定義的變量不會出現變量提高,而 var 定義的變量會提高;

  • let 和 const 是 JS 中的塊級做用域;

  • let 和 const 不容許重複聲明 (會拋出錯誤);

  • let 和 const 定義的變量在定義語句以前,若是使用會拋出錯誤 (造成了暫時性死區),而 var 不會;

  • const 聲明一個只讀的常量。一旦聲明,常量的值就不能改變 (若是聲明是一個對象,那麼不能改變的是對象的引用地址)。

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

變量提高就是變量在聲明以前就可使用,值爲 undefined。

在代碼塊內,使用 let/const 命令聲明變量以前,該變量都是不可用的 (會拋出錯誤)。這在語法上,稱爲「暫時性死區」。暫時性死區也意味着 typeof 再也不是一個百分百安全的操做。

typeof x; // ReferenceError(暫時性死區,拋錯)
let x;
typeof y; // 值是 undefined, 不會報錯

暫時性死區的本質就是,只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。

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

this 的綁定規則有四種:默認綁定,隱式綁定,顯式綁定,new 綁定.

  1. 函數是否在 new 中調用 (new 綁定),若是是,那麼 this 綁定的是新建立的對象;

  2. 函數是否經過 call,apply 調用,或者使用了 bind (即硬綁定),若是是,那麼 this 綁定的就是指定的對象;

  3. 函數是否在某個上下文對象中調用 (隱式綁定),若是是的話,this 綁定的是那個上下文對象。通常是 obj.foo();

  4. 若是以上都不是,那麼使用默認綁定。若是在嚴格模式下,則綁定到 undefined,不然綁定到全局對象;

  5. 若是把 null 或者 undefined 做爲 this 的綁定對象傳入 call、apply 或者 bind, 這些值在調用時會被忽略,實際應用的是默認綁定規則;

  6. 箭頭函數沒有本身的 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 嗎?:

https://github.com/YvetteLau/Blog/issues/6

 12. 詞法做用域和 this 的區別。

  • 詞法做用域是由你在寫代碼時將變量和塊做用域寫在哪裏來決定的;

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

 

 13. 談談你對 JS 執行上下文棧和做用域鏈的理解。

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

  • JavaScript 執行在單線程上,全部的代碼都是排隊執行。

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

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

  • 瀏覽器的 JS 執行引擎老是訪問棧頂的執行上下文。

  • 全局上下文只有惟一的一個,它在瀏覽器關閉時出棧。

做用域鏈: 不管是 LHS 仍是 RHS 查詢,都會在當前的做用域開始查找,若是沒有找到,就會向上級做用域繼續查找目標標識符,每次上升一個做用域,一直到全局做用域爲止。

 14. 什麼是閉包?閉包的做用是什麼?閉包有哪些使用場景?

閉包是指有權訪問另外一個函數做用域中的變量的函數,建立閉包最經常使用的方式就是在一個函數內部建立另外一個函數。

閉包的做用有:

  1. 封裝私有變量;

  2. 模仿塊級做用域 (ES5 中沒有塊級做用域);

  3. 實現 JS 的模塊。

 

 15.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) {
/** 若是第一個參數傳入的是 null 或者是 undefined, 那麼指向 this 指向 window/global */
/** 若是第一個參數傳入的不是 null 或者是 undefined, 那麼必須是一個對象 */
    if (!context) {
//context 爲 null 或者是 undefined
        context = typeof window === 'undefined' ? global : window;
}
context.fn = this; //this 指向的是當前的函數 (Function 的實例)
let args = [...arguments].slice(1);// 獲取除了 this 指向對象之外的參數, 空數組 slice 後返回的仍然是空數組
let result = context.fn(...args); // 隱式綁定, 當前函數的 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');

 16.new 的原理是什麼?經過 new 的方式建立對象和經過字面量建立有什麼區別? new

  1. 建立一個新對象;

  2. 這個新對象會被執行 [[原型]] 鏈接;

  3. 將構造函數的做用域賦值給新對象,即 this 指向這個新對象;

  4. 若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象。

function new(func) {
lat target = {};
target.__proto__ = func.prototype;
let res = func.call(target);
if (typeof(res) == "object" || typeof(res) == "function") {
return res;
}
return target;
}

字面量建立對象,不會調用 Object 構造函數, 簡潔且性能更好。

new Object() 方式建立對象本質上是方法調用,涉及到在 proto 鏈中遍歷該方法,當找到該方法後,又會生產方法調用必須的 堆棧信息,方法調用結束後,還要釋放該堆棧,性能不如字面量的方式。

經過對象字面量定義對象時,不會調用 Object 構造函數。

 17. 談談你對原型的理解?

在 JavaScript 中,每當定義一個對象(函數也是對象)時候,對象中都會包含一些預約義的屬性。其中每一個函數對象都有一個 prototype 屬性,這個屬性指向函數的原型對象。使用原型對象的好處是全部對象實例共享它所包含的屬性和方法。

 18. 什麼是原型鏈?【原型鏈解決的是什麼問題?】

原型鏈解決的主要是繼承問題。

每一個對象擁有一個原型對象,經過 proto (讀音: dunder proto) 指針指向其原型對象,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null(Object.proptotype.__proto__ 指向的是 null)。這種關係被稱爲原型鏈 (prototype chain),經過原型鏈一個對象能夠擁有定義在其餘對象中的屬性和方法。

構造函數 Parent、Parent.prototype 和 實例 p 的關係以下:(p.__proto__ === Parent.prototype)

 19.prototype 和 __proto__ 區別是什麼?

prototype 是構造函數的屬性。

__proto__ 是每一個實例都有的屬性,能夠訪問 [[prototype]] 屬性。

實例的__proto__ 與其構造函數的 prototype 指向的是同一個對象。

function Student(name) {
this.name = name;
}
Student.prototype.setAge = function(){
this.age=20;
}
let Jack = new Student('jack');
console.log(Jack.__proto__);
//console.log(Object.getPrototypeOf(Jack));;
console.log(Student.prototype);
console.log(Jack.__proto__ === Student.prototype);//true

 20. 使用 ES5 實現一個繼承? 組合繼承 (最經常使用的繼承方式)

function SuperType() {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}

function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

SubType.prototype.sayAge = function() {
console.log(this.age);
}

其它繼承方式實現,能夠參考《JavaScript 高級程序設計》

 21. 什麼是深拷貝?深拷貝和淺拷貝有什麼區別?

淺拷貝是指只複製第一層對象,可是當對象的屬性是引用類型時,實質複製的是其引用,當引用指向的值改變時也會跟着變化。

深拷貝複製變量值,對於非基本類型的變量,則遞歸至基本類型變量後,再複製。深拷貝後的對象與原來的對象是徹底隔離的,互不影響,對一個對象的修改並不會影響另外一個對象。

實現一個深拷貝:

function deepClone(obj) { // 遞歸拷貝
if(obj === null) return null; //null 的狀況
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
    if(typeof obj !== 'object') {
// 若是不是複雜數據類型,直接返回
return obj;
}
/**
     * 若是 obj 是數組,那麼 obj.constructor 是 [Function: Array]
     * 若是 obj 是對象,那麼 obj.constructor 是 [Function: Object]
*/
let t = new obj.constructor();
for(let key in obj) {
// 若是 obj[key] 是複雜數據類型,遞歸
t[key] = deepClone(obj[key]);
}
return t;
}

 22. 防抖和節流的區別是什麼?防抖和節流的實現。

防抖和節流的做用都是防止函數屢次調用。區別在於,假設一個用戶一直觸發這個函數,且每次觸發函數的間隔小於設置的時間,防抖的狀況下只會調用一次,而節流的狀況會每隔必定時間調用一次函數。

防抖 (debounce): n 秒內函數只會執行一次,若是 n 秒內高頻事件再次被觸發,則從新計算時間。

function debounce(func, wait, immediate=true) {
let timeout, context, args;
// 延遲執行函數
const later = () => setTimeout(() => {
// 延遲函數執行完畢,清空定時器
timeout = null
// 延遲執行的狀況下,函數會在延遲函數中執行
// 使用到以前緩存的參數和上下文
            if (!immediate) {
func.apply(context, args);
context = args = null;
}
}, wait);
let debounced = function (...params) {
            if (!timeout) {
timeout = later();
if (immediate) {
// 當即執行
func.apply(this, params);
} else {
// 閉包
context = this;
args = params;
}
} else {
clearTimeout(timeout);
timeout = later();
}
}
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};

 防抖的應用場景

  • 每次 resize/scroll 觸發統計事件;

  • 文本輸入的驗證(連續輸入文字後發送 AJAX 請求進行驗證,驗證一次就好)。

節流 (throttle): 高頻事件在規定時間內只會執行一次,執行一次後,只有大於設定的執行週期後纔會執行第二次。

//underscore.js
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
    if (!options) options = {};

var later = function () {
        previous = options.leading === false ? 0 : Date.now() || new Date().getTime();
timeout = null;
result = func.apply(context, args);
        if (!timeout) context = args = null;
};

var throttled = function () {
var now = Date.now() || new Date().getTime();
        if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
// 判斷是否設置了定時器和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};

throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};

return throttled;
};

函數節流的應用場景有:

  • DOM 元素的拖拽功能實現(mousemove);

  • 射擊遊戲的 mousedown/keydown 事件(單位時間只能發射一顆子彈);

  • 計算鼠標移動的距離(mousemove);

  • Canvas 模擬畫板功能(mousemove);

  • 搜索聯想(keyup);

  • 監聽滾動事件判斷是否到頁面底部自動加載更多:給 scroll 加了 debounce 後,只有用戶中止滾動後,纔會判斷是否到了頁面底部;若是是 throttle 的話,只要頁面滾動就會間隔一段時間判斷一次。

     

 23. 取數組的最大值(ES五、ES6)

// ES5 的寫法
Math.max.apply(null, [14, 3, 77, 30]);

// ES6 的寫法
Math.max(...[14, 3, 77, 30]);

// reduce
[14,3,77,30].reduce((accumulator, currentValue)=>{
    return accumulator = accumulator > currentValue ? accumulator : currentValue
});
 
 24.ES6 新的特性有哪些?

1. 新增了塊級做用域 (let,const)

2. 提供了定義類的語法糖 (class)

3. 新增了一種基本數據類型 (Symbol)

4. 新增了變量的解構賦值

5. 函數參數容許設置默認值,引入了 rest 參數,新增了箭頭函數

6. 數組新增了一些 API,如 isArray / from / of 方法 ; 數組實例新增了 entries(),keys() 和 values() 等方法

7. 對象和數組新增了擴展運算符

8.ES6 新增了模塊化 (import/export)

9.ES6 新增了 Set 和 Map 數據結構

10.ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例

11.ES6 新增了生成器 (Generator) 和遍歷器 (Iterator)

 25.setTimeout 倒計時爲何會出現偏差?

setTimeout() 只是將事件插入了「任務隊列」,必須等當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。要是當前代碼消耗時間很長,也有可能要等好久,因此並沒辦法保證回調函數必定會在 setTimeout() 指定的時間執行。因此, setTimeout() 的第二個參數表示的是最少時間,並不是是確切時間。

HTML5 標準規定了 setTimeout() 的第二個參數的最小值不得小於 4 毫秒,若是低於這個值,則默認是 4 毫秒。在此以前。老版本的瀏覽器都將最短期設爲 10 毫秒。另外,對於那些 DOM 的變更(尤爲是涉及頁面從新渲染的部分),一般是間隔 16 毫秒執行。這時使用 requestAnimationFrame() 的效果要好於 setTimeout()。

 26. 爲何 0.1 + 0.2 != 0.3 ?

0.1 + 0.2 != 0.3 是由於在進制轉換和進階運算的過程當中出現精度損失。

下面是詳細解釋:

JavaScript 使用 Number 類型表示數字 (整數和浮點數),使用 64 位表示一個數字。

圖片說明:

  • 第 0 位:符號位,0 表示正數,1 表示負數 (s);

  • 第 1 位到第 11 位:儲存指數部分(e);

  • 第 12 位到第 63 位:儲存小數部分(即有效數字)f;

計算機沒法直接對十進制的數字進行運算, 須要先對照 IEEE 754 規範轉換成二進制,而後對階運算。

 1. 進制轉換

0.1 和 0.2 轉換成二進制後會無限循環

0.1 -> 0.0001100110011001...(無限循環)
0.2 -> 0.0011001100110011...(無限循環)

可是因爲 IEEE 754 尾數位數限制,須要將後面多餘的位截掉,這樣在進制之間的轉換中精度已經損失。

 2. 對階運算

因爲指數位數不相同,運算時須要對階運算 這部分也可能產生精度損失。

按照上面兩步運算(包括兩步的精度損失),最後的結果是

0.0100110011001100110011001100110011001100110011001100

結果轉換成十進制以後就是 0.30000000000000004。

 27.promise 有幾種狀態, Promise 有什麼優缺點 ?

promise 有三種狀態: fulfilled, rejected, pending。

 Promise 的優勢

1 . 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果;

2 . 能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。

 Promise 的缺點

1. 沒法取消 Promise;

2. 當處於 pending 狀態時,沒法得知目前進展到哪個階段。

 28.Promise 構造函數是同步仍是異步執行,then 呢 ?promise 如何實現 then 處理 ?

Promise 的構造函數是同步執行的。then 是異步執行的。

promise 的 then 實現,詳見: Promise 源碼實現:

https://juejin.im/post/5c88e427f265da2d8d6a1c84

 29.Promise 和 setTimeout 的區別 ?

Promise 是微任務,setTimeout 是宏任務,同一個事件循環中,promise.then 老是先於 setTimeout 執行。同一個事件循環中,promise.then 先於 setTimeout 執行。

 30. 如何實現 Promise.all ?

要實現 Promise.all, 首先咱們須要知道 Promise.all 的功能:

1. 若是傳入的參數是一個空的可迭代對象,那麼此 promise 對象回調完成 (resolve), 只有此狀況,是同步執行的,其它都是異步返回的;

2. 若是傳入的參數不包含任何 promise,則返回一個異步完成.promises 中全部的 promise 都「完成」時或參數中不包含 promise 時回調完成;

3. 若是參數中有一個 promise 失敗,那麼 Promise.all 返回的 promise 對象失敗;

4. 在任何狀況下,Promise.all 返回的 promise 的完成狀態的結果都是一個數組。

Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
setTimeout(() => {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
//promises[i] 多是普通值
Promise.resolve(promises[i]).then((data) => {
processValue(i, data);
}, (err) => {
reject(err);
return;
});
}
})
}
});
}

若是想了解更多 Promise 的源碼實現,能夠參考個人另外一篇文章:Promise 的源碼實現(完美符合 Promise/A+ 規範):

https://juejin.im/post/5c88e427f265da2d8d6a1c84#heading-24

 31. 如何實現 Promise.finally ?

無論成功仍是失敗,都會走到 finally 中, 而且 finally 以後,還能夠繼續 then。而且會將值原封不動的傳遞給後面的 then。

Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}

 32. 什麼是函數柯里化?實現 sum(1)(2)(3) 返回結果是 1,2,3 之和

函數柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。

function sum(a) {
return function(b) {
return function(c) {
return a+b+c;
}
}
}
console.log(sum(1)(2)(3)); // 6

引伸:實現一個 curry 函數,將普通函數進行柯里化:

function curry(fn, args = []) {    return function(){        let rest = [...args, ...arguments];        if (rest.length < fn.length) {            return curry.call(this,fn,rest);        }else{            return fn.apply(this,rest);        }    }}//testfunction sum(a,b,c) {    return a+b+c;}let sumFn = curry(sum);console.log(sumFn(1)(2)(3)); //6console.log(sumFn(1)(2, 3)); //6
相關文章
相關標籤/搜索