天天看點

前端工程師常見面試題(前端基礎)——JavaScript

get 請求傳參長度的誤區

參考回答:

誤區: 我們經常說 get 請求參數的大小存在限制, 而 post 請求的參數大小是無限制的 。 實際上 HTTP 協定從未規定 GET/POST 的請求長度限制是多少。對 get 請求參數的限制 是來源與浏覽器或 web 伺服器, 浏覽器或 web 伺服器限制了url 的長度 。為了明确這個概念, 我們必須再次強調下面幾點:

HTTP 協定 未規定 GET 和 POST 的長度限制

GET 的最大長度顯示是因為 浏覽器和 web 伺服器限制了 URI 的長度 不同的浏覽器和 WEB 伺服器, 限制的最大長度不一樣

要支援 IE, 則最大長度為 2083byte, 若隻支援 Chrome, 則最大長度 8182byte

補充 get 和 post 請求在緩存方面的差別

參考回答:

post/get 的請求差別, 具體不再贅述。

補充補充一個 get 和 post 在緩存方面的差別:

get 請求類似于查找的過程, 使用者擷取資料, 可以不用每次都與資料庫連接配接, 是以可以 使用緩存。

post 不同, post 做的一般是修改和删除的工作, 是以必須與資料庫互動, 是以不能使用 緩存 。 是以 get 請求适合于請求緩存。

說一下閉包

參考回答:

一句話可以概括: 閉包就是能夠讀取其他函數内部變量的函數, 或者子函數在外調用, 子函數所在的父函數的作用域不會被釋放。

說一下類的建立和繼承

參考回答:

( 1) 類的建立 (es5) :new 一個 function,在這個 function 的 prototype 裡面增加屬性和 方法。

下面來建立一個 Animal 類:

// 定義一個動物類

function Animal (name) {

// 屬性

this.name = name || 'Animal';

// 執行個體方法

this.sleep = function(){

console.log(this.name + '正在睡覺! ');

}

}

// 原型方法

Animal.prototype.eat = function(food) {

console.log(this.name + '正在吃: ' + food);

};           

這樣就生成了一個 Animal 類, 實力化生成對象後, 有方法和屬性。

( 2) 類的繼承——原型鍊繼承

function Cat(){ }

Cat.prototype = new Animal();

Cat.prototype.name = 'cat';

// Test Code

var cat = new Cat();

console.log(cat.name);

console.log(cat.eat('fish'));

console.log(cat.sleep());

console.log(cat instanceof Animal); //true

console.log(cat instanceof Cat); //true           

介紹:在這裡我們可以看到 new 了一個空對象,這個空對象指向 Animal 并且 Cat.prototype 指向了這個空對象, 這種就是基于原型鍊的繼承。

特點: 基于原型鍊, 既是父類的執行個體, 也是子類的執行個體

缺點: 無法實作多繼承

( 3) 構造繼承: 使用父類的構造函數來增強子類執行個體, 等于是複制父類的執行個體屬性給 子類 (沒用到原型)

function Cat(name){

Animal.call(this);

this.name = name || 'Tom';

}

// Test Code

var cat = new Cat();

console.log(cat.name);

console.log(cat.sleep());

console.log(cat instanceof Animal); // false

console.log(cat instanceof Cat); // true           

特點: 可以實作多繼承

缺點: 隻能繼承父類執行個體的屬性和方法, 不能繼承原型上的屬性和方法。 ( 4) 執行個體繼承和拷貝繼承

執行個體繼承: 為父類執行個體添加新特性, 作為子類執行個體傳回

拷貝繼承: 拷貝父類元素上的屬性和方法

上述兩個實用性不強, 不一一舉例。

( 5) 組合繼承: 相當于構造繼承和原型鍊繼承的組合體 。通過調用父類構造, 繼承父 類的屬性并保留傳參的優點, 然後通過将父類執行個體作為子類原型, 實作函數複用。

function Cat(name){

Animal.call(this);

this.name = name || 'Tom';

}

Cat.prototype = new Animal();

Cat.prototype.constructor = Cat;

// Test Code

var cat = new Cat();

console.log(cat.name);

console.log(cat.sleep());

console.log(cat instanceof Animal); // true

console.log(cat instanceof Cat); // true           

特點: 可以繼承執行個體屬性/方法, 也可以繼承原型屬性/方法

缺點: 調用了兩次父類構造函數, 生成了兩份執行個體

( 6) 寄生組合繼承: 通過寄生方式, 砍掉父類的執行個體屬性, 這樣, 在調用兩次父類的 構造的時候, 就不會初始化兩次執行個體方法/屬性。

function Cat(name){

Animal.call(this);

this.name = name || 'Tom';

}

(function(){

// 建立一個沒有執行個體方法的類

var Super = function(){};

Super.prototype = Animal.prototype;

//将執行個體作為子類的原型

Cat.prototype = new Super();

})();

// Test Code

var cat = new Cat();

console.log(cat.name);

console.log(cat.sleep());

console.log(cat instanceof Animal); // true

console.log(cat instanceof Cat); //true           

較為推薦

如何解決異步回調地獄

參考回答:

1 promise 、generator 、async/await

說說前端中的事件流

參考回答:

HTML 中與 javascript 互動是通過事件驅動來實作的, 例如滑鼠點選事件 onclick 、頁面 的滾動事件 onscroll 等等, 可以向文檔或者文檔中的元素添加事件偵聽器來預訂事件。 想要知道這些事件是在什麼時候進行調用的, 就需要了解一下“事件流”的概念。

什麼是事件流:事件流描述的是從頁面中接收事件的順序,DOM2 級事件流包括下面幾個 階段。

事件捕獲階段

處于目标階段

事件冒泡階段

addEventListener: addEventListener 是 DOM2 級事件新增的指定事件處理程式的操作, 這個方法接收 3 個參數:要處理的事件名、作為事件處理程式的函數和一個布爾值。最 後這個布爾值參數如果是true,表示在捕獲階段調用事件處理程式; 如果是false,表示 在冒泡階段調用事件處理程式。

IE 隻支援事件冒泡。

如何讓事件先冒泡後捕獲

參考回答:

在 DOM 标準事件模型中, 是先捕獲後冒泡 。但是如果要實作先冒泡後捕獲的效果, 對 于同一個事件,監聽捕獲和冒泡,分别對應相應的處理函數,監聽到捕獲事件,先暫緩 執行, 直到冒泡事件被捕獲後再執行捕獲之間。

說一下事件委托

參考回答:

簡介: 事件委托指的是,不在事件的發生地 (直接 dom) 上設定監聽函數, 而是在其父 元素上設定監聽函數,通過事件冒泡,父元素可以監聽到子元素上事件的觸發,通過判 斷事件發生元素 DOM 的類型, 來做出不同的響應。

舉例: 最經典的就是 ul 和 li 标簽的事件監聽, 比如我們在添加事件時候, 采用事件委 托機制, 不會在 li 标簽上直接添加, 而是在 ul 父元素上添加。

好處: 比較合适動态元素的綁定,新添加的子元素也會有監聽函數,也可以有事件觸發 機制。

說一下圖檔的懶加載和預加載

參考回答:

預加載: 提前加載圖檔, 當使用者需要檢視時可直接從本地緩存中渲染。

懶加載: 懶加載的主要目的是作為伺服器前端的優化, 減少請求數或延遲請求數。

兩種技術的本質: 兩者的行為是相反的, 一個是提前加載, 一個是遲緩甚至不加載。 懶加載對伺服器前端有一定的緩解壓力作用, 預加載則會增加伺服器前端壓力。

mouseover 和 mouseenter 的差別

參考回答:

mouseover:當滑鼠移入元素或其子元素都會觸發事件,是以有一個重複觸發, 冒泡的過 程 。對應的移除事件是mouseout

mouseenter: 當滑鼠移除元素本身 (不包含元素的子元素) 會觸發事件, 也就是不會冒 泡, 對應的移除事件是 mouseleave

JS 的 new 操作符做了哪些事情

參考回答:

new 操作符建立了一個空對象,這個對象原型指向構造函數的prototype,執行構造函數 後傳回這個對象。

改變函數内部 this 指針的指向函數 (bind, apply, call 的差別)

參考回答:

通過 apply 和 call 改變函數的 this 指向, 他們兩個函數的第一個參數都是一樣的表示要 改變指向的那個對象,第二個參數,apply 是數組,而 call 則是 arg1,arg2...這種形式。通 過 bind 改變 this 作用域會傳回一個新的函數, 這個函數不會馬上執行。

JS 的各種位置, 比如 clientHeight,scrollHeight,offsetHeight , 以及 scrollTop,

offsetTop,clientTop 的差別?

參考回答:

clientHeight: 表示的是可視區域的高度, 不包含 border 和滾動條

offsetHeight: 表示可視區域的高度, 包含了 border 和滾動條

scrollHeight: 表示了所有區域的高度, 包含了因為滾動被隐藏的部分。

clientTop: 表示邊框 border 的厚度, 在未指定的情況下一般為 0

scrollTop: 滾動後被隐藏的高度, 擷取對象相對于由 offsetParent 屬性指定的父坐标(css 定位的元素或 body 元素)距離頂端的高度。

JS 拖拽功能的實作

參考回答:

首先是三個事件, 分别是 mousedown, mousemove, mouseup

當滑鼠點選按下的時候, 需要一個 tag 辨別此時已經按下, 可以執行 mousemove 裡面的 具體方法。

clientX, clientY 辨別的是滑鼠的坐标, 分别辨別橫坐标和縱坐标, 并且我們用 offsetX 和 offsetY 來表示元素的元素的初始坐标, 移動的舉例應該是:

滑鼠移動時候的坐标-滑鼠按下去時候的坐标。

也就是說定位資訊為:

滑鼠移動時候的坐标-滑鼠按下去時候的坐标+元素初始情況下的offetLeft.

還有一點也是原理性的東西,也就是拖拽的同時是絕對定位,我們改變的是絕對定位條 件下的 left

以及 top 等等值。

補充: 也可以通過 html5 的拖放 ( Drag 和 drop) 來實作

異步加載 JS 的方法

參考回答:

defer: 隻支援 IE 如果您的腳本不會改變文檔的内容, 可将 defer 屬性加入到<script>标 簽中, 以便加快處理文檔的速度 。因為浏覽器知道它将能夠安全地讀取文檔的剩餘部分 而不用執行腳本, 它将推遲對腳本的解釋, 直到文檔已經顯示給使用者為止。

async, HTML5 屬性僅适用于外部腳本, 并且如果在 IE 中, 同時存在 defer 和 async,那 麼 defer 的優先級比較高, 腳本将在頁面完成時執行。

建立 script 标簽, 插入到 DOM 中

Ajax 解決浏覽器緩存問題

參考回答:

在 ajax 發送請求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。

在 ajax 發送請求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。

在 URL 後面加上一個随機數: "fresh=" + Math.random()。

在 URL 後面加上時間搓: "nowtime=" + new Date().getTime()。

如果是使用 jQuery, 直接這樣就可以了 $.ajaxSetup({cache:false}) 。這樣頁面的所有 ajax 都會執行這條語句就是不需要儲存緩存記錄。

JS 的節流和防抖

參考回答:

JS 中的垃圾回收機制

參考回答:

必要性: 由于字元串、對象和數組沒有固定大小,所有當他們的大小已知時,才能對他 們進行動态的存儲配置設定 。JavaScript 程式每次建立字元串 、數組或對象時, 解釋器都必 須配置設定記憶體來存儲那個實體。隻要像這樣動态地配置設定了記憶體,最終都要釋放這些記憶體以 便他們能夠被再用, 否則, JavaScript 的解釋器将會消耗完系統中所有可用的記憶體, 造成系統崩潰。

