天天看點

JavaScript面試題大全之基礎面試題(附答案)

JavaScript面試題大全之基礎面試題(附答案)

近年來,從事web前端開發的程式員越來越多,都需要使用JavaScript,這篇文章主要整理一些最常見的JavaScript面試題以及答案。

介紹JavaScript的資料類型

值類型(基本類型):字元串(String)、數字(Number)、布爾(Boolean)、對空(Null)、未定義(Undefined)、Symbol(獨一無二的值)。

引用資料類型:對象(Object)、數組(Array)、函數(Function)。

注:Symbol 是 ES6 引入了一種新的原始資料類型,表示獨一無二的值。

基本資料類型與引用類型在存儲上有什麼差別?

1.存儲位置不同:

基本資料類型:以棧的形式存儲, 儲存與指派指向資料本身, 用typeof 來判斷類型,存儲空間固定。

引用類型:以堆的形式存儲, 儲存于指派指向對象的一個指針, 用instanceof 來判斷類型 , 存儲空間不固定。

2.傳值方式不同:

基本資料類型按值傳遞,無法改變一個基本資料類型的值

引用類型按引用傳遞,應用類型值可以改變

判斷 js 類型的方式

1. typeof

可以判斷出'string','number','boolean','undefined','symbol'

但判斷 typeof(null) 時值為 'object'; 判斷數組和對象時值均為 'object'

2. instanceof

原理是構造函數的 prototype 屬性是否出現在對象的原型鍊中的任何位置

function A() {}
let a = new A();
a instanceof A     //true,因為 Object.getPrototypeOf(a) === A.prototype;      

3. Object.prototype.toString.call()

常用于判斷浏覽器内置對象,對于所有基本的資料類型都能進行判斷,即使是 null 和 undefined​

Object.prototype.toString.call(null)//"[object Null]"
Object.prototype.toString.call(undefined)//"[object Undefined]"
Object.prototype.toString.call(Object)//"[object Function]"      

4. Array.isArray()

用于判斷是否為數組。

typeof運算符和instanceof運算符以及isPrototypeOf()方法的差別

typeof是一個運算符,用于檢測資料的類型,比如基本資料類型null、undefined、string、number、boolean,以及引用資料類型object、function,但是對于正規表達式、日期、數組這些引用資料類型,它會全部識别為object

instanceof同樣也是一個運算符,它就能很好識别資料具體是哪一種引用類型。它與isPrototypeOf的差別就是它是用來檢測構造函數的原型是否存在于指定對象的原型鍊當中;

而isPrototypeOf是用來檢測調用此方法的對象是否存在于指定對象的原型鍊中,是以本質上就是檢測目标不同。

NaN 是什麼

NaN 即非數值(Not a Number),NaN 屬性用于引用特殊的非數字值,該屬性指定的并不是不合法的數字。

NaN 屬性 與 Number.Nan 屬性相同。

提示: 請使用 isNaN() 來判斷一個值是否是數字。原因是 NaN 與所有值都不相等,包括它自己。

js中本地對象?内置對象?宿主對象?

本地對象:ECMA-262 把本地對象(native object)定義為“獨立于宿主環境的 ECMAScript 實作提供的對象”。

包含了:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError。

内置對象:ECMA-262 把内置對象(built-in object)定義為“由 ECMAScript 實作提供的、獨立于宿主環境的所有對象,在 ECMAScript 程式開始執行時出現”。這意味着開發者不必明确執行個體化内置對象,它已被執行個體化了。包含了:Gobal 和 Math。

宿主對象:由ECMAScript實作的宿主環境提供的對象,可以了解為:浏覽器提供的對象。所有的BOM和DOM都是宿主對象。

棧和堆的差別?

棧(stack):由編譯器自動配置設定釋放,存放函數的參數值,局部變量等;

堆(heap):一般由程式員配置設定釋放,若程式員不釋放,程式結束時可能由作業系統釋放。

描述以下變量的差別:null,undefined或undeclared

null 表示"沒有對象",即該處不應該有值,轉為數值時為0。典型用法是:

(1) 作為函數的參數,表示該函數的參數不是對象。

(2) 作為對象原型鍊的終點。

undefined 表示"缺少值",就是此處應該有一個值,但是還沒有定義,轉為數值時為NaN。典型用法是:

(1)變量被聲明了,但沒有指派時,就等于undefined。

(2) 調用函數時,應該提供的參數沒有提供,該參數等于undefined。

(3)對象沒有指派的屬性,該屬性的值為undefined。

(4)函數沒有傳回值時,預設傳回undefined。

undeclared :js文法錯誤,沒有申明直接使用,js無法找到對應的上下文。

for..in 和 object.keys的差別

Object.keys不會周遊繼承的原型屬性

for...in 會周遊繼承的原型屬性

JS中的匿名函數是什麼?

匿名函數:就是沒有函數名的函數,如:

(function(x, y){
    alert(x + y);  
})(2, 3);      

這裡建立了一個匿名函數(在第一個括号内),第二個括号用于調用該匿名函數,并傳入參數。

解釋 JS 中的函數提升

JS允許将聲明移動到頂部的預設行為稱為提升。JS中建立函數的兩種方法是函數聲明和函數表達式。

函數聲明

具有特定參數的函數稱為函數聲明,在JS中建立變量稱為聲明。如:

hoisted(); // logs "foo"
function hoisted() {
  console.log('foo');
}      

函數表達式

當使用表達式建立函數時,稱為函數表達式。如:

notHoisted(); // TypeError: notHoisted is not a function
var notHoisted = function() {
   console.log('bar');
};      

Js隐式轉換介紹

在js中,當運算符在運算時,如果兩邊資料不統一,CPU就無法計算,這時我們編譯器會自動将運算符兩邊的資料做一個資料類型轉換,轉成一樣的資料類型再計算

這種無需程式員手動轉換,而由編譯器自動轉換的方式就稱為隐式轉換