這段話解釋了為什麼需要系統需要垃圾回收,JS 不像C/C++,他有自己的一套垃圾回收 機制 ( Garbage Collection) 。JavaScript 的解釋器可以檢測到何時程式不再使用一個對象 了, 當他确定了一個對象是無用的時候,他就知道不再需要這個對象,可以把它所占用 的記憶體釋放掉了 。例如:

var a="hello world";

var b="world";

var a=b;

//這時, 會釋放掉"hello world", 釋放記憶體以便再引用

垃圾回收的方法: 标記清除 、計數引用。

标記清除

這是最常見的垃圾回收方式,當變量進入環境時,就标記這個變量為”進入環境“,從邏 輯上講,永遠不能釋放進入環境的變量所占的記憶體,永遠不能釋放進入環境變量所占用 的記憶體, 隻要執行流程進入相應的環境,就可能用到他們 。當離開環境時,就标記為離 開環境。

垃圾回收器在運作的時候會給存儲在記憶體中的變量都加上标記 (所有都加) ,然後去掉 環境變量中的變量, 以及被環境變量中的變量所引用的變量 (條件性去除标記) ,删除 所有被标記的變量,删除的變量無法在環境變量中被通路是以會被删除,最後垃圾回收 器, 完成了記憶體的清除工作, 并回收他們所占用的記憶體。

引用計數法

另一種不太常見的方法就是引用計數法, 引用計數法的意思就是每個值沒引用的次數, 當聲明了一個變量, 并用一個引用類型的值指派給改變量, 則這個值的引用次數為 1,; 相反的,如果包含了對這個值引用的變量又取得了另外一個值,則原先的引用值引用次 數就減 1, 當這個值的引用次數為 0 的時候, 說明沒有辦法再通路這個值了, 是以就把 所占的記憶體給回收進來,這樣垃圾收集器再次運作的時候,就會釋放引用次數為 0 的這 些值。

用引用計數法會存在記憶體洩露, 下面來看原因:

function problem() {

var objA = new Object();

var objB = new Object();

objA.someOtherObject = objB;

objB.anotherObject = objA;

}

在這個例子裡面, objA 和 objB 通過各自的屬性互相引用, 這樣的話, 兩個對象的引用 次數都為 2, 在采用引用計數的政策中, 由于函數執行之後, 這兩個對象都離開了作用 域, 函數執行完成之後, 因為計數不為 0, 這樣的互相引用如果大量存在就會導緻記憶體 洩露。

特别是在 DOM 對象中, 也容易存在這種問題:

var element=document.getElementById ( ’‘ ) ;

var myObj=new Object();

myObj.element=element;

element.someObject=myObj;

這樣就不會有垃圾回收的過程。

eval 是做什麼的

參考回答:

它的功能是将對應的字元串解析成 JS 并執行,應該避免使用 JS, 因為非常消耗性能 ( 2 次, 一次解析成 JS, 一次執行)

如何了解前端子產品化

參考回答:

前端子產品化就是複雜的檔案程式設計一個一個獨立的子產品, 比如 JS 檔案等等, 分成獨立的 子產品有利于重用 (複用性) 和維護 (版本疊代) ,這樣會引來子產品之間互相依賴的問題, 是以有了 commonJS 規範, AMD, CMD 規範等等, 以及用于 JS 打包 (編譯等處理) 的 工具 webpack。

說一下 CommonJS 、AMD 和 CMD

參考回答:

一個子產品是能實作特定功能的檔案,有了子產品就可以友善的使用别人的代碼,想要什麼 功能就能加載什麼子產品。

CommonJS: 開始于伺服器端的子產品化, 同步定義的子產品化, 每個子產品都是一個單獨的 作用域, 子產品輸出, modules.exports, 子產品加載 require()引入子產品。

AMD: 中文名異步子產品定義的意思。

requireJS 實作了 AMD 規範, 主要用于解決下述兩個問題 。 1.多個檔案有依賴關系, 被依賴的檔案需要早于依賴它的檔案加載到浏覽器

2.加載的時候浏覽器會停止頁面渲染, 加載檔案越多, 頁面失去響應的時間越長。 文法: requireJS 定義了一個函數 define, 它是全局變量, 用來定義子產品 。 requireJS 的例子:

//定義子產品

define(['dependency'], function(){

var name = 'Byron';

function printName(){

console.log(name);

}

return {

printName: printName

};

});

//加載子產品

require(['myModule'], function (my){

my.printName();

}

RequireJS 定義了一個函數 define,它是全局變量, 用來定義子產品:

define(id?dependencies?,factory)           

在頁面上使用子產品加載函數:

require([dependencies],factory);

總結 AMD 規範: require () 函數在加載依賴函數的時候是異步加載的, 這樣浏覽器不 會失去響應, 它指定的回調函數, 隻有前面的子產品加載成功, 才會去執行。

因為網頁在加載 JS 的時候會停止渲染, 是以我們可以通過異步的方式去加載 JS,而如果 需要依賴某些, 也是異步去依賴, 依賴後再執行某些方法。

對象深度克隆的簡單實作

參考回答:

function deepClone(obj){

var newObj= obj instanceof Array ? []:{};

for(var item in obj){

var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item]; newObj[item] = temple;

}

return newObj;

}           

ES5 的常用的對象克隆的一種方式 。注意數組是對象, 但是跟對象又有一定差別,是以 我們一開始判斷了一些類型, 決定 newObj 是對象還是數組。

實作一個 once 函數, 傳入函數參數隻執行一次

參考回答:

function ones(func){

var tag=true;

return function(){

if(tag==true){

func.apply(null,arguments);

tag=false;

}

return undefined

}

}

           

将原生的 ajax 封裝成 promise

參考回答:

var myNewAjax=function(url){

return new Promise(function(resolve,reject){

var xhr = new XMLHttpRequest();

xhr.open('get',url);

xhr.send(data);

xhr.onreadystatechange=function(){

if(xhr.status==200&&readyState==4){

var json=JSON.parse(xhr.responseText);

resolve(json)

}else if(xhr.readyState==4&&xhr.status!=200){

reject('error');

}

}

})

}           

JS 監聽對象屬性的改變

參考回答:

我們假設這裡有一個 user 對象,

(1)在 ES5 中可以通過 Object.defineProperty 來實作已有屬性的監聽

Object.defineProperty(user,'name',{

set: function(key,value){

}

})           

缺點: 如果 id 不在 user 對象中, 則不能監聽 id 的變化

(2)在 ES6 中可以通過 Proxy 來實作

var user = new Proxy({}, {

set: function(target,key,value,receiver){

}

})           

這樣即使有屬性在user 中不存在, 通過 user.id 來定義也同樣可以這樣監聽這個屬性的 變化哦。

如何實作一個私有變量, 用 getName 方法可以通路, 不能直接通路

參考回答:

(1)通過 defineProperty 來實作

obj={

name:yuxiaoliang,

getName:function(){

return this.name

}

}

object.defineProperty(obj,"name",{

//不可枚舉不可配置

});           

(2)通過函數的建立形式

function product(){

var name='yuxiaoliang';

this.getName=function(){

return name;

}

}

var obj=new product();
           

==和=== 、 以及 Object.is 的差別

參考回答:

(1) ==

主要存在: 強制轉換成 number,null==undefined

" "==0 //true

"0"==0 //true

" " !="0" //true

123=="123" //true

null==undefined //true

(2)Object.js

主要的差別就是+0! =-0 而 NaN==NaN

(相對比===和==的改進)

setTimeout 、setInterval 和 requestAnimationFrame 之間的差別

參考回答:

這裡有一篇文章講的是 requestAnimationFrame:

與 setTimeout 和 setInterval 不同, requestAnimationFrame 不需要設定時間間隔,

大多數電腦顯示器的重新整理頻率是 60Hz,大概相當于每秒鐘重繪 60 次。大多數浏覽器都 會對重繪操作加以限制,不超過顯示器的重繪頻率, 因為即使超過那個頻率使用者體驗也 不會有提升 。 是以, 最平滑動畫的最佳循環間隔是 1000ms/60, 約等于 16.6ms。

RAF 采用的是系統時間間隔, 不會因為前面的任務, 不會影響 RAF, 但是如果前面的 任務多的話,

會響應 setTimeout 和 setInterval 真正運作時的時間間隔。

特點:

( 1) requestAnimationFrame 會把每一幀中的所有 DOM 操作集中起來,在一次重繪或回 流中就完成, 并且重繪或回流的時間間隔緊緊跟随浏覽器的重新整理頻率。

( 2) 在隐藏或不可見的元素中,requestAnimationFrame 将不會進行重繪或回流,這當然 就意味着更少的 CPU 、GPU 和記憶體使用量

( 3) requestAnimationFrame 是由浏覽器專門為動畫提供的 API,在運作時浏覽器會自動 優化方法的調用, 并且如果頁面不是激活狀态下的話, 動畫會自動暫停, 有效節省了 CPU 開銷。

實作一個兩列等高布局, 講講思路

參考回答:

為了實作兩列等高, 可以給每列加上 padding-bottom:9999px;

margin-bottom:-9999px;同時父元素設定 overflow:hidden;

自己實作一個 bind 函數

參考回答:

原理: 通過 apply 或者 call 方法來實作。

(1)初始版本

Function.prototype.bind=function(obj,arg){

var arg=Array.prototype.slice.call(arguments,1);

var context=this;

return function(newArg){

arg=arg.concat(Array.prototype.slice.call(newArg));

return context.apply(obj,arg);

}

}           

(2) 考慮到原型鍊

為什麼要考慮? 因為在new 一個 bind 過生成的新函數的時候, 必須的條件是要繼承原 函數的原型

Function.prototype.bind=function(obj,arg){

var arg=Array.prototype.slice.call(arguments,1);

var context=this;

var bound=function(newArg){

arg=arg.concat(Array.prototype.slice.call(newArg));

return context.apply(obj,arg);

}

var F=function(){}

//這裡需要一個寄生組合繼承

F.prototype=context.prototype;

bound.prototype=new F();

return bound;

}           

用 setTimeout 來實作 setInterval

參考回答:

(1)用 setTimeout()方法來模拟 setInterval()與 setInterval()之間的什麼差別?

首先來看 setInterval 的缺陷, 使用 setInterval()建立的定時器確定了定時器代碼規則地插 入隊列中 。這個問題在于: 如果定時器代碼在代碼再次添加到隊列之前還沒完成執行, 結果就會導緻定時器代碼連續運作好幾次 。而之間沒有間隔 。不過幸運的是:javascript引擎足夠聰明,能夠避免這個問題 。當且僅當沒有該定時器的如何代碼執行個體時,才會将定時器代碼添加到隊列中。這確定了定時器代碼加入隊列中最小的時間間隔為指定時間。 這種重複定時器的規則有兩個問題:1.某些間隔會被跳過 2.多個定時器的代碼執行時間 可能會比預期小。

下面舉例子說明:

假設,某個 onclick 事件處理程式使用啦 setInterval()來設定了一個 200ms 的重複定時器。 如果事件處理程式花了 300ms 多一點的時間完成。

<img width="626" alt="2018-07-10 11 36 43" src="https://user-images.githubusercontent.co m/17233651/42487876-92656f2c-8435-11e8-8a5f-0a97918039da.png">           

這個例子中的第一個定時器是在 205ms 處添加到隊列中,但是要過 300ms 才能執行。在 405ms 又添加了一個副本 。在一個間隔, 605ms 處, 第一個定時器代碼還在執行中, 而 且隊列中已經有了一個定時器執行個體,結果是 605ms 的定時器代碼不會添加到隊列中。結 果是在 5ms 處添加的定時器代碼執行結束後, 405 處的代碼立即執行。

function say(){

//something

setTimeout(say,200);

}           

或者

setTimeout(function(){

//do something

setTimeout(arguments.callee,200);

},200);           

JS 怎麼控制一次加載一張圖檔, 加載完後再加載下一張

參考回答:

(1)方法 1

<script type="text/javascript">