例如1 > "0"這行代碼在js中并不會報錯,編譯器在運算符時會先把右邊的"0"轉成數字0`然後在比較大小

隐式轉換規則:

1. 轉成string類型: +(字元串連接配接符) 2..轉成number類型:++/--(自增自減運算符) + - * / %(算術運算符) > < >= <= == != === !=== (關系運算符)

2. 轉成boolean類型:!(邏輯非運算符)

例子:

console.log([] == []) // false
console.log([] == ![]) // true
console.log([] !== [])  // true
console.log(NaN != NaN) // true
console.log(null == undefined) // true
console.log(null === undefined) // false
console.log(1 == true) // true
console.log(null > 0) // false
console.log(true + 1) // 2
console.log(undefined + 1) // NaN
console.log({} + 1) // [object Object]1
console.log([] + {}) // [object Object]
console.log([2,3] + [1,2])  // 2,31,2      

Object.is()與原來的比較操作符"==="、"==” 的差別?

(1)兩等号判等,會在比較時進行類型轉換;

(2)三等号判等(判斷嚴格),比較時不進行隐式類型轉換,(類 型不同則會傳回false);

(3)Object.is 在三等号判等的基礎上特别處理了NaN、-0和+0,保證-0和+0不再相同,但Object.is(NaN, NaN)會傳回true。Object.is應被認為有其特殊的用途,而不能用它認為它比其它的相等對比更寬松或嚴格。

JS 中 == 和 === 差別是什麼?

1、對于string,number等基礎類型,==和===有差別

1)不同類型間比較,==之比較“轉化成同一類型後的值”看“值”是否相等,===如果類型不同,其結果就是不等。

2)同類型比較,直接進行“值”比較,兩者結果一樣。

2、對于Array,Object等進階類型,==和===沒有差別

進行“指針位址”比較。

3、基礎類型與進階類型,==和===有差別

1)對于==,将進階轉化為基礎類型,進行“值”比較。

2)因為類型不同,===結果為false。

ES5 和 ES6 分别幾種方式聲明變量

ES5 有倆種:var 和 function

ES6 有六種:增加四種,let、const、class 和 import

注意:let、const、class聲明的全局變量再也不會和全局對象的屬性挂鈎。

什麼是事件代理/事件委托?

事件代理/事件委托是利用事件冒泡的特性,将本應該綁定在多個元素上的事件綁定在他們的祖先元素上,尤其在動态添加子元素的時候,可以非常友善的提高程式性能,減小記憶體空間。

什麼是事件冒泡?什麼是事件捕獲?

冒泡型事件:事件按照從最特定的事件目标到最不特定的事件目标(document對象)的順序觸發。

捕獲型事件:事件從最不精确的對象(document 對象)開始觸發,然後到最精确(也可以在視窗級别捕獲事件,不過必須由開發人員特别指定)。

在添加事件時用addEventListener(event,fn,useCapture)方法,基中第3個參數useCapture是一個Boolean值,用來設定事件是在事件捕獲時執行,還是事件冒泡時執行。

注意:IE浏覽器用attachEvent()方法,此方法沒有相關設定,不過IE的事件模型預設是在事件冒泡時執行的,也就是在useCapture等于false的時候執行,是以把在處理事件時把useCapture設定為false是比較安全,也實作相容浏覽器的效果。

如何阻止事件冒泡?

w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true。例如:

window.event.cancelBubble = true;
e.stopPropagation();      

return false也可以阻止冒泡。

如何阻止預設事件?

w3c的方法是e.preventDefault(),IE則是使用e.returnValue = false,比如:​

function stopDefault( e ) { 
 if ( e && e.preventDefault )         
   e.preventDefault(); //IE中阻止函數器預設動作的方式  
 else          
   window.event.returnValue = false; 
}      

return false也能阻止預設行為。 

DOM 事件有哪些階段?談談對事件代理的了解

分為三大階段:捕獲階段--目标階段--冒泡階段

事件代理簡單說就是:事件不直接綁定到某元素上,而是綁定到該元素的父元素上,進行觸發事件操作時(例如'click'),再通過條件判斷,執行事件觸發後的語句(例如'alert(e.target.innerhtml)')

好處:(1)使代碼更簡潔;(2)節省記憶體開銷

如何用原生js給一個按鈕綁定兩個onclick事件?

使用addEventListener的方式來綁定多個事件。例如​

var btn = document.getElementById('btn')
btn.addEventListener('click', fn1)
btn.addEventListener('click', fn2)
function fn1 () {
  console.log('我是方法1')  
}
function fn2 () {
  console.log('我是方法2')  
}      

響應事件有哪些?

onclick滑鼠點選某個對象;onfocus擷取焦點;onblur失去焦點;onmousedown滑鼠被按下等,常用的如下:

  1. 滑鼠單擊事件( onclick )
  2. 滑鼠經過事件( onmouseover )
  3. 滑鼠移開事件( onmouseout )
  4. 光标聚焦事件( onfocus )
  5. 失焦事件( onblur )
  6. 内容選中事件( onselect )
  7. 文本框内容改變事件( onchange )
  8. 加載事件( onload )
  9. 解除安裝事件( onunload )

閉包的概念?優缺點?使用場景?

閉包的概念:閉包就是能讀取其他函數内部變量的函數。

  1. 避免全局變量的污染
  2. 希望一個變量長期存儲在記憶體中(緩存變量)

缺點:

  1. 記憶體洩露(消耗)
  2. 常駐記憶體,增加記憶體使用量

使用場景:封裝功能時(需要使用私有的屬性和方法),函數防抖、函數節流、函數柯裡化、給元素僞數組添加事件需要使用元素的索引值。

造成記憶體洩露的原因

  1. 意外的全局變量(在函數内部沒有使用var進行聲明的變量)
  2. console.log
  3. 閉包
  4. 對象的循環引用
  5. 未清除的計時器
  6. DOM洩露(擷取到DOM節點之後,将DOM節點删除,但是沒有手動釋放變量,拿對應的DOM節點在變量中還可以通路到,就會造成洩露)

new操作符具體幹了什麼呢?

1) 建立一個空對象,并且 this 變量引用該對象,同時還繼承了該函數的原型。

2) 屬性和方法被加入到 this 引用的對象中。

3) 新建立的對象由 this 所引用,并且最後隐式的傳回 this 。

javascript中this的指向

this永遠指向函數運作時所在的對象,而不是函數被建立時所在的對象。

普通的函數調用,函數被誰調用,this就是誰。

構造函數的話,如果不用new操作符而直接調用,那即this指向window。用new操作符生成對象執行個體後,this就指向了新生成的對象。

匿名函數或不處于任何對象中的函數指向window 。

如果是call,apply等,指定的this是誰,就是誰。

談談this的了解

1) this總是指向函數的直接調用者(而非間接調用者)

2) 如果有new關鍵字,this指向new出來的那個對象

3) 在事件中,this指向目标元素,特殊的是IE的attachEvent中的this總是指向全局對象window。

eval是做什麼的?

它的功能是把對應的字元串解析成JS代碼并運作;應該避免使用eval,不安全,非常耗性能(2次,一次解析成js語句,一次執行)。

call,apply,bind的差別

call apply bind都可以改變函數調用的this指向。

函數.call(對象,arg1,arg2....)
函數.apply(對象,[arg1,arg2,...])
var ss=函數.bind(對象,arg1,arg2,....)      

1.第一個參數都是指定函數内部中this的指向(函數執行時所在的作用域),然後根據指定的作用域,調用該函數。

2.都可以在函數調用時傳遞參數。call,bind方法需要直接傳入,而apply方法需要以數組的形式傳入。

3.call,apply方法是在調用之後立即執行函數,而bind方法沒有立即執行,需要将函數再執行一遍。

4.改變this對象的指向問題不僅有call,apply,bind方法,也可以使用that變量來固定this的指向。

Javascript作用鍊域

作用域鍊的原理和原型鍊很類似,如果這個變量在自己的作用域中沒有,那麼它會尋找父級的,直到最頂層。

注意:JS沒有塊級作用域,若要形成塊級作用域,可通過(function(){})();立即執行的形式實作。

Javascript原型鍊

首先了解三個概念:

  • prototype:原型對象,每個函數都有一個 prototype 屬性,再通過 new 指令執行個體對象時,該屬性會成為該執行個體的原型對象。
  • constructor:構造函數。指向原型對象的 constructor
  • __proto__:執行個體對象的原型

在javascript中,執行個體對象與原型之間的連結,叫做原型鍊。Javascript解析引擎在讀取一個Object的屬性的值時,會沿着原型鍊向上尋找,如果最終沒有找到,則該屬性值為undefined;如果最終找到該屬性的值,則傳回結果。

繼承(6種方式)以及優缺點

1.原型鍊繼承

2.構造函數繼承

3.組合繼承(原型鍊繼承+構造函數繼承)

4.原型式繼承

5.寄生繼承

6.組合寄生繼承

代碼示例:

//借助構造函數實作繼承:缺點是父構造函數的原型鍊繼承不了,若要全部繼承除非将所有屬性和方法定義在構造函數中
function Parent1 () {
  this.name = 'parent1';
}
function Child1 () {
  //這麼做的好處是定義在父構造函數中的引用類型的屬性,對于子構造函數的每個執行個體來說是獨立的
  //并且在子構造函數執行個體化時,可以給父構造函數傳參
  Parent.call(this);
  this.type = 'child1';
}


//借助原型鍊實作繼承:缺點是繼承的引用類型屬性是共享的,子構造函數的執行個體更改會影響其他執行個體上的這個屬性,比如 play 屬性
function Parent2 () {
  this.name = 'parent2';
  this.play = [1, 2, 3];
}
function Child2 () {
  this.type = 'Child2';
}
Child2.prototype = new Parent2();


//組合方式:缺點是會執行兩次父構造函數
function Child3 () {
  //執行第一次
  Parent2.call(this);
  this.type = 'Child3';
}
Child3.prototype = new Parent2(); //執行第二次


//組合優化1,不需要再将定義在父構造函數中的屬性和方法再繼承一次,隻需要繼承原型鍊上的
Child3.prototype = Parent2.prototype;
//缺點是無法區分一個執行個體是子函構造函數執行個體化的還是父構造函數執行個體化的
let s1 = new Child3();
//s1.constructor 指向了 Parent2,而不是 Child3,因為 Child3 原型對象的屬性 constructor 繼承了 Parent2 原型對象上的
//如果你強行執行 Child3.prototype.constructor = Child3 的話,也會将 Parent2.prototype.constructor 改成 Child3


//組合優化2,通過 Object.create() 建立一個中間對象,将兩個原型對象差別開來,并且繼承父構造函數的原型鍊
Child3.prototype = Object.create(Parent2.prototype);
Child3.prototype.constructor = Child3;
//即 Child3.prototype.__proto__ === Parent2.prototype 為 true
//如此 Child3.prototype 和 Parent2.prototype 被隔離開,是兩個對象,不會互相影響
//這種方式為理想繼承方式      

請解釋變量聲明提升

通過var聲明的變量會被提升至作用域的頂端。不僅僅是變量,函數聲明也一樣會被提升。

當同一作用域内同時出現變量和函數聲明提升時,變量仍然在函數前面。

函數聲明與函數表達式的差別?

在Javscript中,解析器在向執行環境中加載資料時,對函數聲明和函數表達式并非是一視同仁的,解析器會率先讀取函數聲明,并使其在執行任何代碼之前可用(可以通路),至于函數表達式,則必須等到解析器執行到它所在的代碼行,才會真正被解析執行。

什麼是window對象? 什麼是document對象?

window對象代表浏覽器中打開的一個視窗。document對象代表整個html文檔。實際上,document對象是window對象的一個屬性。

document.onload和document.ready兩個事件的差別

頁面加載完成有兩種事件,一是ready,表示文檔結構已經加載完成(不包含圖檔等非文字媒體檔案),二是onload,訓示頁面包含圖檔等檔案在内的所有元素都加載完成。

Js的淺拷貝和深拷貝

淺拷貝隻複制指向某個對象的指針,而不複制對象本身,新舊對象還是共享同一塊記憶體。但深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享記憶體,修改新對象不會改到原對象。

淺拷貝

// 第一層為深拷貝
Object.assign()
Array.prototype.slice()
擴充運算符 ...      

深拷貝

JSON.parse(JSON.stringify())      

遞歸函數

function cloneObject(obj) {
var newObj = {} //如果不是引用類型,直接傳回
if (typeof obj !== 'object') {
return obj
  }
//如果是引用類型,周遊屬性
else {
for (var attr in obj) {
//如果某個屬性還是引用類型,遞歸調用
      newObj[attr] = cloneObject(obj[attr])
    }
  }
return newObj
}      

JavaScript裡arguments究竟是什麼?

Javascrip中國每個函數都會有一個Arguments對象執行個體arguments,它引用着函數的實參,可以用數組下标的方式"[]"引用arguments的元素。arguments.length為函數實參個數,arguments.callee引用函數自身。

在函數代碼中,使用特殊對象arguments,開發者無需明确指出參數名,通過使用下标就可以通路相應的參數。

function test() { 
var s = ""; 
for (var i = 0; i < arguments.length; i++) { 
  alert(arguments[i]);             
  s += arguments[i] + ","; 
 }
return s; 
} 
test("name", "age");//name,age      

arguments雖然有一些數組的性質,但其并非真正的數組,隻是一個類數組對象。其并沒有數組的很多方法,不能像真正的數組那樣調用.jion(),.concat(),.pop()等方法。

什麼是"use strict";?使用它的好處和壞處分别是什麼?

在代碼中出現表達式-"use strict"; 意味着代碼按照嚴格模式解析,這種模式使得Javascript在更嚴格的條件下運作。

好處:

  • 消除Javascript文法的一些不合理、不嚴謹之處,減少一些怪異行為;
  • 消除代碼運作的一些不安全之處,保證代碼運作的安全;
  • 提高編譯器效率,增加運作速度;
  • 為未來新版本的Javascript做好鋪墊。

壞處:

  • 同樣的代碼,在"嚴格模式"中,可能會有不一樣的運作結果;
  • 一些在"正常模式"下可以運作的語句,在"嚴格模式"下将不能運作。

什麼是跨域?有什麼方法解決跨域帶來的問題?

跨域需要針對浏覽器的同源政策來了解,同源政策指的是請求必須是同一個端口,同一個協定,同一個域名,不同源的用戶端腳本在沒有明确授權的情況下,不能讀寫對方資源。

受浏覽器同源政策的影響,不是同源的腳本不能操作其他源下面的對象。想要操作另一個源下的對象是就需要跨域。

常用解決方案:

  • 跨域資源共享(CORS)
  • nginx代理跨域
  • nodejs中間件代理跨域
  • jsonp跨域

解釋什麼是Json

(1)JSON 是一種輕量級的資料交換格式。

(2)JSON 獨立于語言和平台,JSON 解析器和 JSON 庫支援許多不同的程式設計語言。

(3)JSON的文法表示三種類型值,簡單值(字元串,數值,布爾值,null),數組,對象

解釋jsonp的原理,以及為什麼不是真正的ajax

Json是通過動态建立script标簽,回調函數的方式實作的,而Ajax是頁面無重新整理請求資料操作。

原生Js的實作:

(function (window,document) {
"use strict";
    var jsonp = function (url,data,callback) {
// 1.将傳入的data資料轉化為url字元串形式
// {id:1,name:'fly63'} => id=1&name=fly63
        var dataString = url.indexof('?') == -1? '?': '&';
for(var key in data){
            dataString += key + '=' + data[key] + '&';
        };
// 2 處理url中的回調函數
// cbFuncName回調函數的名字 :my_json_cb_名字的字首 + 随機數(把小數點去掉)
        var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');
        dataString += 'callback=' + cbFuncName;
// 3.建立一個script标簽并插入到頁面中
        var scriptEle = document.createElement('script');
        scriptEle.src = url + dataString;
// 4.挂載回調函數
window[cbFuncName] = function (data) {
            callback(data);
// 處理完回調函數的資料之後,删除jsonp的script标簽
document.body.removeChild(scriptEle);
        }
// 5.append到頁面中
document.body.appendChild(scriptEle);
    }
window.$jsonp = jsonp;// 因為jsonp是一個私有函數外部不能調用,所有jsonp函數作文window對象的一個方法,供外部調用
})(window,document)      

談談Cookie的弊端?

(1)Cookie數量和長度的限制。每個domain最多隻能有20條cookie,每個cookie長度不能超過4KB,否則會被截掉。

(2)安全性問題。如果cookie被 人攔截了,那人就可以取得所有的session資訊。即使加密也與事無補,因為攔截者并不需要知道cookie的意義,他隻要原樣轉發cookie就可以達到目的了。

(3)有些狀态不可能儲存在用戶端。例如,為了防止重複送出表單,我們需要在伺服器端儲存一個計數器。如果我們把這個計數器儲存在用戶端,那麼它起不到任何作用。

什麼是Cookie 隔離?(或者:請求資源的時候不要帶cookie怎麼做)

通過使用多個非主要域名來請求靜态檔案,如果靜态檔案都放在主域名下,那靜态檔案請求的時候帶有的cookie的資料送出給server是非常浪費的,還不如隔離開。因為cookie有域的限制,是以不能跨域送出請求,故使用非主要域名的時候,請求頭中就不會帶有cookie資料,這樣可以降低請求頭的大小,降低請求時間,進而達到降低整體請求延時的目的。同時這種方式不會将cookie傳入server,也減少了server對cookie的處理分析環節,提高了server的http請求的解析速度。

描述一下cookies,sessionStorage和localStorage的差別?

sessionStorage和localStorage是HTML5 Web Storage API提供的,可以友善的在web請求之間儲存資料。有了本地資料,就可以避免資料在浏覽器和伺服器間不必要地來回傳遞。sessionStorage、localStorage、cookie都是在浏覽器端存儲的資料,其中sessionStorage的概念很特别,引入了一個“浏覽器視窗”的概念。sessionStorage是在同源的同視窗(或tab)中,始終存在的資料。也就是說隻要這個浏覽器視窗沒有關閉,即使重新整理頁面或進入同源另一頁面,資料仍然存在。關閉視窗後,sessionStorage即被銷毀。同時“獨立”打開的不同視窗,即使是同一頁面,sessionStorage對象也是不同的cookies會發送到伺服器端。其餘兩個不會。Microsoft指出InternetExplorer8增加cookie限制為每個域名50個,但IE7似乎也允許每個域名50個cookie。

  • Firefox每個域名cookie限制為50個。
  • Opera每個域名cookie限制為30個。
  • Firefox和Safari允許cookie多達4097個位元組,包括名(name)、值(value)和等号。
  • Opera允許cookie多達4096個位元組,包括:名(name)、值(value)和等号。
  • InternetExplorer允許cookie多達4095個位元組,包括:名(name)、值(value)和等号。

localstorage不能手動删除的時候,什麼時候過期

除非被清除,否則永久儲存 clear()可清楚

sessionStorage 僅在目前會話下有效,關閉頁面或浏覽器後被清除

什麼是函數柯裡化

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

柯裡化的目的是,減少代碼備援,以及增加代碼的可讀性。下面看一個與柯裡化有關的經典例子:

// 實作一個add方法,使計算結果能夠滿足類似如下的預期:
// add(1)(2)(3) = 6;
// add(1, 2, 3)(4) = 10;
// add(1)(2)(3)(4)(5) = 15;
var add_currying=function(...rest){
  var sum=0;
for(let item of rest){
   sum+=item;
  }
  var add_back = (...rest) => {
for(let item of rest){
      sum+=item;
    }
return add_back;
 };
 add_back.toString = () => sum;
return add_back;
}
console.log(add_currying(1,2,3)); //6
console.log(add_currying(1,2)(3,4)); //10
console.log(add_currying(1,2)(3)(4,5)); //15
console.log(add_currying(1)(2)(3)(4)(5)(6)); //21
//列印出來會自動使用toString,即使是寫var a=add_currying(1,2,3)也會自動調用此方法(預設将函數語句以字元串打出)
//而為了列印出我們想要的結果我們就需要自己重寫toString方法
//如果不用es6的三點運算符就隻能使用以前的Array.prototype.slice.call(arguments)方法      

js如何處理防抖和節流?

在進行視窗的resize、scroll,輸入框内容校驗等操作時,如果事件處理函數調用的頻率無限制,會加重浏覽器的負擔,導緻使用者體驗非常糟糕。

此時我們可以采用debounce(防抖)和throttle(節流)的方式來減少調用頻率,同時又不影響實際效果。

函數防抖(debounce):

當持續觸發事件時,一定時間段内沒有再觸發事件,事件處理函數才會執行一次,如果設定的時間到來之前,又一次觸發了事件,就重新開始延時。

如下,持續觸發scroll事件時,并不執行handle函數,當1000毫秒内沒有觸發scroll事件時,才會延時觸發scroll事件。

function debounce(fn, wait) { 
var timeout = null; 
return function() { 
if(timeout !== null) clearTimeout(timeout);           
  timeout = setTimeout(fn, wait); 
 } 
} 
// 處理函數 
function handle() {      
console.log(Math.random()); 
} 
// 滾動事件
window.addEventListener('scroll', debounce(handle, 1000)); 函數節流      

函數節流(throttle):

當持續觸發事件時,保證一定時間段内隻調用一次事件處理函數。

節流通俗解釋就比如我們水龍頭放水,閥門一打開,水嘩嘩的往下流,秉着勤儉節約的優良傳統美德,我們要把水龍頭關小點,最好是如我們心意按照一定規律在某個時間間隔内一滴一滴的往下滴。

如下,持續觸發scroll事件時,并不立即執行handle函數,每隔1000毫秒才會執行一次handle函數。

var throttle =function(func, delay) { 
 var prev = Date.now(); 
 return function() { 
  var context = this;
  var args = arguments; 
  var now = Date.now(); 
  if (now - prev >= delay) { 
   func.apply(context, args);                           
   prev = Date.now();
  } 
 } 
}
function handle() {              
 console.log(Math.random()); 
}         
window.addEventListener('scroll', throttle(handle, 1000));      

JS裡垃圾回收機制是什麼,常用的是哪種,怎麼處理的?

JS的垃圾回收機制是為了以防記憶體洩漏,記憶體洩漏的含義就是當已經不需要某塊記憶體時這塊記憶體還存在着,垃圾回收機制就是間歇的不定期的尋找到不再使用的變量,并釋放掉它們所指向的記憶體。

JS中最常見的垃圾回收方式是标記清除。

工作原理:是當變量進入環境時,将這個變量标記為“進入環境”。當變量離開環境時,則将其标記為“離開環境”。标記“離開環境”的就回收記憶體。

工作流程:

  1. 垃圾回收器,在運作的時候會給存儲在記憶體中的所有變量都加上标記。
  2. 去掉環境中的變量以及被環境中的變量引用的變量的标記。
  3. 再被加上标記的會被視為準備删除的變量。
  4. 垃圾回收器完成記憶體清除工作,銷毀那些帶标記的值并回收他們所占用的記憶體空間。

那些操作會造成記憶體洩漏

全局變量、閉包、DOM清空或删除時,事件未清除、子元素存在引用

什麼是虛拟DOM

文檔對象模型或 DOM 定義了一個接口,該接口允許 JavaScript 之類的語言通路和操作 HTML 文檔。元素由樹中的節點表示,并且接口允許我們操縱它們。但是此接口需要付出代價,大量非常頻繁的 DOM 操作會使頁面速度變慢。

vue 通過在記憶體中實作文檔結構的虛拟表示來解決此問題,其中虛拟節點(VNode)表示 DOM 樹中的節點。當需要操縱時,可以在虛拟 DOM的 記憶體中執行計算和操作,而不是在真實 DOM 上進行操縱。這自然會更快,并且允許虛拟 DOM 算法計算出最優化的方式來更新實際 DOM 結構。

一旦計算出,就将其應用于實際的 DOM 樹,這就提高了性能,這就是為什麼基于虛拟 DOM 的架構(例如 vue 和 react)如此突出的原因。

Ajax使用

所謂異步,就是向伺服器發送請求的時候,我們不必等待結果,而是可以同時做其他的事情,等到有了結果它自己會根據設定進行後續操作,與此同時,頁面是不會發生整頁重新整理的,提高了使用者體驗。

建立Ajax的過程:

1) 建立XMLHttpRequest對象(異步調用對象)

var xhr = new XMLHttpRequest();      

2) 建立新的Http請求(方法、URL、是否異步)

xhr.open(‘get’,’example.php’,false);      

3) 設定響應HTTP請求狀态變化的函數。

onreadystatechange事件中readyState屬性等于4。響應的HTTP狀态為200(OK)或者304(Not Modified)。

4) 發送http請求

xhr.send(data);      

5) 擷取異步調用傳回的資料

注意:

1) 頁面初次加載時,盡量在web伺服器一次性輸出所有相關的資料,隻在頁面加載完成之後,使用者進行操作時采用ajax進行互動。

2) 同步ajax在IE上會産生頁面假死的問題。是以建議采用異步ajax。

3) 盡量減少ajax請求次數

4) ajax安全問題,對于敏感資料在伺服器端處理,避免在用戶端處理過濾。對于關鍵業務邏輯代碼也必須放在伺服器端處理。

Ajax的原生寫法

function ajax() {
  //建立一個 XHR 對象
  let oAjax = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new window.ActiveXobject('Microsoft.XMLHTTP'));
  //傳回一個函數,這是函數柯裡化操作,不用每次調用 ajax 都判斷浏覽器環境
  //但是會占用更多的記憶體,因為總是會儲存外部函數的作用域
return function(url, fnSucc, fnFaild) {
    //隻要 XHR 對象的 readyState 屬性的值發生改變,就觸發這個事件
    oAjax.onreadystatechange = function() {
      // readyState 屬性是 0-4 的值,當為 4 時,表示已經接收到全部響應資料,并可以在用戶端使用
if (oAjax.readyState === 4) {
        //響應的 HTTP 狀态
        let s = oAjax.status;
if (s === 200 || s === 206 || s === 304) {
          //将響應主體被傳回的文本作為參數傳給這個函數,并執行這個函數
if (fnSucc) fnSucc(oAjax.responseText);
        } else {
if (fnFaild) fnFaild(oAjax.status);
        }
      }
    };
    //啟動一個請求,準備發送
    oAjax.open('GET', url, true);
    //發送請求
    oAjax.send(null);
  }
}      

ajax請求的時候get 和post方式的差別

最直覺的差別就是GET把參數包含在URL中,POST通過request body傳遞參數。

  • GET在浏覽器回退時是無害的,而POST會再次送出請求。
  • GET産生的URL位址可以被Bookmark,而POST不可以。
  • GET請求會被浏覽器主動cache,而POST不會,除非手動設定。
  • GET請求隻能進行url編碼,而POST支援多種編碼方式。
  • GET請求參數會被完整保留在浏覽器曆史記錄裡,而POST中的參數不會被保留。
  • GET請求在URL中傳送的參數是有長度限制的,而POST麼有。
  • 對參數的資料類型,GET隻接受ASCII字元,而POST沒有限制。
  • GET比POST更不安全,因為參數直接暴露在URL上,是以不能用來傳遞敏感資訊。
  • GET參數通過URL傳遞,POST放在Request body中。

Javascript中,執行時對象查找時,永遠不會去查找原型的函數?

Object.hasOwnProperty(proName):是用來判斷一個對象是否有你給出名稱的屬性。不過需要注意的是,此方法無法檢查該對象的原型鍊中是否具有該屬性,該屬性必須是對象本身的一個成員。

JS延遲加載的方式有哪些?

JS的延遲加載有助與提高頁面的加載速度。

defer和async、動态建立DOM方式(用得最多)、按需異步載入JS

defer:延遲腳本。立即下載下傳,但延遲執行(延遲到整個頁面都解析完畢後再運作),按照腳本出現的先後順序執行。

async:異步腳本。下載下傳完立即執行,但不保證按照腳本出現的先後順序執行。

同步和異步的差別?

同步的概念在作業系統中:不同程序協同完成某項工作而先後次序調整(通過阻塞、喚醒等方式),同步強調的是順序性,誰先誰後。異步不存在順序性。

同步:浏覽器通路伺服器,使用者看到頁面重新整理,重新發請求,等請求完,頁面重新整理,新内容出現,使用者看到新内容之後進行下一步操作。

異步:浏覽器通路伺服器請求,使用者正常操作,浏覽器在後端進行請求。等請求完,頁面不重新整理,新内容也會出現,使用者看到新内容。

頁面編碼和被請求的資源編碼如果不一緻如何處理?

若請求的資源編碼,如外引js檔案編碼與頁面編碼不同。可根據外引資源編碼方式定義為 charset="utf-8"或"gbk"。

比如:http://www.fly63.com/a.html 中嵌入了一個http://www.fly63.com/test.js

a.html 的編碼是gbk或gb2312的。 而引入的js編碼為utf-8的 ,那就需要在引入的時候

<script src="http://www.fly63.com/test.js" charset="utf-8"></script>      

子產品化開發怎麼做?

子產品化開發指的是在解決某一個複雜問題或者一系列問題時,依照一種分類的思維把問題進行系統性的分解。

子產品化是一種将複雜系統分解為代碼結構更合理,可維護性更高的可管理的子產品方式。對于軟體行業:系統被分解為一組高内聚,低耦合的子產品。

(1)定義封裝的子產品

(2)定義新子產品對其他子產品的依賴

(3)可對其他子產品的引入支援。在JavaScript中出現了一些非傳統子產品開發方式的規範。 CommonJS的子產品規範,AMD(Asynchronous Module Definition),CMD(Common Module Definition)等。AMD是異步子產品定義,所有的子產品将被異步加載,子產品加載不影響後邊語句運作。

AMD與CMD規範差別?

AMD 是 RequireJS 在推廣過程中對子產品定義的規範化産出。CMD 是 SeaJS 在推廣過程中對子產品定義的規範化産出。差別:

  1. 1) 對于依賴的子產品,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。
  2. 2) CMD 推崇依賴就近,AMD 推崇依賴前置。
  3. 3) AMD 的 API 預設是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。

解釋頁面的回流與重繪

當render tree中的一部分(或全部)因為元素的規模尺寸,布局,隐藏等改變而需要重新建構。這就稱為回流(reflow)。

每個頁面至少需要一次回流,就是在頁面第一次加載的時候,這時候是一定會發生回流的,因為要建構render tree。

在回流的時候,浏覽器會使渲染樹中受到影響的部分失效,并重新構造這部分渲染樹,完成回流後,浏覽器會重新繪制受影響的部分到螢幕中,該過程成為重繪。

當render tree中的一些元素需要更新屬性,而這些屬性隻是影響元素的外觀,風格,而不會影響布局的,比如background-color。則就叫稱為重繪。

差別:

回流必将引起重繪,而重繪不一定會引起回流。比如:隻有顔色改變的時候就隻會發生重繪而不會引起回流

當頁面布局和幾何屬性改變時就需要回流,比如:添加或者删除可見的DOM元素,元素位置改變,元素尺寸改變——邊距、填充、邊框、寬度和高度,内容改變

浏覽器的滾動距離

可視區域距離頁面頂部的距離​

var scrollTop=document.documentElement.scrollTop||document.body.scrollTop      

可視區的大小

(1)innerXXX(不相容ie)

window.innerHeight 可視區高度,包含滾動條寬度

window.innerWidth 可視區寬度,包含滾動條寬度

(2)document.documentElement.clientXXX(相容ie)

document.documentElement.clientWidth可視區寬度,不包含滾動條寬度

document.documentElement.clientHeight可視區高度,不包含滾動條寬度

節點的種類有幾種,分别是什麼?

(1)元素節點:nodeType ===1;

(2)文本節點:nodeType ===3;

(3)屬性節點:nodeType ===2;

innerHTML和outerHTML的差別

innerHTML(元素内包含的内容)

outerHTML(自己以及元素内的内容)

document.write和innerHTML的差別

document.write 将内容寫入頁面,清空替換掉原來的内容,會導緻重繪

document.innerHTML 将内容寫入某個Dom節點,不會重繪

offsetWidth offsetHeight和clientWidth clientHeight的差別

(1)offsetWidth (content寬度+padding寬度+border寬度)

(2)offsetHeight(content高度+padding高度+border高度)

(3)clientWidth(content寬度+padding寬度)

(4)clientHeight(content高度+padding高度)

DOM操作

(1)建立新節點

createDocumentFragment() //建立一個DOM片段

createElement() //建立一個具體的元素

createTextNode() //建立一個文本節點

(2)添加、移除、替換、插入

appendChild()

removeChild()

replaceChild()

insertBefore() //在已有的子節點前插入一個新的子節點

(3)查找

getElementsByTagName() //通過标簽名稱

getElementsByName() //通過元素的Name屬性的值(IE容錯能力較強,會得到一個數組,其中包括id等于name值的)

getElementById() //通過元素Id,唯一性

BOM 和 DOM 的關系

BOM全稱Browser Object Model,即浏覽器對象模型,主要處理浏覽器視窗和架構。

DOM全稱Document Object Model,即文檔對象模型,是 HTML 和XML 的應用程式接口(API),遵循W3C 的标準,所有浏覽器公共遵守的标準。

JS是通過通路BOM(Browser Object Model)對象來通路、控制、修改用戶端(浏覽器),由于BOM的window包含了document,window對象的屬性和方法是直接可以使用而且被感覺的,是以可以直接使用window對象的document屬性,通過document屬性就可以通路、檢索、修改XHTML文檔内容與結構。因為document對象又是DOM的根節點。

可以說,BOM包含了DOM(對象),浏覽器提供出來給予通路的是BOM對象,從BOM對象再通路到DOM對象,進而js可以操作浏覽器以及浏覽器讀取到的文檔。

數組對象有哪些原生方法,列舉一下

pop、push、shift、unshift、splice、reverse、sort、concat、join、slice、toString、indexOf、lastIndexOf、reduce、reduceRight、forEach、map、filter、every、some

pop:删除并傳回數組最後一個元素(改變原數組);

push:傳回添加完成後的數組的長度(改變原數組);

shift:移除并傳回數組的第一個元素(改變原數組);

unshift:在數組頭部插入一個元素

slice:slice(下标,個數)傳回裁剪後的數組(不改變原數組);

splice:插入,删除或替換數組的元素

concat:合并數組傳回組合數組(不改變原數組);

join:将數組用辨別符連結成字元串傳回拼接好的字元串(不改變原數組);

reverse:翻轉數組(改變原數組);

toString:将數組轉換成一個字元串;

split:把字元串分割開,以數組方式儲存;

forEach:主要用于周遊數組;

every:主要用于檢查數組中每個元素是否符合函數的條件,如果其中有一個不符合,則傳回false;

indexOf:主要用于在數組中查找元素,并把元素的位置傳回來。

字元串方法

charAt():根據下标找到對應值

charCodeAt():通過下标值找到對應字元Unicode編碼

indexOf():通過字元查找對應下标(首次出現)

lastIndexOf():通過字元找最後一次出現的下标值

slice():截取字元串,2個參數,(起始位置,結束位置)

split():把字元串按分隔符分割成數組

substring():截取字元串,(起始位置,結束位置)

substr():截取指定位置和長度的字元串,(起始位置,長度)

toLowerCase():字元串轉為小寫

toUpperCase():字元串轉成大寫

trim():去掉字元串前後所有空格

JS中的Array.splice()和Array.slice()方法有什麼差別

話不多說,來看第一個例子:​

var arr=[0,1,2,3,4,5,6,7,8,9];//設定一個數組
console.log(arr.slice(2,7));//2,3,4,5,6
console.log(arr.splice(2,7));//2,3,4,5,6,7,8
//由此我們簡單推測數量兩個函數參數的意義,
slice(start,end)第一個參數表示開始位置,第二個表示截取到的位置(不包含該位置)
splice(start,length)第一個參數開始位置,第二個參數截取長度      

接着看第二個:​

var x=y=[0,1,2,3,4,5,6,7,8,9]
console.log(x.slice(2,5));//2,3,4
console.log(x);[0,1,2,3,4,5,6,7,8,9]原數組并未改變
//接下來用同樣方式測試splice
console.log(y.splice(2,5));//2,3,4,5,6
console.log(y);//[0,1,7,8,9]顯示原數組中的數值被剔除掉了      

slice和splice雖然都是對于數組對象進行截取,但是二者還是存在明顯差別,函數參數上slice和splice第一個參數都是截取開始位置,slice第二個參數是截取的結束位置(不包含),而splice第二個參數(表示這個從開始位置截取的長度),slice不會對原數組産生變化,而splice會直接剔除原數組中的截取資料!

如何在JS中動态添加/删除對象的屬性?

咱們可以使用object.property_name = value向對象添加屬性,delete object.property_name 用于删除屬性。

例如:​

let user = new Object();
// adding a property
user.name='Anil';
user.age  =25;
console.log(user);
delete user.age;
console.log(user);      

圖檔的懶加載和預加載

預加載:常用的是new Image();,設定其src來實作預載,再使用onload方法回調預載完成事件。​

function loadImage(url, callback){
    var img = new Image(); //建立一個Image對象,實作圖檔預下載下傳
    img.src = url;
    if (img.complete){
         // 如果圖檔已經存在于浏覽器緩存,直接調用回調函數
        callback.call(img);
        return; // 直接傳回,不用再處理onload事件
    }
    img.onload = function (){
    //圖檔下載下傳完畢時異步調用callback函數。
    callback.call(img);//将回調函數的this替換為Image對象 ,如果你直接用img.width的時候,圖檔還沒有完全下載下傳下來
    };
}      

懶加載:主要目的是作為伺服器前端的優化,緩解伺服器前端壓力,一次性請求次數減少或延遲請求。實作方式:

1.第一種是純粹的延遲加載,使用setTimeOut、setInterval進行加載延遲.

2.第二種是條件加載,符合某些條件,或觸發了某些事件才開始異步下載下傳。

3.第三種是可視區加載,即僅加載使用者可以看到的區域,這個主要由監控滾動條來實作,一般會在距使用者看到某圖檔前一定距離遍開始加載,這樣能保證使用者拉下時正好能看到圖檔。

什麼是回調

回調函數是作為參數或選項傳遞給某個方法的普通JS函數。它是一個函數,在另一個函數完成執行後執行,是以稱為回調。

在JS中,函數是對象,是以,函數可以接受函數作為參數,并且可以由其他函數傳回。

JS中的宿主對象與原生對象有何不同?

宿主對象:這些是運作環境提供的對象。這意味着它們在不同的環境下是不同的。例如,浏覽器包含像windows這樣的對象,但是Node.js環境提供像Node List這樣的對象。

原生對象:這些是JS中的内置對象。它們也被稱為全局對象,因為如果使用JS,内置對象不受是運作環境影響。

漸進增強與優雅降級

漸進增強:針對低版本浏覽器進行建構頁面,保證最基本的功能,然後再針對進階浏覽器進行效果、互動等改進,達到更好的使用者體驗。

優雅降級:一開始就建構完整的功能,然後再針對低版本浏覽器進行相容。

Web Worker和Web Socket?

web socket:在一個單獨的持久連接配接上提供全雙工、雙向的通信。使用自定義的協定(ws://、wss://),同源政策對web socket不适用。

web worker:運作在背景的JavaScript,不影響頁面的性能。

建立worker:var worker = new Worker(url);

向worker發送資料:worker.postMessage(data);

接收worker傳回的資料:worker.onmessage

終止一個worker的執行:worker.terminate();

浏覽器的線程

JS引擎線程:解釋執行JS代碼、使用者輸入、網絡請求等

GUI線程(渲染線程):繪制使用者界面、與JS主線程互斥

HTTP網絡請求線程:處理使用者的GET、POST等請求,等拿到傳回結果後,将回調函數推入事件隊列

定時器觸發線程:setTimeout、setInterval等待時間結束後,将執行函數推入事件隊列中

事件處理線程:将click、mouse、input等互動事件發生後,将事件處理函數推入事件隊列中

js 執行機制、事件循環

JavaScript 語言的一大特點就是單線程,同一個時間隻能做一件事。單線程就意味着,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等着。

JavaScript 語言的設計者意識到這個問題,将所有任務分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous),在所有同步任務執行完之前,任何的異步任務是不會執行的。

當我們打開網站時,網頁的渲染過程就是一大堆同步任務,比如頁面骨架和頁面元素的渲染。而像加載圖檔音樂之類占用資源大耗時久的任務,就是異步任務。

JS 異步有一個機制,就是遇到宏任務,先執行宏任務,将宏任務放入 Event Queue,然後再執行微任務,将微任務放入 Event Queue,但是,這兩個 Queue 不是一個 Queue。

當你往外拿的時候先從微任務裡拿這個回調函數,然後再從宏任務的 Queue 拿宏任務的回調函數。

宏任務:整體代碼 script,setTimeout,setInterval

微任務:Promise,process.nextTick

JS為什麼要區分微任務和宏任務

(1)js是單線程的,但是分同步異步

(2)微任務和宏任務皆為異步任務,它們都屬于一個隊列

(3)宏任務一般是:script,setTimeout,setInterval、setImmediate

(4)微任務:原生Promise

(5)遇到微任務,先執行微任務,執行完後如果沒有微任務,就執行下一個宏任務,如果有微任務,就按順序一個一個執行微任務

減少頁面加載時間的方法

  • 優化圖檔
  • 圖像格式的選擇(GIF:提供的顔色較少,可用在一些對顔色要求不高的地方)
  • 優化css(壓縮合并css,如margin-top,margin-left...)
  • 網址後加斜杠(如www.campr.com/目錄,會判斷這個“目錄是什麼檔案類型,或者是目錄。)
  • 标明高度和寬度(如果浏覽器沒有找到這兩個參數,它需要一邊下載下傳圖檔一邊計算大小,如果圖檔很多,浏覽器需要不斷地調整頁面。這不但影響速度,也影響浏覽體驗。當浏覽器知道了高度和寬度參數後,即使圖檔暫時無法顯示,頁面上也會騰出圖檔的空位,然後繼續加載後面的内容。進而加載時間快了,浏覽體驗也更好了。)
  • 減少http請求(合并檔案,合并圖檔)。

線程與程序的差別

一個程式至少有一個程序,一個程序至少有一個線程。線程的劃分尺度小于程序,使得多線程程式的并發性高。

另外,程序在執行過程中擁有獨立的記憶體單元,而多個線程共享記憶體,進而極大地提高了程式的運作效率。

線程在執行過程中與程序還是有差別的。每個獨立的線程有一個程式運作的入口、順序執行序列和程式的出口。

但是線程不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制。

從邏輯角度來看,多線程的意義在于一個應用程式中,有多個執行部分可以同時執行。但作業系統并沒有将多個線程看做多個獨立的應用,來實作程序的排程和管理以及資源配置設定。這就是程序和線程的重要差別。

說說你對語義化的了解?

1.去掉或樣式丢失的時候能讓頁面呈現清晰的結構:html本身是沒有表現的,我們看到例如<h1>是粗體,字型大小2em,加粗;<strong>是加粗的,不要認為這是html的表現,這些其實html預設的css樣式在起作用。

是以去掉或樣式丢失的時候能讓頁面呈現清晰的結構不是語義化的HTML結構的優點,但是浏覽器都有有預設樣式,預設樣式的目的也是為了更好的表達html的語義,可以說浏覽器的預設樣式和語義化的HTML結構是不可分割的。

2.螢幕閱讀器(如果訪客有視障)會完全根據你的标記來“讀”你的網頁。

3.PDA、手機等裝置可能無法像普通電腦的浏覽器一樣來渲染網頁(通常是因為這些裝置對CSS的支援較弱)。

4.有利于SEO:和搜尋引擎建立良好溝通,有助于爬蟲抓取更多的有效資訊:爬蟲依賴于标簽來确定上下文和各個關鍵字的權重。

5.便于團隊開發和維護,語義化更具可讀性,是下一步吧網頁的重要動向,遵循W3C标準的團隊都遵循這個标準,可以減少差異化。

為什麼利用多個域名來提供網站資源會更有效?

1.CDN緩存更友善

2.突破浏覽器并發限制(一般每個域名建立的連結不超過6個)

3.Cookieless,節省帶寬,尤其是上行帶寬一般比下行要慢

4.對于UGC的内容和主站隔離,防止不必要的安全問題(上傳js竊取主站cookie之類的)。正是這個原因要求使用者内容的域名必須不是自己主站的子域名,而是一個完全獨立的第三方域名。

5.資料做了劃分,甚至切到了不同的實體叢集,通過子域名來分流比較省事。這個可能被用的不多。

PS:關于Cookie的問題,帶寬是次要的,安全隔離才是主要的。關于多域名,也不是越多越好,雖然伺服器端可以做泛解釋,浏覽器做dns解釋也是耗時間的,而且太多域名,如果要走https的話,還有要多買證書和部署的問題。

你如何組織,優化自己的代碼?

對内:子產品模式;對外:繼承

代碼重用

避免全局變量(命名空間,封閉空間,子產品化mvc…)

拆分函數避免函數過于臃腫

注釋

前端子產品化群組件化

子產品化:可複用,側重的功能的封裝,主要是針對Javascript代碼,隔離、組織複制的javascript代碼,将它封裝成一個個具有特定功能的的子產品。

元件化:可複用,更多關注的UI部分,頁面的每個部件,比如頭部,彈出框甚至确認按鈕都可以成為一個元件,每個元件有獨立的HTML、css、js代碼。

http的cache機制及作用?

定義:浏覽器緩存(Browser Caching)是為了加速浏覽,浏覽器在使用者磁盤上對最近請求過的文檔進行存儲,當通路者再次請求這個頁面時,浏覽器就可以從本地磁盤顯示文檔,這樣就可以加速頁面的閱覽。

cache的作用:

1、減少延遲,讓你的網站更快,提高使用者體驗。

2、避免網絡擁塞,減少請求量,減少輸出帶寬。

實作手段:

Cache-Control中的max-age是實作内容cache的主要手段,共有3種常用政策:max-age和Last-Modified(If-Modified-Since)的組合、僅max-age、max-age和ETag的組合。

對于強制緩存,伺服器通知浏覽器一個緩存時間,在緩存時間内,下次請求,直接用緩存,不在時間内,執行比較緩存政策。

對于比較緩存,将緩存資訊中的Etag和Last-Modified通過請求發送給伺服器,由伺服器校驗,傳回304狀态碼時,浏覽器直接使用緩存。

HTTP 常見的狀态碼

400 用戶端請求有文法錯誤,不能被伺服器所了解

403 伺服器收到請求但是拒絕提供服務|

200 用戶端請求成功|

404 請求資源不存在 eg:輸入錯誤的URL |

500 伺服器發生不可預期的錯誤 |

503 伺服器目前不能處理用戶端請求,一段時間後可能恢複正常

JS 中的主要有哪幾類錯誤

JS有三類的錯誤:

加載時錯誤:加載web頁面時出現的錯誤(如文法錯誤)稱為加載時錯誤,它會動态生成錯誤。

運作時錯誤:由于濫用html語言中的指令而導緻的錯誤。

邏輯錯誤:這些錯誤是由于對具有不同操作的函數執行了錯誤的邏輯而導緻的

列出JS中的一些設計模式

設計模式是軟體設計中常見問題的通用可重用解決方案,以下是一些設計模式是:

建立模式:該模式抽象了對象執行個體化過程。

結構型模式:這些模式處理不同的類和對象以提供新功能。

行為模式:也稱釋出-訂閱模式,定義了一個被觀察者和多個觀察者的、一對多的對象關系。

并行設計模式:這些模式處理多線程程式設計範例。

架構設計模式:這些模式用于處理架構設計。

es6的新特性

  1. const let
  2. 模闆字元串
  3. 箭頭函數
  4. 函數的參數預設值
  5. 對象和數組解構
  6. for...of 和 for...in

解釋一下什麼是箭頭函數?

箭頭函數是在es6或更高版本中編寫函數表達式的簡明方法。箭頭函數不能用作構造函數,也不支援this,arguments,super或new.target關鍵字,它最适合非方法函數。 

通常,箭頭函數看起來像 const function_name =()=> {}。

const greet=()=>{console.log('hello');}
greet();      

普通函數和箭頭函數的差別

1、普通函數

可以通過bind、call、apply改變this指向

可以使用new

2、箭頭函數

本身沒有this指向,

它的this在定義的時候繼承自外層第一個普通函數的this

被繼承的普通函數的this指向改變,箭頭函數的this指向會跟着改變

箭頭函數外層沒有普通函數時,this指向window

不能通過bind、call、apply改變this指向

使用new調用箭頭函數會報錯,因為箭頭函數沒有constructor

解釋一下什麼是 promise ?

promise是js中的一個對象,用于生成可能在将來産生結果的值。 值可以是已解析的值,也可以是說明為什麼未解析該值的原因。

promise 可以有三種狀态:

pending:初始狀态,既不是成功也不是失敗

fulfilled:意味着操作完全成功

rejected:意味着操作失敗

一個等待狀态的promise對象能夠成功後傳回一個值,也能失敗後帶回一個錯誤,當這兩種情況發生的時候,處理函數會排隊執行通過then方法會被調用。

resolve和reject分别是兩個函數,當在回調中調用時,會改變promise執行個體的狀态,resolve改變狀态為成功,reject為失敗。

手寫promise 

function Promise(exector) {
  let _this = this;
  //status表示一種狀态
  let status = “pending“;
  let value = undefined;
  let reason = undefined;
  //成功
  function resolve(value) {
    if (status == “pending“) {
      _this.value = value;
      _this.status = “resolve“;
    }
  }
  //執行失敗
  function reject(value) {
    if (status == “pending“) {
      _this.value = value;
      _this.status = “reject“
    }
  }
  //異步操作
  try {
    exector(resolve, reject)
  } catch (e) {
    reject(e)
  }
  //測試then
  Promise.prototype.then = function(reject, resolve) {
    let _this = this;
    if (this.status == “resolve“) {
      reject(_this.value)
    }
    if (this.status == “reject“) {
      resolve(_this.reason)
    }
  }
} 
//new Promise測試
let promise = new Promise((reject,resolve)=>{
    resolve(“return resolve“);
});
promise.then(data => {
  console.log(`success${data}`);
}, err => {
  console.log(`err${data}`);
})      

promise、async有什麼差別

1、什麼是Async/Await

async/await是寫異步代碼的新方式,使用的方式看起來像同步

async/await是基于Promise實作的,它不能用于普通的回調函數。

2、什麼是promise

為了解決異步嵌套而産生,讓代碼更易于了解

差別:async/await讓代碼更像同步,進一步優化了代碼

介紹各種異步方案

(1)回調函數(異步回調)

回調是一個函數被作為一個參數傳遞到另一個函數裡,在那個函數執行完後再執行。

(2)promise 

promise對象是commonJS工作組提出的一種規範,一種模式,目的是為了異步程式設計提供統一接口。

(3)async/await

(4)事件監聽

采用事件驅動模式。任務的執行不取決于代碼的順序,而取決于某個事件是否發生。

(5)釋出/訂閱

module.exports 和 exports 之間有什麼差別?

module和exports是Node.js給每個js檔案内置的兩個對象。可以通過console.log(module)和console.log(exports)列印出來。如果你在main.js中寫入下面兩行,然後運作$ node main.js:​

console.log(exports);//輸出:{}
console.log(module);//輸出:Module {..., exports: {}, ...} (注:...代表省略了其他一些屬性)      

從列印咱們可以看出,module.exports和exports一開始都是一個空對象{},實際上,這兩個對象指向同一塊記憶體。這也就是說module.exports和exports是等價的(有個前提:不去改變它們指向的記憶體位址)。

import 和 exports 是什麼?

//index.js
 import name,age from './person'; 
 console.log(name);
 console.log(age);


 //person.js
 let name ='Sharad', occupation='developer', age =26;
 export { name, age};