var obj=new Image();

obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg"; obj.onload=function(){

alert('圖檔的寬度為: '+obj.width+'; 圖檔的高度為: '+obj.height);

document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />"; }

</script>

<div id="mypic">onloading……</div>           

(2)方法 2

<script type="text/javascript">

var obj=new Image();

obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg"; obj.onreadystatechange=function(){

if(this.readyState=="complete"){

alert('圖檔的寬度為: '+obj.width+'; 圖檔的高度為: '+obj.height);

document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />"; }

}

</script>

<div id="mypic">onloading……</div>           

代碼的執行順序

參考回答:

setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){

console.log(2);

resolve();

}).then(function(){console.log(3)

}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

console.log(6);

//輸出 2,6,5,3,4,1           

為什麼呢?具體請參考這篇文章:

如何實作 sleep 的效果 (es5 或者 es6)

參考回答:

(1)while 循環的方式

function sleep(ms){

var start=Date.now(),expire=start+ms;

while(Date.now()<expire);

console.log('1111');

return;

}           

執行 sleep(1000)之後, 休眠了 1000ms 之後輸出了 1111 。上述循環的方式缺點很明顯, 容易造成死循環。

(2)通過 promise 來實作

function sleep(ms){

var temple=new Promise(

(resolve)=>{

console.log(111);setTimeout(resolve,ms)

});

return temple

}

sleep(500).then(function(){

//console.log(222)

})

//先輸出了 111, 延遲 500ms 後輸出 222           

(3)通過 async 封裝

function sleep(ms){

return new Promise((resolve)=>setTimeout(resolve,ms));

}

async function test(){

var temple=await sleep(1000);

console.log(1111)

return temple

}

test();

//延遲 1000ms 輸出了 1111           

(4).通過 generate 來實作

function* sleep(ms){

yield new Promise(function(resolve,reject){

console.log(111);

setTimeout(resolve,ms);

})

}

sleep(500).next().value.then(function(){console.log(2222)})           

簡單的實作一個 promise

參考回答:

A+規範

如何實作一個 promise, 參考這篇文章:

一般不會問的很詳細, 隻要能寫出上述文章中的 v1.0 版本的簡單 promise 即可。

Function._proto_(getPrototypeOf)是什麼?

參考回答:

擷取一個對象的原型, 在 chrome 中可以通過_proto_的形式, 或者在 ES6 中可以通過 Object.getPrototypeOf 的形式。

那麼 Function.proto 是什麼麼?也就是說 Function 由什麼對象繼承而來,我們來做如下判 别。

Function.__proto__==Object.prototype //false

Function.__proto__==Function.prototype//true

我們發現 Function 的原型也是 Function。

我們用圖可以來明确這個關系:

<img width="646" alt="2018-07-10 2 38 27" src="https://user-images.githubusercontent.com/ 17233651/42493275-f55d0860-844e-11e8-983f-e04189a4f3d8.png">           

實作 JS 中所有對象的深度克隆 (包裝對象, Date 對象, 正則對象)

參考回答:

通過遞歸可以簡單實作對象的深度克隆,但是這種方法不管是 ES6 還是 ES5 實作,都有 同樣的缺陷, 就是隻能實作特定的 object 的深度複制 (比如數組和函數) , 不能實作包 裝對象 Number, String , Boolean, 以及 Date 對象, RegExp 對象的複制。

(1)前文的方法

function deepClone(obj){

var newObj= obj instanceof Array?[]:{};

for(var i in obj){

newObj[i]=typeof obj[i]=='object'?

deepClone(obj[i]):obj[i];

}

return newObj;

}           

這種方法可以實作一般對象和數組對象的克隆, 比如:

var arr=[1,2,3];

var newArr=deepClone(arr);

// newArr->[1,2,3]

var obj={

x:1,

y:2

}

var newObj=deepClone(obj);

// newObj={x:1,y:2}           

但是不能實作例如包裝對象 Number,String,Boolean, 以及正則對象 RegExp 和 Date 對象的 克隆, 比如:

//Number 包裝對象

var num=new Number(1);

typeof num // "object"

var newNum=deepClone(num);

//newNum -> {} 空對象

//String 包裝對象

var str=new String("hello");

typeof str //"object"

var newStr=deepClone(str);

//newStr-> {0:'h',1:'e',2:'l',3:'l',4:'o'};

//Boolean 包裝對象

var bol=new Boolean(true);

typeof bol //"object"

var newBol=deepClone(bol);

// newBol ->{} 空對象

....           

(2)valueof()函數

所有對象都有 valueOf 方法,valueOf 方法對于: 如果存在任意原始值, 它就預設将對象 轉換為表示它的原始值 。對象是複合值, 而且大多數對象無法真正表示為一個原始值, 是以預設的 valueOf()方法簡單地傳回對象本身, 而不是傳回一個原始值。數組 、函數和 正規表達式簡單地繼承了這個預設方法,調用這些類型的執行個體的 valueOf()方法隻是簡單 傳回這個對象本身。

對于原始值或者包裝類:

function baseClone(base){

return base.valueOf();

}

//Number

var num=new Number(1);

var newNum=baseClone(num);

//newNum->1

//String

var str=new String('hello');

var newStr=baseClone(str);

// newStr->"hello"

//Boolean

var bol=new Boolean(true);

var newBol=baseClone(bol);

//newBol-> true           

其實對于包裝類, 完全可以用=号來進行克隆, 其實沒有深度克隆一說, 這裡用valueOf 實作, 文法上比較符合規範。

對于 Date 類型:

因為 valueOf 方法, 日期類定義的 valueOf()方法會傳回它的一個内部表示: 1970 年 1 月 1 日以來的毫秒數. 是以我們可以在 Date 的原型上定義克隆的方法:

Date.prototype.clone=function(){

return new Date(this.valueOf());

}

var date=new Date('2010');

var newDate=date.clone();

// newDate-> Fri Jan 01 2010 08:00:00 GMT+0800           

對于正則對象 RegExp:

RegExp.prototype.clone = function() {

var pattern = this.valueOf();

var flags = '';

flags += pattern.global ? 'g' : '';

flags += pattern.ignoreCase ? 'i' : '';

flags += pattern.multiline ? 'm' : '';

return new RegExp(pattern.source, flags);

};

var reg=new RegExp('/111/');

var newReg=reg.clone();

//newReg-> /\/111\//           

簡單實作 Node 的 Events 子產品

參考回答:

簡介:觀察者模式或者說訂閱模式,它定義了對象間的一種一對多的關系,讓多個觀察 者對象同時監聽某一個主題對象,當一個對象發生改變時,所有依賴于它的對象都将得 到通知。

node 中的 Events 子產品就是通過觀察者模式來實作的:

var events=require('events');

var eventEmitter=new events.EventEmitter();

eventEmitter.on('say',function(name){

console.log('Hello',name);

})

eventEmitter.emit('say','Jony yu');           

這樣, eventEmitter 發出say 事件, 通過 On 接收, 并且輸出結果, 這就是一個訂閱模式 的實作, 下面我們來簡單的實作一個 Events 子產品的 EventEmitter。

(1)實作簡單的 Event 子產品的 emit 和 on 方法

function Events(){

this.on=function(eventName,callBack){

if(!this.handles){

this.handles={};

}

if(!this.handles[eventName]){

this.handles[eventName]=[];

}

this.handles[eventName].push(callBack);

}

this.emit=function(eventName,obj){

if(this.handles[eventName]){

for(var i=0;o<this.handles[eventName].length;i++){

this.handles[eventName][i](obj);

}

}

}

return this;

}

           

這樣我們就定義了 Events, 現在我們可以開始來調用:

var events=new Events();

events.on('say',function(name){

console.log('Hello',nama)

});

events.emit('say','Jony yu');

//結果就是通過 emit 調用之後, 輸出了 Jonyyu           

(2)每個對象是獨立的

因為是通過 new 的方式, 每次生成的對象都是不相同的, 是以:

var event1=new Events();

var event2=new Events();

event1.on('say',function(){

console.log('Jony event1');

});

event2.on('say',function(){

console.log('Jony event2');

})

event1.emit('say');

event2.emit('say');

//event1 、event2 之間的事件監聽互相不影響

//輸出結果為'Jony event1' 'Jony event2'

           

箭頭函數中 this 指向舉例

參考回答:

var a=11;

function test2(){

this.a=22;

let b=()=>{console.log(this.a)}

b();

}

var x=new test2();

//輸出 22           

定義時綁定。

JS 判斷類型

參考回答:

判斷方法: typeof(), instanceof, Object.prototype.toString.call()等

參考回答:

push(), pop(), shift(), unshift(), splice(), sort(), reverse(), map()等

數組去重

參考回答:

法一: indexOf 循環去重

法二: ES6 Set 去重; Array.from(new Set(array))

法三:Object 鍵值對去重;把數組的值存成 Object 的 key 值,比如 Object[value1] = true, 在判斷另一個值的時候, 如果 Object[value2]存在的話, 就說明該值是重複的。

閉包有什麼用

參考回答:

( 1) 什麼是閉包:

閉包是指有權通路另外一個函數作用域中的變量的函數。

閉包就是函數的局部變量集合,隻是這些局部變量在函數傳回後會繼續存在。閉包就是 就是函數的“堆棧”在函數傳回後并不釋放,我們也可以了解為這些函數堆棧并不在棧 上配置設定而是在堆上配置設定 。當在一個函數内定義另外一個函數就會産生閉包。

( 2) 為什麼要用:

匿名自執行函數: 我們知道所有的變量, 如果不加上 var 關鍵字, 則預設的會添加到全 局對象的屬性上去,這樣的臨時變量加入全局對象有很多壞處, 比如:别的函數可能誤 用這些變量; 造成全局對象過于龐大, 影響通路速度(因為變量的取值是需要從原型鍊 上周遊的)。除了每次使用變量都是用var 關鍵字外,我們在實際情況下經常遇到這樣一 種情況, 即有的函數隻需要執行一次, 其内部變量無需維護, 可以用閉包。

結果緩存: 我們開發中會碰到很多情況, 設想我們有一個處理過程很耗時的函數對象, 每次調用都會花費很長時間,那麼我們就需要将計算出來的值存儲起來,當調用這個函 數的時候,首先在緩存中查找,如果找不到, 則進行計算,然後更新緩存并傳回值,如 果找到了,直接傳回查找到的值即可 。閉包正是可以做到這一點, 因為它不會釋放外部 的引用, 進而函數内部的值可以得以保留。

封裝: 實作類和繼承等。

事件代理在捕獲階段的實際應用

參考回答:

可以在父元素層面阻止事件向子元素傳播, 也可代替子元素執行某些操作。

去除字元串首尾空格

參考回答:

使用正則(^\s*)|(\s*$)即可

性能優化

參考回答:

減少 HTTP 請求

使用内容釋出網絡 (CDN)

添加本地緩存

壓縮資源檔案

将 CSS 樣式表放在頂部, 把 javascript 放在底部 (浏覽器的運作機制決定) 避免使用CSS 表達式

減少 DNS 查詢

使用外部 javascript 和 CSS

避免重定向

圖檔 lazyLoad

能來講講 JS 的語言特性嗎

參考回答:

運作在用戶端浏覽器上;

不用預編譯, 直接解析執行代碼;

是弱類型語言, 較為靈活;

與作業系統無關, 跨平台的語言;

腳本語言 、解釋性語言

如何判斷一個數組(講到 typeof 差點掉坑裡)

參考回答:

Object.prototype.call.toString()

instanceof

你說到typeof, 能不能加一個限制條件達到判斷條件

參考回答:

typeof 隻能判斷是 object,可以判斷一下是否擁有數組的方法

JS 實作跨域

參考回答:

JSONP: 通過動态建立 script, 再請求一個帶參網址實作跨域通信 。document.domain + iframe 跨域:兩個頁面都通過 js 強制設定 document.domain 為基礎主域,就實作了同域。 location.hash + iframe 跨域: a 欲與 b 跨域互相通信, 通過中間頁 c 來實作 。 三個頁面, 不同域之間利用 iframe 的 location.hash 傳值, 相同域之間直接 js 通路來通信。

window.name + iframe 跨域:通過 iframe 的 src 屬性由外域轉向本地域,跨域資料即由iframe 的 window.name 從外域傳遞到本地域。

postMessage 跨域: 可以跨域操作的 window 屬性之一。

CORS:服務端設定 Access-Control-Allow-Origin 即可,前端無須設定,若要帶 cookie 請 求, 前後端都需要設定。

代理跨域: 啟一個代理伺服器, 實作資料的轉發

參考

JS 基本資料類型

參考回答:

基本資料類型: undefined 、null 、number 、boolean 、string 、symbol

JS 深度拷貝一個元素的具體實作

參考回答:

var deepCopy = function(obj) {

if (typeof obj !== 'object') return;

var newObj = obj instanceof Array ? [] : {};

for (var key in obj) {

if (obj.hasOwnProperty(key)) {

newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]; }

}

return newObj;

}           

之前說了 ES6set 可以數組去重, 是否還有數組去重的方法

參考回答:

法一: indexOf 循環去重

法二:Object 鍵值對去重;把數組的值存成 Object 的 key 值,比如 Object[value1] = true, 在判斷另一個值的時候, 如果 Object[value2]存在的話, 就說明該值是重複的。

重排和重繪, 講講看

參考回答:

重繪 (repaint 或 redraw) : 當盒子的位置 、大小以及其他屬性, 例如顔色 、字型大小等 都确定下來之後,浏覽器便把這些原色都按照各自的特性繪制一遍,将内容呈現在頁面 上。重繪是指一個元素外觀的改變所觸發的浏覽器行為,浏覽器會根據元素的新屬性重 新繪制, 使元素呈現新的外觀。

觸發重繪的條件: 改變元素外觀屬性 。如: color, background-color 等。

注意:table 及其内部元素可能需要多次計算才能确定好其在渲染樹中節點的屬性值,比 同等元素要多花兩倍時間, 這就是我們盡量避免使用 table 布局頁面的原因之一。

重排 (重構/回流/reflow) : 當渲染樹中的一部分(或全部)因為元素的規模尺寸, 布局, 隐藏等改變而需要重新建構, 這就稱為回流(reflow) 。每個頁面至少需要一次回流, 就是 在頁面第一次加載的時候。

重繪和重排的關系:在回流的時候,浏覽器會使渲染樹中受到影響的部分失效,并重新 構造這部分渲染樹,完成回流後,浏覽器會重新繪制受影響的部分到螢幕中,該過程稱 為重繪 。是以, 重排必定會引發重繪, 但重繪不一定會引發重排。

JS 的全排列

參考回答:

function permutate(str) {

var result = [];

if(str.length > 1) {

var left = str[0];

var rest = str.slice(1, str.length);

var preResult = permutate(rest);

for(var i=0; i<preResult.length; i++) {

for(var j=0; j<preResult[i].length; j++) {

var tmp = preResult[i],slice(0, j) + left + preResult[i].slice(j, preResult[i].length);

result.push(tmp);

}

}

} else if (str.length == 1) {

return [str];

}

return result;

}           

跨域的原理

參考回答:

跨域,是指浏覽器不能執行其他網站的腳本。它是由浏覽器的同源政策造成的,是浏覽 器對 JavaScript 實施的安全限制, 那麼隻要協定 、域名 、端口有任何一個不同, 都被當 作是不同的域 。跨域原理, 即是通過各種方式, 避開浏覽器的安全限制。

不同資料類型的值的比較, 是怎麼轉換的, 有什麼規則

參考回答:

前端工程師常見面試題(前端基礎)——JavaScript

null == undefined 為什麼

參考回答:

要比較相等性之前, 不能将 null 和 undefined 轉換成其他任何值, 但 null == undefined 會傳回 true 。ECMAScript 規範中是這樣定義的。

this 的指向 哪幾種

參考回答:

預設綁定: 全局環境中, this 預設綁定到window。

隐式綁定: 一般地, 被直接對象所包含的函數調用時, 也稱為方法調用, this 隐式綁定 到該直接對象。

隐式丢失:隐式丢失是指被隐式綁定的函數丢失綁定對象,進而預設綁定到window。顯

式綁定: 通過 call() 、apply() 、bind()方法把對象綁定到 this 上, 叫做顯式綁定。

new 綁定: 如果函數或者方法調用之前帶有關鍵字new, 它就構成構造函數調用 。對于 this 綁定來說, 稱為 new 綁定 。 【 1】構造函數通常不使用return 關鍵字, 它們通常初始化新對象, 當構造函數的函數 體執行完畢時,它會顯式傳回。在這種情況下,構造函數調用表達式的計算結果就是這 個新對象的值 。 【 2】如果構造函數使用return 語句但沒有指定傳回值, 或者傳回一個原始值, 那麼這 時将忽略傳回值, 同時使用這個新對象作為調用結果 。 【 3】如果構造函數顯式地使用return 語句傳回一個對象, 那麼調用表達式的值就是這 個對象。

暫停死區

參考回答:

在代碼塊内, 使用let 、const 指令聲明變量之前, 該變量都是不可用的 。這在文法上, 稱為“暫時性死區”。

AngularJS 雙向綁定原理

參考回答:

Angular 将雙向綁定轉換為一堆 watch 表達式,然後遞歸這些表達式檢查是否發生過變化, 如果變了則執行相應的 watcher 函數(指 view 上的指令,如 ng-bind,ng-show 等或是{{}}) 。 等到model 中的值不再發生變化,也就不會再有 watcher 被觸發,一個完整的 digest 循環 就完成了。

Angular 中在 view 上聲明的事件指令, 如: ng-click 、ng-change 等, 會将浏覽器的事件 轉發給$scope 上相應的model 的響應函數 。等待相應函數改變model, 緊接着觸發髒檢 查機制重新整理view。

watch 表達式: 可以是一個函數 、可以是$scope 上的一個屬性名, 也可以是一個字元串 形式的表達式 。$watch 函數所監聽的對象叫做 watch 表達式 。watcher 函數: 指在view 上的指令 (ngBind, ngShow 、ngHide 等) 以及{{}}表達式, 他們所注冊的函數 。每一個

watcher 對象都包括: 監聽函數, 上次變化的值, 擷取監聽表達式的方法以及監聽表達 式, 最後還包括是否需要使用深度對比 (angular.equals())

寫一個深度拷貝

參考回答:

function clone( obj ) {

var copy;

switch( typeof obj ) {

case "undefined":

break;

case "number":

copy = obj - 0;

break;

case "string":

copy = obj + "";

break;

case "boolean":

copy = obj;

break;

case "object": //object 分為兩種情況 對象 ( Object) 和數組 (Array)

if(obj === null) {

copy = null;

} else {

if( Object.prototype.toString.call(obj).slice(8, -1) === "Array") {

copy = [];

for( var i = 0 ; i < obj.length ; i++ ) {

copy.push(clone(obj[i]));

}

} else {

copy = {};

for( var j in obj) {

copy[j] = clone(obj[j]);

}

}

}

break;

default:

copy = obj;

break;

}

return copy;

}           

履歷中提到了 requestAnimationFrame, 請問是怎麼使用的

參考回答:

requestAnimationFrame() 方法告訴浏覽器您希望執行動畫并請求浏覽器在下一次重繪之 前調用指定的函數來更新動畫。該方法使用一個回調函數作為參數,這個回調函數會在 浏覽器重繪之前調用。

有一個遊戲叫做 Flappy Bird,就是一隻小鳥在飛,前面是無盡的沙漠,上下不斷有鋼管生成,你要躲避鋼管。然後小明在玩這個遊戲時候老是卡頓 甚至崩潰, 說出原因 ( 3-5 個) 以及解決辦法 ( 3-5 個)

參考回答:

原因可能是:

1. 記憶體溢出問題。

2.資源過大問題。

3.資源加載問題。

4.canvas 繪制頻率問題

解決辦法:

1.針對記憶體溢出問題, 我們應該在鋼管離開可視區域後, 銷毀鋼管, 讓垃圾收集器回收 鋼管, 因為不斷生成的鋼管不及時清理容易導緻記憶體溢出遊戲崩潰 。 2.針對資源過大問題,我們應該選擇圖檔檔案大小更小的圖檔格式,比如使用webp、png 格式的圖檔, 因為繪制圖檔需要較大計算量。

3.針對資源加載問題, 我們應該在可視區域之前就預加載好資源, 如果在可視區域生成 鋼管的話, 使用者的體驗就認為鋼管是卡頓後才生成的, 不流暢。

4.針對 canvas 繪制頻率問題, 我們應該需要知道大部分顯示器重新整理頻率為 60 次/s, 是以 遊戲的每一幀繪制間隔時間需要小于 1000/60=16.7ms, 才能讓使用者覺得不卡頓。

(注意因為這是單機遊戲, 是以回答與網絡無關)

編寫代碼,滿足以下條件:

( 1) Hero("37er");

執行結果為 Hi! This is 37er。

( 2) Hero("37er").kill(1).recover(30);

執行結果為 Hi! This is 37er Kill 1 bug Recover 30 bloods

( 3) Hero("37er").sleep(10).kill(2)

執行結果為 Hi! This is 37er //等待 10s 後 Kill 2 bugs //注意為 bugs (雙斜線後的為 提示資訊, 不需要列印)

參考回答:

function Hero(name){

let o=new Object();

o.name=name;

o.time=0;

console.log("Hi! This is "+o.name);

o.kill=function(bugs) {

if(bugs==1){

console.log("Kill "+(bugs)+" bug");

}else {

setTimeout(function () {

console.log("Kill " + (bugs) + " bugs");

}, 1000 * this.time);

}

return o;

};

o.recover=function (bloods) {

console.log("Recover "+(bloods)+" bloods");

return o;

}

o.sleep=function (sleepTime) {

o.time=sleepTime;

return o;

}

return o;

}           

什麼是按需加載

參考回答:

當使用者觸發了動作時才加載對應的功能。觸發的動作,是要看具體的業務場景而言,包 括但不限于以下幾個情況: 滑鼠點選、輸入文字、拉動滾動條, 滑鼠移動 、視窗大小更 改等 。加載的檔案, 可以是 JS 、 圖檔 、CSS 、HTML 等。

說一下什麼是 virtual dom

參考回答:

用 JavaScript 對象結構表示 DOM 樹的結構; 然後用這個樹建構一個真正的 DOM 樹, 插到文檔當中 當狀态變更的時候, 重新構造一棵新的對象樹 。然後用新的樹和舊的樹 進行比較,記錄兩棵樹差異 把所記錄的差異應用到所建構的真正的 DOM 樹上,視圖就 更新了 。Virtual DOM 本質上就是在 JS 和 DOM 之間做了一個緩存。

webpack 用來幹什麼的

參考回答:

webpack 是一個現代 JavaScript 應用程式的靜态子產品打包器(module bundler) 。當 webpack 處理應用程式時, 它會遞歸地建構一個依賴關系圖(dependency graph), 其中包 含應用程式需要的每個子產品, 然後将所有這些子產品打包成一個或多個 bundle。

ant-design 優點和缺點

參考回答:

優點: 元件非常全面, 樣式效果也都比較不錯。

缺點: 架構自定義程度低, 預設 UI 風格修改困難。

JS 中繼承實作的幾種方式,

參考回答:

1 、原型鍊繼承, 将父類的執行個體作為子類的原型, 他的特點是執行個體是子類的執行個體也是父 類的執行個體, 父類新增的原型方法/屬性, 子類都能夠通路, 并且原型鍊繼承簡單易于實 現,缺點是來自原型對象的所有屬性被所有執行個體共享,無法實作多繼承,無法向父類構 造函數傳參。

2 、構造繼承, 使用父類的構造函數來增強子類執行個體, 即複制父類的執行個體屬性給子類, 構造繼承可以向父類傳遞參數,可以實作多繼承,通過 call 多個父類對象。但是構造繼 承隻能繼承父類的執行個體屬性和方法,不能繼承原型屬性和方法,無法實作函數服用,每 個子類都有父類執行個體函數的副本, 影響性能

3 、執行個體繼承, 為父類執行個體添加新特性, 作為子類執行個體傳回, 執行個體繼承的特點是不限制 調用方法, 不管是new 子類 () 還是子類 () 傳回的對象具有相同的效果, 缺點是實 例是父類的執行個體, 不是子類的執行個體, 不支援多繼承

4 、拷貝繼承: 特點: 支援多繼承, 缺點: 效率較低, 記憶體占用高 (因為要拷貝父類的 屬性) 無法擷取父類不可枚舉的方法 (不可枚舉方法, 不能使用forin 通路到)

5 、組合繼承: 通過調用父類構造, 繼承父類的屬性并保留傳參的優點, 然後通過将父 類執行個體作為子類原型, 實作函數複用

6 、寄生組合繼承: 通過寄生方式, 砍掉父類的執行個體屬性, 這樣, 在調用兩次父類的構 造的時候, 就不會初始化兩次執行個體方法/屬性, 避免的組合繼承的缺點

寫一個函數, 第一秒列印 1, 第二秒列印 2

參考回答:

兩個方法, 第一個是用let 塊級作用域

for(let i=0;i<5;i++){

setTimeout(function(){

console.log(i)

},1000*i)

}           

第二個方法閉包

for(var i=0;i<5;i++){

(function(i){

setTimeout(function(){

console.log(i)

},1000*i)

})(i)

}           

Vue 的生命周期

參考回答:

Vue 執行個體有一個完整的生命周期, 也就是從開始建立 、初始化資料 、編譯模闆 、挂載 Dom、渲染→更新→渲染、銷毀等一系列過程,我們稱這是 Vue 的生命周期。通俗說就 是 Vue 執行個體從建立到銷毀的過程, 就是生命周期。

每一個元件或者執行個體都會經曆一個完整的生命周期,總共分為三個階段:初始化、運作 中 、銷毀。

執行個體 、元件通過 new Vue() 建立出來之後會初始化事件和生命周期, 然後就會執行 beforeCreate 鈎子函數,這個時候,資料還沒有挂載呢,隻是一個空殼,無法通路到資料 和真實的dom, 一般不做操作

挂載資料, 綁定事件等等, 然後執行 created 函數, 這個時候已經可以使用到資料, 也 可以更改資料,在這裡更改資料不會觸發 updated 函數,在這裡可以在渲染前倒數第二次 更改資料的機會, 不會觸發其他的鈎子函數, 一般可以在這裡做初始資料的擷取

接下來開始找執行個體或者元件對應的模闆,編譯模闆為虛拟 dom 放入到 render 函數中準備 渲染, 然後執行 beforeMount 鈎子函數, 在這個函數中虛拟dom 已經建立完成, 馬上就 要渲染,在這裡也可以更改資料, 不會觸發 updated, 在這裡可以在渲染前最後一次更改 資料的機會, 不會觸發其他的鈎子函數, 一般可以在這裡做初始資料的擷取

接下來開始render, 渲染出真實dom, 然後執行 mounted 鈎子函數, 此時, 元件已經出 現在頁面中, 資料 、真實 dom 都已經處理好了,事件都已經挂載好了, 可以在這裡操作 真實 dom 等事情...

當元件或執行個體的資料更改之後, 會立即執行 beforeUpdate, 然後 Vue 的虛拟 dom 機制會 重新建構虛拟 dom 與上一次的虛拟 dom 樹利用diff 算法進行對比之後重新渲染,一般不 做什麼事兒

當更新完成後, 執行 updated, 資料已經更改完成, dom 也重新 render 完成, 可以操作 更新後的虛拟dom

當經過某種途徑調用$destroy 方法後,立即執行 beforeDestroy,一般在這裡做一些善後工 作, 例如清除計時器 、清除非指令綁定的事件等等

元件的資料綁定、監聽...去掉後隻剩下 dom 空殼,這個時候, 執行 destroyed,在這裡做 善後工作也可以

簡單介紹一下 symbol

參考回答:

Symbol 是 ES6 的新增屬性, 代表用給定名稱作為唯一辨別, 這種類型的值可以這樣創 建, let id=symbol(“id”)

Symbl 確定唯一, 即使采用相同的名稱, 也會産生不同的值, 我們建立一個字段, 僅為 知道對應 symbol 的人能通路,使用 symbol 很有用,symbol 并不是 100%隐藏,有内置方 法 Object.getOwnPropertySymbols(obj)可以獲得所有的 symbol。

也有一個方法 Reflect.ownKeys(obj)傳回對象所有的鍵, 包括 symbol。

是以并不是真正隐藏 。但大多數庫内置方法和文法結構遵循通用約定他們是隐藏的。

什麼是事件監聽

參考回答:

addEventListener()方法, 用于向指定元素添加事件句柄, 它可以更簡單的控制事件, 語 法為

element.addEventListener(event, function, useCapture);

第一個參數是事件的類型(如 "click" 或 "mousedown").

第二個參數是事件觸發後調用的函數。

第三個參數是個布爾值用于描述事件是冒泡還是捕獲 。該參數是可選的。 事件傳遞有兩種方式, 冒泡和捕獲

事件傳遞定義了元素事件觸發的順序, 如果你将 P 元素插入到 div 元素中, 使用者點選 P 元素,

在冒泡中, 内部元素先被觸發, 然後再觸發外部元素,

捕獲中, 外部元素先被觸發, 在觸發内部元素。

介紹一下 promise, 及其底層如何實作

參考回答:

Promise 是一個對象, 儲存着未來将要結束的事件, 她有兩個特征:

1 、對象的狀态不受外部影響, Promise 對象代表一個異步操作, 有三種狀态, pending 進行中, fulfilled 已成功, rejected 已失敗, 隻有異步操作的結果, 才可以決定目前是哪 一種狀态, 任何其他操作都無法改變這個狀态, 這也就是 promise 名字的由來。

2 、一旦狀态改變, 就不會再變, promise 對象狀态改變隻有兩種可能, 從 pending 改到 fulfilled 或者從 pending 改到 rejected, 隻要這兩種情況發生, 狀态就凝固了, 不會再改 變, 這個時候就稱為定型 resolved,

Promise 的基本用法,

let promise1 = new Promise(function(resolve,reject){

setTimeout(function(){

resolve('ok')

},1000)

})

promise1.then(function success(val){

console.log(val)

})           

最簡單代碼實作 promise

class PromiseM {

constructor (process) {

this.status = 'pending'

this.msg = ''

process(this.resolve.bind(this), this.reject.bind(this))

return this

}

resolve (val) {

this.status = 'fulfilled'

this.msg = val

}

reject (err) {

this.status = 'rejected'

this.msg = err

}

then (fufilled, reject) {

if(this.status === 'fulfilled') {

fufilled(this.msg)

}

if(this.status === 'rejected') {

reject(this.msg)

}

}

}

//測試代碼

var mm=new PromiseM(function(resolve,reject){

resolve('123');

});

mm.then(function(success){

console.log(success);

},function(){

console.log('fail!');

});

           

說說 C++,Java, JavaScript 這三種語言的差別

參考回答:

從靜态類型還是動态類型來看

靜态類型,編譯的時候就能夠知道每個變量的類型,程式設計的時候也需要給定類型,如 Java 中的整型 int, 浮點型 float 等 。C 、C++ 、Java 都屬于靜态類型語言。

動态類型, 運作的時候才知道每個變量的類型, 程式設計的時候無需顯示指定類型, 如 JavaScript 中的 var 、PHP 中的$ 。JavaScript 、Ruby 、Python 都屬于動态類型語言 。 靜态類型還是動态類型對語言的性能有很大影響。

對于靜态類型, 在編譯後會大量利用已知類型的優勢, 如 int 類型, 占用4 個位元組, 編 譯後的代碼就可以用記憶體位址加偏移量的方法存取變量,而位址加偏移量的算法彙編很 容易實作。

對于動态類型, 會當做字元串通通存下來, 之後存取就用字元串比對。

從編譯型還是解釋型來看

編譯型語言, 像 C 、C++, 需要編譯器編譯成本地可執行程式後才能運作, 由開發人員 在編寫完成後手動實施。使用者隻使用這些編譯好的本地代碼,這些本地代碼由系統加載 器執行, 由作業系統的 CPU 直接執行, 無需其他額外的虛拟機等。

源代碼=》抽象文法樹=》 中間表示=》本地代碼

解釋性語言,像 JavaScript、Python, 開發語言寫好後直接将代碼交給使用者,使用者使用腳 本解釋器将腳本檔案解釋執行。對于腳本語言,沒有開發人員的編譯過程, 當然,也不 絕對。

源代碼=》抽象文法樹=》解釋器解釋執行。

對于 JavaScript, 随着 Java 虛拟機 JIT 技術的引入, 工作方式也發生了改變 。可以将抽 象文法樹轉成中間表示 (位元組碼) ,再轉成本地代碼,如 JavaScriptCore,這樣可以大大 提高執行效率 。也可以從抽象文法樹直接轉成本地代碼, 如 V8

Java 語言,分為兩個階段。首先像 C++語言一樣,經過編譯器編譯。和 C++的不同,C++ 編譯生成本地代碼,Java 編譯後, 生成位元組碼, 位元組碼與平台無關 。第二階段, 由 Java 的運作環境也就是 Java 虛拟機運作位元組碼,使用解釋器執行這些代碼。一般情況下,Java 虛拟機都引入了 JIT 技術, 将位元組碼轉換成本地代碼來提高執行效率。

注意,在上述情況中,編譯器的編譯過程沒有時間要求,是以編譯器可以做大量的代碼 優化措施。

對于 JavaScript 與 Java 它們還有的不同:

對于 Java,Java 語言将源代碼編譯成位元組碼,這個同執行階段是分開的。也就是從源代 碼到抽象文法樹到位元組碼這段時間的長短是無所謂的。

對于 JavaScript,這些都是在網頁和 JavaScript 檔案下載下傳後同執行階段一起在網頁的加載 和渲染過程中實施的, 是以對于它們的處理時間有嚴格要求。

JS 原型鍊,原型鍊的頂端是什麼? Object 的原型是什麼? Object 的原型的原型是什麼?在數組原型鍊上實作删除數組重複資料的方法

參考回答:

能夠把這個講清楚弄明白是一件很困難的事,首先明白原型是什麼, 在 ES6 之前, JS 沒有類和繼承的概念, JS 是通過原型來實作繼 承的, 在 JS 中一個構造函數預設帶有一個 prototype 屬性, 這個的屬性值是一個對象。

同時這個 prototype 對象自帶有一個 constructor 屬性,這個屬性指向這個構造函數,同時 每一個執行個體都會有一個_proto_屬性指向這個 prototype 對象,我們可以把這個叫做隐式原 型,我們在使用一個執行個體的方法的時候,會先檢查這個執行個體中是否有這個方法,沒有的 話就會檢查這個 prototype 對象是否有這個方法,

基于這個規則, 如果讓原型對象指向另一個類型的執行個體, 即

constructor1.protoytpe=instance2, 這時候如果試圖引用 constructor1 構造的執行個體 instance1 的某個屬性 p1,

首先會在 instance1 内部屬性中找一遍,

接着會在 instance1._proto_ (constructor1.prototype) 即是 instance2 中尋找 p1

搜尋軌迹: instance1->instance2->constructor2.prototype……->Object.prototype;這即是原 型鍊, 原型鍊頂端是 Object.prototype

補充學習:

每個函數都有一個 prototype 屬性,這個屬性指向了一個對象,這個對象正是調用該函數 而建立的執行個體的原型, 那麼什麼是原型呢, 可以這樣了解, 每一個 JavaScript 對象在創 建的時候就會預制管理另一個對象,這個對象就是我們所說的原型,每一個對象都會從原型繼承屬性, 如圖:

前端工程師常見面試題(前端基礎)——JavaScript

那麼怎麼表示執行個體與執行個體原型的關系呢, 這時候就要用到第二個屬性_proto_ 這是每一個 JS 對象都會有的一個屬性, 指向這個對象的原型, 如圖:

前端工程師常見面試題(前端基礎)——JavaScript

既然執行個體對象和構造函數都可以指向原型,那麼原型是否有屬性指向構造函數或者執行個體 呢,指向執行個體是沒有的, 因為一個構造函數可以生成多個執行個體,但是原型有屬性可以直 接指向構造函數, 通過 constructor 即可

接下來講解執行個體和原型的關系:

當讀取執行個體的屬性時,如果找不到,就會查找與對象相關的原型中的屬性,如果還查不 到,就去找原型的原型,一直找到最頂層,那麼原型的原型是什麼呢,首先,原型也是 一個對象,既然是對象,我們就可以通過構造函數的方式建立它,是以原型對象就是通 過 Object 構造函數生成的, 如圖:

前端工程師常見面試題(前端基礎)——JavaScript

那麼 Object.prototype 的原型呢, 我們可以列印 console.log(Object.prototype.__proto__ === null), 傳回 true

null 表示沒有對象, 即該處不應有值, 是以 Object.prototype 沒有原型, 如圖:

前端工程師常見面試題(前端基礎)——JavaScript

圖中這條藍色的線即是原型鍊,

最後補充三點:

constructor:

function Person(){

}

var person = new Person();

console.log(Person === person.constructor);

原本 person 中沒有 constructor 屬性,當不能讀取到 constructor 屬性時,會從 person 的原 型中讀取, 是以指向構造函數 Person

__proto__:

絕大部分浏覽器支援這個非标準的方法通路原型,然而它并不存在與 Person.prototype 中, 實際上它來自 Object.prototype, 當使用 obj.__proto__時, 可以了解為傳回來

Object.getPrototype(obj)

繼承:

前面說到,每個對象都會從原型繼承屬性,但是引用《你不知道的 JS》中的話,繼承意 味着複制操作, 然而 JS 預設不會複制對象的屬性, 相反, JS 隻是在兩個對象之間建立 一個關聯,這樣子一個對象就可以通過委托通路另一個對象的屬性和函數,是以與其叫 繼承, 叫委托更合适。

用閉包寫個單例模式

參考回答:

單例模式:

var Singleton = (function(){

var instance;

var CreateSingleton = function (name) {

this.name = name;

if(instance) {

return instance;

}

// 列印執行個體名字

this.getName();

// instance = this;

// return instance;

return instance = this;

}

// 擷取執行個體的名字

CreateSingleton.prototype.getName = function() {

console.log(this.name)

}

return CreateSingleton;

})();

// 建立執行個體對象 1

var a = new Singleton('a');

// 建立執行個體對象 2

var b = new Singleton('b');

console.log(a===b);

           

promise+Generator+Async 的使用

參考回答:

Promise

解決的問題: 回調地獄

Promise 規範:

promise 有三種狀态,等待(pending) 、已完成 (fulfilled/resolved) 、已拒絕 (rejected) .Promise 的狀态隻能從“等待”轉到“完成”或者“拒絕”, 不能逆向轉換, 同時“完成”和 “拒絕”也不能互相轉換.

promise 必須提供一個 then 方法以通路其目前值 、終值和據因 。promise.then(resolve, reject),resolve 和 reject 都是可選參數。如果 resolve 或 reject 不是函數,其必須被忽略. then 方法必須傳回一個 promise 對象.

使用:

執行個體化 promise 對象需要傳入函數(包含兩個參數),resolve 和 reject, 内部确定狀态.resolve 和 reject 函數可以傳入參數在回調函數中使用.

resolve 和 reject 都是函數,傳入的參數在 then 的回調函數中接收.

var promise = new Promise(function(resolve, reject) {

setTimeout(function(){

resolve('好哈哈哈哈');

});

});

promise.then(function(val){

console.log(val)

})           

then 接收兩個函數,分别對應 resolve 和 reject 狀态的回調,函數中接收執行個體化時傳入的參 數.

promise.then(val=>{

//resolved

},reason=>{

//rejected

})           

catch 相當于.then(null, rejection)

當 then 中沒有傳入 rejection 時,錯誤會冒泡進入 catch 函數中,若傳入了 rejection,則錯誤會 被 rejection 捕獲,而且不會進入 catch.此外,then 中的回調函數中發生的錯誤隻會在下一級 的 then 中被捕獲,不會影響該 promise 的狀态.

new Promise((resolve,reject)=>{

throw new Error('錯誤')

}).then(null,(err)=>{

console.log(err,1);//此處捕獲

}).catch((err)=>{

console.log(err,2);

});

// 對比

new Promise((resolve,reject)=>{

throw new Error('錯誤')

}).then(null,null).catch((err)=>{

console.log(err,2);//此處捕獲

});

// 錯誤示例

new Promise((resolve,reject)=>{

resolve('正常');

}).then((val)=>{

throw new Error('回調函數中錯誤')

},(err)=>{

console.log(err,1);

}).then(null,(err)=>{

console.log(err,2);//此處捕獲,也可用 catch

});           

兩者不等價的情況:

此時, catch 捕獲的并不是 p1 的錯誤, 而是 p2 的錯誤,

p1().then(res=>{

return p2()//p2 傳回一個 promise 對象

}).catch(err=> console.log(err))

一個錯誤捕獲的錯誤用例:

該函數調用中即使發生了錯誤依然會進入 then 中的 resolve 的回調函數,因為函數 p1 中實 例化 promise 對象時已經調用了 catch,若發生錯誤會進入catch 中,此時會傳回一個新的 promise, 是以即使發生錯誤依然會進入 p1 函數的 then 鍊中的resolve 回調函數.

function p1(val){

return new Promise((resolve,reject)=>{

if(val){

var len = val.length;//傳入 null 會發生錯誤,進入 catch 捕獲錯

resolve(len);

}else{

reject();

}

}).catch((err)=>{

console.log(err)

})

};

p1(null).then((len)=>{

console.log(len,'resolved');

},()=>{

console.log('rejected');

}).catch((err)=>{

console.log(err,'catch');

})           

Promise 回調鍊:

promise 能夠在回調函數裡面使用 return 和 throw, 是以在 then 中可以 return 出一個 promise 對象或其他值, 也可以 throw 出一個錯誤對象, 但如果沒有return, 将預設傳回 undefined, 那麼後面的 then 中的回調參數接收到的将是 undefined.

function p1(val){

return new Promise((resolve,reject)=>{

val==1?resolve(1):reject()

})

};

function p2(val){

return new Promise((resolve,reject)=>{

val==2?resolve(2):reject();

})

};

let promimse = new Promise(function(resolve,reject){

resolve(1)

})

.then(function(data1) {

return p1(data1)//如果去掉 return,則傳回 undefined 而不是 p1 的傳回值,會導緻報錯 })

.then(function(data2){

return p2(data2+1)

})

.then(res=>console.log(res))           

Generator 函數:

generator 函數使用:

1 、分段執行, 可以暫停

2 、可以控制階段和每個階段的傳回值

3 、可以知道是否執行到結尾

function* g() {

var o = 1;

yield o++;

yield o++;

}

var gen = g();

console.log(gen.next()); // Object {value: 1, done: false}

var xxx = g();

console.log(gen.next()); // Object {value: 2, done: false}

console.log(xxx.next()); // Object {value: 1, done: false}

console.log(gen.next()); // Object {value: undefined, done: true}           

generator 和異步控制:

利用 Generator 函數的暫停執行的效果, 可以把異步操作寫在 yield 語句裡面, 等到調用 next 方法時再往後執行。這實際上等同于不需要寫回調函數了, 因為異步操作的後續操 作可以放在 yield 語句下面, 反正要等到調用next 方法時再執行 。是以, Generator 函數 的一個重要實際意義就是用來處理異步操作, 改寫回調函數。

async 和異步:

用法:

async 表示這是一個 async 函數, await 隻能用在這個函數裡面。

await 表示在這裡等待異步操作傳回結果, 再繼續執行。

await 後一般是一個 promise 對象

示例:async 用于定義一個異步函數, 該函數傳回一個 Promise。

如果 async 函數傳回的是一個同步的值,這個值将被包裝成一個了解resolve 的 Promise, 等同于 return Promise.resolve(value)。

await 用于一個異步操作之前, 表示要“等待”這個異步操作的傳回值 。await 也可以用 于一個同步的值。

let timer = async function timer(){

return new Promise((resolve,reject) => {

setTimeout(() => {

resolve('500');

},500);

});

}

timer().then(result => {

console.log(result); //500

}).catch(err => {

console.log(err.message);

});

//傳回一個同步的值

let sayHi = async function sayHi(){

let hi = await 'hello world';

return hi; //等同于 return Promise.resolve(hi);

}

sayHi().then(result => {

console.log(result);

});

           

事件委托以及冒泡原理。

參考回答:

事件委托是利用冒泡階段的運作機制來實作的,就是把一個元素響應事件的函數委托到 另一個元素, 一般是把一組元素的事件委托到他的父元素上, 委托的優點是減少記憶體消耗, 節約效率。

動态綁定事件

事件冒泡,就是元素自身的事件被觸發後, 如果父元素有相同的事件, 如 onclick 事件, 那麼元素本身的觸發狀态就會傳遞,也就是冒到父元素,父元素的相同僚件也會一級一 級根據嵌套關系向外觸發, 直到document/window, 冒泡過程結束。

寫個函數, 可以轉化下劃線命名到駝峰命名

參考回答:

public static String UnderlineToHump(String para){

StringBuilder result=new StringBuilder();

String a[]=para.split("_");

for(String s:a){

if(result.length()==0){

result.append(s.toLowerCase());

}else{

result.append(s.substring(0, 1).toUpperCase());

result.append(s.substring(1).toLowerCase());

}

}

return result.toString();

}

}           

深淺拷貝的差別和實作

參考回答:

數組的淺拷貝:

如果是數組,我們可以利用數組的一些方法, 比如slice,concat 方法傳回一個新數組的 特性來實作拷貝,但假如數組嵌套了對象或者數組的話,使用concat 方法克隆并不完整, 如果數組元素是基本類型,就會拷貝一份,互不影響,而如果是對象或數組,就會隻拷 貝對象和數組的引用,這樣我們無論在新舊數組進行了修改,兩者都會發生變化,我們 把這種複制引用的拷貝方法稱為淺拷貝,

深拷貝就是指完全的拷貝一個對象, 即使嵌套了對象,兩者也互相分離,修改一個對象 的屬性, 不會影響另一個

如何深拷貝一個數組

1 、這裡介紹一個技巧, 不僅适用于數組還适用于對象! 那就是:

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]

var new_arr = JSON.parse( JSON.stringify(arr) );

console.log(new_arr);

原理是 JOSN 對象中的 stringify 可以把一個 js 對象序列化為一個 JSON 字元串, parse 可 以把 JSON 字元串反序列化為一個 js 對象,通過這兩個方法,也可以實作對象的深複制。

但是這個方法不能夠拷貝函數

淺拷貝的實作:

以上三個方法 concat,slice ,JSON.stringify 都是技巧類, 根據實際項目情況選擇使用, 我 們可以思考下如何實作一個對象或數組的淺拷貝,周遊對象,然後把屬性和屬性值都放 在一個新的對象裡即可

var shallowCopy = function(obj) {

// 隻拷貝對象

if (typeof obj !== 'object') return;

// 根據 obj 的類型判斷是建立一個數組還是對象

var newObj = obj instanceof Array ? [] : {};

// 周遊 obj, 并且判斷是 obj 的屬性才拷貝

for (var key in obj) {

if (obj.hasOwnProperty(key)) {

newObj[key] = obj[key];

}

}

return newObj;           

深拷貝的實作

那如何實作一個深拷貝呢?說起來也好簡單,我們在拷貝的時候判斷一下屬性值的類型, 如果是對象, 我們遞歸調用深拷貝函數不就好了~

var deepCopy = function(obj) {

if (typeof obj !== 'object') return;

var newObj = obj instanceof Array ? [] : {};

for (var key in obj) {

if (obj.hasOwnProperty(key)) {

newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]; }

}

return newObj;

}           

JS 中 string 的 startwith 和 indexof 兩種方法的差別

參考回答:

JS 中 startwith 函數,其參數有 3 個,stringObj,要搜尋的字元串對象,str,搜尋的字元串, position,可選,從哪個位置開始搜尋,如果以 position 開始的字元串以搜尋字元串開頭, 則傳回 true, 否則傳回 false

Indexof 函數, indexof 函數可傳回某個指定字元串在字元串中首次出現的位置。

JS 字元串轉數字的方法

參考回答:

通過函數 parseInt() ,可解析一個字元串,并傳回一個整數,文法為 parseInt(string ,radix) string: 被解析的字元串

radix: 表示要解析的數字的基數, 預設是十進制, 如果 radix<2 或>36,則傳回 NaN

let const var 的差別 , 什麼是塊級作用域, 如何用ES5 的方法實作塊級作用域 (立即執行函數) , ES6 呢

參考回答:

提起這三個最明顯的差別是 var 聲明的變量是全局或者整個函數塊的, 而 let,const 聲明 的變量是塊級的變量,var 聲明的變量存在變量提升,let,const 不存在,let 聲明的變量允 許重新指派, const 不允許。

ES6 箭頭函數的特性

參考回答:

ES6 增加了箭頭函數, 基本文法為

let func = value => value;

相當于

let func = function (value) {

return value;

};

箭頭函數與普通函數的差別在于:

1、箭頭函數沒有this,是以需要通過查找作用域鍊來确定 this 的值,這就意味着如果箭 頭函數被非箭頭函數包含, this 綁定的就是最近一層非箭頭函數的this,

2 、箭頭函數沒有自己的 arguments 對象, 但是可以通路外圍函數的 arguments 對象

3 、不能通過 new 關鍵字調用, 同樣也沒有 new.target 值和原型

setTimeout 和 Promise 的執行順序

參考回答:

首先我們來看這樣一道題:

setTimeout(function() {

console.log(1)

}, 0);

new Promise(function(resolve, reject) {

console.log(2)

for (var i = 0; i < 10000; i++) {

if(i === 10) {console.log(10)}

i == 9999 && resolve();

}

console.log(3)

}).then(function() {

console.log(4)

})

console.log(5);           

輸出答案為 2 10 3 5 4 1

要先弄清楚 settimeout (fun,0) 何時執行, promise 何時執行, then 何時執行

settimeout 這種異步操作的回調, 隻有主線程中沒有執行任何同步代碼的前提下, 才會 執行異步回調, 而 settimeout (fun,0) 表示立刻執行, 也就是用來改變任務的執行順序, 要求浏覽器盡可能快的進行回調

promise 何時執行, 由上圖可知 promise 建立後立即執行, 是以 promise 構造函數裡代碼 同步執行的,

then 方法指向的回調将在目前腳本所有同步任務執行完成後執行,

那麼 then 為什麼比 settimeout 執行的早呢, 因為 settimeout (fun,0) 不是真的立即執行, 經過測試得出結論: 執行順序為: 同步執行的代碼-》promise.then->settimeout

有了解過事件模型嗎, DOM0 級和 DOM2 級有什麼差別, DOM 的分級是什麼

參考回答:

JSDOM 事件流存在如下三個階段:

事件捕獲階段

處于目标階段

事件冒泡階段

JSDOM 标準事件流的觸發的先後順序為: 先捕獲再冒泡, 點選 DOM 節點時,事件傳播 順序:事件捕獲階段,從上往下傳播, 然後到達事件目标節點,最後是冒泡階段,從下 往上傳播

DOM 節點添加事件監聽方法 addEventListener,中參數 capture 可以指定該監聽是添加在 事件捕獲階段還是事件冒泡階段, 為 false 是事件冒泡, 為 true 是事件捕獲, 并非所有 的事件都支援冒泡, 比如 focus, blur 等等, 我們可以通過 event.bubbles 來判斷

事件模型有三個常用方法:

event.stopPropagation:阻止捕獲和冒泡階段中, 目前事件的進一步傳播,

event.stopImmediatePropagetion, 阻止調用相同僚件的其他偵聽器,

event.preventDefault, 取消該事件 (假如事件是可取消的) 而不停止事件的進一步傳播, event.target: 指向觸發事件的元素, 在事件冒泡過程中這個值不變

event.currentTarget = this, 時間幫頂的目前元素, 隻有被點選時目标元素的 target 才會等 于 currentTarget,

最後, 對于執行順序的問題, 如果 DOM 節點同時綁定了兩個事件監聽函數, 一個用于 捕獲,一個用于冒泡,那麼兩個事件的執行順序真的是先捕獲在冒泡嗎,答案是否定的, 綁定在被點選元素的事件是按照代碼添加順序執行的, 其他函數是先捕獲再冒泡。

平時是怎麼調試 JS 的

參考回答:

一般用Chrome 自帶的控制台

JS 的基本資料類型有哪些, 基本資料類型和引用資料類型的差別, NaN

是什麼的縮寫, JS 的作用域類型, undefined==null 傳回的結果是什麼, undefined 與 null 的差別在哪, 寫一個函數判斷變量類型

參考回答:

JS 的基本資料類型有字元串,數字,布爾,數組,對象,Null,Undefined,基本資料類型 是按值通路的, 也就是說我們可以操作儲存在變量中的實際的值,

基本資料類型和引用資料類型的差別如下:

基本資料類型的值是不可變的,任何方法都無法改變一個基本類型的值,當這個變量重 新指派後看起來變量的值是改變了,但是這裡變量名隻是指向變量的一個指針,是以改 變的是指針的指向改變, 該變量是不變的, 但是引用類型可以改變

基本資料類型不可以添加屬性和方法, 但是引用類型可以

基本資料類型的指派是簡單指派,如果從一個變量向另一個變量指派基本類型的值,會 在變量對象上建立一個新值,然後把該值複制到為新變量配置設定的位置上,引用資料類型 的指派是對象引用,

基本資料類型的比較是值的比較,引用類型的比較是引用的比較, 比較對象的記憶體位址 是否相同

基本資料類型是存放在棧區的, 引用資料類型同僚儲存在棧區和堆區

NaN 是 JS 中的特殊值, 表示非數字, NaN 不是數字, 但是他的資料類型是數字, 它不 等于任何值,包括自身,在布爾運算時被當做 false,NaN 與任何數運算得到的結果都是 NaN, 黨員算失敗或者運算無法傳回正确的數值的就會傳回 NaN, 一些數學函數的運算 結果也會出現 NaN ,

JS 的作用域類型:

一般認為的作用域是詞法作用域, 此外 JS 還提供了一些動态改變作用域的方法, 常見 的作用域類型有:

函數作用域,如果在函數内部我們給未定義的一個變量指派,這個變量會轉變成為一個 全局變量,

塊作用域: 塊作用域吧辨別符限制在{}中,

改變函數作用域的方法:

eval () , 這個方法接受一個字元串作為參數, 并将其中的内容視為好像在書寫時就存 在于程式中這個位置的代碼,

with 關鍵字: 通常被當做重複引用同一個對象的多個屬性的快捷方式

undefined 與 null: 目前 null 和 undefined 基本是同義的, 隻有一些細微的差别, null 表 示沒有對象, undefined 表示缺少值, 就是此處應該有一個值但是還沒有定義, 是以 undefined==null 傳回 false

此外了解== 和===的差別:

在做==比較時 。不同類型的資料會先轉換成一緻後在做比較, ===中如果類型不一緻就 直接傳回false, 一緻的才會比較

類型判斷函數, 使用typeof 即可, 首先判斷是否為null, 之後用 typeof 哦按段, 如果是 object 的話, 再用 array.isarray 判斷是否為數組, 如果是數字的話用 isNaN 判斷是否是 NaN 即可

擴充學習:

JS 采用的是詞法作用域,也就是靜态作用域,是以函數的作用域在函數定義的時候就決 定了,

看如下例子:

var value = 1;

function foo() {

console.log(value);

}

function bar() {

var value = 2;

foo();

}

bar();           

假設 JavaScript 采用靜态作用域, 讓我們分析下執行過程:

執行 foo 函數, 先從 foo 函數内部查找是否有局部變量 value, 如果沒有, 就根據書寫 的位置, 查找上面一層的代碼, 也就是 value 等于 1, 是以結果會列印 1。

假設 JavaScript 采用動态作用域, 讓我們分析下執行過程:

執行 foo 函數, 依然是從 foo 函數内部查找是否有局部變量 value 。如果沒有, 就從調 用函數的作用域, 也就是 bar 函數内部查找 value 變量, 是以結果會列印 2。

前面我們已經說了, JavaScript 采用的是靜态作用域, 是以這個例子的結果是 1。

setTimeout(fn,100);100 毫秒是如何權衡的

參考回答:

setTimeout()函數隻是将事件插入了任務清單,必須等到目前代碼執行完,主線程才會去 執行它指定的回調函數, 有可能要等很久, 是以沒有辦法保證回調函數一定會在 setTimeout 指定的時間内執行, 100 毫秒是插入隊列的時間+等待的時間

JS 的垃圾回收機制

參考回答:

GC (garbage collection) , GC 執行時, 中斷代碼, 停止其他操作,周遊所有對象,對于 不可通路的對象進行回收, 在 V8 引擎中使用兩種優化方法,

分代回收,2、增量 GC, 目的是通過對象的使用頻率,存在時長來區分新生代和老生代 對象, 多回收新生代區, 少回收老生代區, 減少每次周遊的時間, 進而減少 GC 的耗時 回收方法:

引用計次, 當對象被引用的次數為零時進行回收,但是循環引用時,兩個對象都至少被 引用了一次, 是以導緻記憶體洩漏,标記清除。

寫一個 newBind 函數, 完成 bind 的功能。

參考回答:

bind () 方法, 建立一個新函數, 當這個新函數被調用時, bind () 的第一個參數将作 為它運作時的this, 之後的一序列參數将會在傳遞的實參前傳入作為它的參數

Function.prototype.bind2 = function (context) {

if (typeof this !== "function") {

throw new Error("Function.prototype.bind - what is trying to be bound is not callable");

}

var self = this;

var args = Array.prototype.slice.call(arguments, 1);

var fNOP = function () {};

var fbound = function () {

self.apply(this instanceof self ? this : context,

args.concat(Array.prototype.slice.call(arguments)));

}

fNOP.prototype = this.prototype;

fbound.prototype = new fNOP();

return fbound;

}

           

怎麼獲得對象上的屬性: 比如說通過 Object.key ()

參考回答:

從 ES5 開始, 有三種方法可以列出對象的屬性

for (let I in obj) 該方法依次通路一個對象及其原型鍊中所有可枚舉的類型 object.keys:傳回一個數組, 包括所有可枚舉的屬性名稱

object.getOwnPropertyNames:傳回一個數組包含不可枚舉的屬性

簡單講一講 ES6 的一些新特性

參考回答:

ES6 在變量的聲明和定義方面增加了let 、const 聲明變量, 有局部變量的概念, 指派中 有比較吸引人的結構指派, 同時 ES6 對字元串 、 數組 、正則 、對象 、函數等拓展了一 些方法,如字元串方面的模闆字元串 、函數方面的預設參數、對象方面屬性的簡潔表達 方式。

ES6 也引入了新的資料類型 symbol, 新的資料結構 set 和 map,symbol 可以通過 typeof 檢測出來, 為解決異步回調問題, 引入了 promise 和 generator, 還有最為吸引人 了實作 Class 和子產品,通過 Class 可以更好的面向對象程式設計,使用子產品加載友善子產品化編 程, 當然考慮到 浏覽器相容性, 我們在實際開發中需要使用babel 進行編譯

重要的特性:

塊級作用域:ES5 隻有全局作用域和函數作用域,塊級作用域的好處是不再需要立即執 行的函數表達式, 循環體中的閉包不再有問題

rest 參數: 用于擷取函數的多餘參數, 這樣就不需要使用arguments 對象了, promise:一種異步程式設計的解決方案, 比傳統的解決方案回調函數和事件更合理強大

子產品化:其子產品功能主要有兩個指令構成,export 和 import,export 指令用于規定子產品的 對外接口, import 指令用于輸入其他子產品提供的功能

call 和 apply 是用來做什麼?

參考回答:

Call 和 apply 的作用是一模一樣的, 隻是傳參的形式有差別而已

1 、改變 this 的指向

2 、借用别的對象的方法,

3 、調用函數, 因為 apply, call 方法會使函數立即執行

了解事件代理嗎, 這樣做有什麼好處

參考回答:

事件代理/事件委托: 利用了事件冒泡, 隻指定一個事件處理程式, 就可以管理某一類 型的事件,

簡而言之:事件代理就是說我們将事件添加到本來要添加的事件的父節點,将事件委托 給父節點來觸發處理函數,這通常會使用在大量的同級元素需要添加同一類事件的時候, 比如一個動态的非常多的清單,需要為每個清單項都添加點選事件,這時就可以使用事 件代理, 通過判斷 e.target.nodeName 來判斷發生的具體元素, 這樣做的好處是減少事件 綁定, 同僚動态的 DOM 結構任然可以監聽, 事件代理發生在冒泡階段。

如何寫一個繼承?

參考回答:

原型鍊繼承

核心: 将父類的執行個體作為子類的原型

特點:

非常純粹的繼承關系, 執行個體是子類的執行個體, 也是父類的執行個體

父類新增原型方法/原型屬性, 子類都能通路到

簡單, 易于實作

缺點:

要想為子類新增屬性和方法, 不能放到構造器中

無法實作多繼承

來自原型對象的所有屬性被所有執行個體共享

建立子類執行個體時, 無法向父類構造函數傳參

構造繼承

核心:使用父類的構造函數來增強子類執行個體,等于是複制父類的執行個體屬性給子類 (沒用 到原型)

特點:

解決了子類執行個體共享父類引用屬性的問題

建立子類執行個體時, 可以向父類傳遞參數

可以實作多繼承 (call 多個父類對象)

缺點:

執行個體并不是父類的執行個體, 隻是子類的執行個體

隻能繼承父類的執行個體屬性和方法, 不能繼承原型屬性/方法

無法實作函數複用, 每個子類都有父類執行個體函數的副本, 影響性能

執行個體繼承

核心: 為父類執行個體添加新特性, 作為子類執行個體傳回

特點:

不限制調用方式, 不管是new 子類()還是子類(),傳回的對象具有相同的效果 缺點:

執行個體是父類的執行個體, 不是子類的執行個體

不支援多繼承

拷貝繼承

特點:

支援多繼承

缺點:

效率較低, 記憶體占用高 (因為要拷貝父類的屬性)

組合繼承

核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然後通過将父類執行個體作 為子類原型, 實作函數複用

特點:

可以繼承執行個體屬性/方法, 也可以繼承原型屬性/方法

既是子類的執行個體, 也是父類的執行個體

不存在引用屬性共享問題

可傳參

函數可複用

寄生組合繼承

核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然後通過将父類執行個體作 為子類原型, 實作函數複用

參考

給出以下代碼, 輸出的結果是什麼? 原因? for(var i=0;i<5;i++){ setTimeout(function(){ console.log(i); },1000); } console.log(i)

參考回答:

在一秒後輸出 5 個 5

每次 for 循環的時候 setTimeout 都會執行,但是裡面的 function 則不會執行被放入任務隊 列, 是以放了 5 次;for 循環的 5 次執行完之後不到 1000 毫秒; 1000 毫秒後全部執行任 務隊列中的函數, 是以就是輸出5 個 5。

給兩個構造函數 A 和 B, 如何實作 A 繼承 B?

參考回答:

function A(...) {} A.prototype...

function B(...) {} B.prototype...

A.prototype = Object.create(B.prototype);

// 再在 A 的構造函數裡 new B(props);

for(var i = 0; i < lis.length; i++) {

lis[i].addEventListener('click', function(e) {

alert(i);

}, false)

}           

問能不能正常列印索引

參考回答:

在 click 的時候, 已經變成 length 了

如果已經有三個 promise, A 、B 和 C, 想串行執行, 該怎麼寫?

參考回答:

// promise

A.then(B).then(C).catch(...)

// async/await

(async ()=>{

await a();

await b();

await c();

})()           

知道 private 和 public 嗎

參考回答:

public:public 表明該資料成員、成員函數是對所有使用者開放的,所有使用者都可以直接進 行調用

private:private 表示私有,私有的意思就是除了 class 自己之外,任何人都不可以直接使用。

async 和 await 具體該怎麼用?

參考回答:

(async () = > {

await new promise();

})()           

知道哪些 ES6, ES7 的文法

參考回答:

promise, await/async, let 、const 、塊級作用域 、箭頭函數

promise 和 await/async 的關系

參考回答:

都是異步程式設計的解決方案

JS 的資料類型

參考回答:

字元串, 數字, 布爾, 數組, null, Undefined, symbol, 對象。

JS 加載過程阻塞, 解決方法。

參考回答:

指定 script 标簽的 async 屬性。

如果 async="async", 腳本相對于頁面的其餘部分異步地執行 (當頁面繼續進行解析時, 腳本将被執行)

如果不使用 async 且 defer="defer": 腳本将在頁面完成解析時執行

JS 對象類型, 基本對象類型以及引用對象類型的差別

參考回答:

分為基本對象類型和引用對象類型

基本資料類型:按值通路,可操作儲存在變量中的實際的值。基本類型值指的是簡單 資料段 。基本資料類型有這六種:undefined 、null 、string 、number 、boolean 、symbol。

引用類型: 當複制儲存着對象的某個變量時,操作的是對象的引用,但在為對象添加屬 性時, 操作的是實際的對象 。引用類型值指那些可能為多個值構成的對象。

引用類型有這幾種:Object、Array、RegExp、Date、Function、特殊的基本包裝類型(String、 Number 、Boolean)以及單體内置對象(Global 、Math)。

JavaScript 中的輪播實作原理? 假如一個頁面上有兩個輪播, 你會怎麼實作?

參考回答:

圖檔輪播的原理就是圖檔排成一行,然後準備一個隻有一張圖檔大小的容器,對這個容 器設定超出部分隐藏,在控制定時器來讓這些圖檔整體左移或右移,這樣呈現出來的效 果就是圖檔在輪播了。

如果有兩個輪播, 可封裝一個輪播元件, 供兩處調用

怎麼實作一個計算一年中有多少周?

參考回答:

首先你得知道是不是閏年, 也就是一年是 365 還是 366.

其次你得知道當年 1 月 1 号是周幾。假如是周五,一年 365 天把 1 号 2 号 3 号減去,也 就是把第一個不到一周的天數減去等于 362

還得知道最後一天是周幾, 加入是周五, 需要把周一到周五減去, 也就是 362-5=357. 正常情況 357 這個數計算出來是 7 的倍數 。357/7=51 。 即為周數。

引用類型常見的對象

參考回答:

Object、Array、RegExp、Date、Function、特殊的基本包裝類型(String、Number、Boolean) 以及單體内置對象(Global 、Math)等

class

參考回答:

ES6 提供了更接近傳統語言的寫法,引入了 Class (類) 這個概念,作為對象的模闆。通 過 class 關鍵字, 可以定義類。

口述數組去重

參考回答:

法一: indexOf 循環去重

法二: ES6 Set 去重; Array.from(new Set(array))

法三:Object 鍵值對去重;把數組的值存成 Object 的 key 值,比如 Object[value1] = true, 在判斷另一個值的時候, 如果 Object[value2]存在的話, 就說明該值是重複的。

箭頭函數和 function 有什麼差別

參考回答:

箭頭函數根本就沒有綁定自己的this,在箭頭函數中調用 this 時,僅僅是簡單的沿着作 用域鍊向上尋找, 找到最近的一個 this 拿來使用

new 操作符原理

參考回答:

1. 建立一個類的執行個體: 建立一個空對象 obj, 然後把這個空對象的__proto__設定為構造 函數的 prototype。

2. 初始化執行個體: 構造函數被傳入參數并調用, 關鍵字 this 被設定指向該執行個體obj。

3. 傳回執行個體 obj。

bind,apply,call

參考回答:

apply: 調用一個對象的一個方法, 用另一個對象替換目前對象 。例如: B.apply(A, arguments);即 A 對象應用 B 對象的方法。

call:調用一個對象的一個方法,用另一個對象替換目前對象。例如:B.call(A, args1,args2); 即 A 對象調用B 對象的方法 。 bind 除了傳回是函數以外, 它的參數和 call 一樣。

bind 和 apply 的差別

參考回答:

傳回不同: bind 傳回是函數

參數不同: apply(A, arguments), bind(A, args1,args2)

說 promise, 沒有 promise 怎麼辦

參考回答:

沒有 promise, 可以用回調函數代替

事件委托

參考回答:

把一個元素響應事件 (click 、keydown......) 的函數委托到另一個元素; 優點: 減少記憶體消耗 、動态綁定事件。

箭頭函數和 function 的差別

參考回答:

箭頭函數根本就沒有綁定自己的this,在箭頭函數中調用 this 時,僅僅是簡單的沿着作 用域鍊向上尋找, 找到最近的一個 this 拿來使用

arguments

參考回答:

arguments 是類數組對象, 有 length 屬性, 不能調用數組方法可用 Array.from()轉換

箭頭函數擷取 arguments

參考回答:

可用…rest 參數擷取

事件代理

參考回答:

事件代理是利用事件的冒泡原理來實作的,何為事件冒泡呢?就是事件從最深的節點開 始, 然後逐漸向上傳播事件,舉個例子: 頁面上有這麼一個節點樹,div>ul>li>a;比如給 最裡面的 a 加一個 click 點選事件, 那麼這個事件就會一層一層的往外執行, 執行順序 a>li>ul>div,有這樣一個機制,那麼我們給最外面的 div 加點選事件,那麼裡面的 ul,li, a 做點選事件的時候, 都會冒泡到最外層的div 上, 是以都會觸發, 這就是事件代理, 代理它們父級代為執行事件。

Eventloop

參考回答:

任務隊列中, 在每一次事件循環中, macrotask 隻會提取一個執行, 而 microtask 會一直 提取, 直到 microsoft 隊列為空為止。

也就是說如果某個 microtask 任務被推入到執行中, 那麼當主線程任務執行完成後, 會 循環調用該隊列任務中的下一個任務來執行,直到該任務隊列到最後一個任務為止。而 事件循環每次隻會入棧一個 macrotask,主線程執行完成該任務後又會檢查microtasks 隊 列并完成裡面的所有任務後再執行 macrotask 的任務 。macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering

microtasks: process.nextTick, Promise, MutationObserver

繼續閱讀