知識體系來源於一名【合格】前端工程師的自檢清單
winter
在他的《重學前端》課程中提到:
到現在爲止,前端工程師已經成爲研發體系中的重要崗位之一。可是,與此相對的是,我發現極少或者幾乎沒有大學的計算機專業願意開設前端課程,更沒有系統性的教學方案出現。大部分前端工程師的知識,其實都是來自於實踐和工作中零散的學習。
一.JavaScript基礎
變量和類型
-
Javascript規定了幾種語言類型
七種內置類型null,undefined,string,number,boolean,object,symbol
undefined:1.undefined類型表示爲定義,它的值只有一個undefined
2.任何變量賦值前都是undefined類型,值爲undefined
3.undefined是一個變量不是關鍵字
null:1.只有一個值就是null 2.表示空值是關鍵字,可用null關鍵字獲取null
string:1.string的意義並非字符串,而是字符串UTF16編碼
2.字符串是永遠無法變更的
number:1.number類型有264- 253+3 個值。
2.根據雙精度浮點數定義,有效的整數範圍是 -0x1fffffffffffff 至 0x1fffffffffffff,無法精確表示此範圍外的整數。
3.根據雙精度浮點數定義,非整數的Number類型無法用==來比較(三個等號也不行),正確的比較方法是用 JavaScript提供的最小精度值:
console.log( 0.1 + 0.2 == 0.3);//false
//正確的比較方法
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);//true
symbol:1.表示獨一無二的值,它是一切非字符串的對象key的集合
2.Symbol 值通過Symbol函數生成。這就是說,對象的屬性名現在可以有兩種類型
object:object是js中最複雜的類型。也是js的核心機制之一,object是對象,在js中,對象的定義是“屬性的集合”,屬性分爲數據屬性和訪問器屬性,二者都是key-value的結構,key可以是字符串或者symbol類型
除了七種語言類型,還有規範類型 :
1.list和record:用於描述函數傳參的過程
2.set:主要用於解釋字符集等
3.completion record:用於描述異常、跳出等語句執行過程
4.reference:用於描述對象屬性訪問、delete等
5.property descriptor:用於描述對象的屬性
6.lexical environment和environment record:用於描述變量和作用域
7.data block:用於描述二進制數據
-
Javascript對象的底層數據結構是什麼
什麼是數據結構?百度百科:數據結構是計算機存儲、組織數據的方式。數據結構是指相互之間存在一種或多種特定關係的數據元素的集合。精心選擇的數據結構可帶來更高運行或者存儲效率。
其實就是數據的事,結構組織。不再關注數據,而是關注組織數據,數據就事一堆書本,怎麼擺放合適
常用的數據結構:1.棧和隊列 2.單向鏈接列表和雙重鏈接列表 3.樹(深度優先搜索和廣度優先搜索)
堆是動態分配內存,內存大小不一,也不會自動釋放
棧是自動分配相對固定大小的內存空間,並由系統自動釋放,棧現金後出,隊列後勁先出
JS基本類型數據都是直按值存儲在棧中的(undefined、null、不是new出來的布爾,數字,字符串),每種類型的數據佔用的內存空間大小是確定的,更容易管理內存空間
JS引用類型數據存儲於堆中(如對象,數據,函數等),引用類型的數據的地址指針是存儲於棧中的,當我們想要訪問引用類型的值的時候,需要先從棧中獲得對象的地址指針,然後,在通過地址指針找到堆中的所需要的數據
數據在內存中的存儲結構分爲兩種:
順序存儲結構:吧數據元素存放在地址連續的存儲單元裏比如數組
鏈式存儲結構:把數據元素存放在內次年任意的存儲單元內比如鏈表
JavaScript的數據結構,ES5中自帶的array、object,ES6自帶的set、map、weakset、weakmap
在JavaScript中不管多複雜的數據,都可以組織成object形式的對象。對象大多表現爲Dictionary如{a: foo,b :bar}
-
Symbol
類型在實際開發中的應用、可手動實現一個簡單的Symbol
由於每一個Symbol值都是不相等的,這意味着Symbol值可以作爲標識符,用於對象的屬性名,就能保證不會出現同名的屬性。這對於一個對象由多個模塊構成的情況非常有用,能防止某一個鍵被不小心改寫或覆蓋。
應用1:使用Symbol來作爲對象屬性名(key)
Symbol類型的key是不能通過Object.keys()
或者for...in
來枚舉的,因此我們可把不需對外操作和訪問屬性使用symbol定義,如想獲取可以使用Object.getOwnPropertySymbols()或Reflect.ownKeys()
應用2:使用Symbol來代替常量
應用3:使用Symbol定義類的私有屬性/方法
應用4:註冊和獲取全局Symbol
使用Symbol.for()可以註冊或獲取一個window間全局的Symbol實例
-
JavaScript
中的變量在內存中的具體存儲形式
JavaScript中的變量分爲基本類型和引用類型
基本類型是保存在棧內存中的簡單數據段,它們的值都有固定的大小,保存在棧空間,通過按值訪問
引用類型是保存在堆內存中的對象,值大小不固定,棧內存中存放的該對象的訪問地址指向堆內存中的對象,JavaScript不允許直接訪問堆內存中的位置,因此操作對象時,實際操作對象的引用
-
基本類型對應的內置對象,以及他們之間的裝箱拆箱操作
String()、Number()、Boolean()、RegExp()、Date()、Error()、Array()、
Function()、Object()、symbol();類似於對象的構造函數
裝箱轉換:基本類型 -> 對象
1.每一種基本類型,都在對象中有對應的類,裝箱機制會頻繁產生臨時對象
2.使用object函數可以顯示調用裝箱能力
3.每一類裝箱對象皆有私有的 Class 屬性,這些屬性可以Object.prototype.toString 獲取。在 JavaScript 中,沒有任何方法可以更改私有的Class 屬性,因此 Object.prototype.toString 是可以準確識別對象對應的基本類型的方法,它比 instanceof 更加準確。
4.call函數本身會產生裝箱操作,需要配合typeof來區分基本類型還是對象類型。
拆箱轉換:對象 -> 基本類型
1.ToPrimitive 函數,它是對象類型到基本類型的轉換
2.拆箱轉換會嘗試調用 valueOf 和 toString 來獲得拆箱後的基本類型。如果valueOf 和 toString都不存在,或者沒有返回基本類型,則會產生TypeError。
-
理解值類型和引用類型
值類型:string、number、boolean、undefined、null
1.佔用空間固定,保存在棧中
2.保存與複製的是值的本身
3.使用typeof檢測數據類型
4.基本類型數據是值類型
引用類型:Object、Array、Function
1.佔用空間不固定,保存在堆內
2.保存於複製的是隻想對象的一個指針
3.使用instanceof檢測數據類型
4.使用new()方法構造出的對象是引用型的
-
null
和undefined
的區別
Undefined類型只有一個值,即undefined。當聲明的變量還未被初始化時,變量的默認值爲undefined。
Null類型也只有一個值,即null。null用來表示尚未存在的對象,常用來表示函數企圖返回一個不存在的對象。
-
至少可以說出三種判斷
JavaScript
數據類型的方式,以及他們的優缺點,如何準確的判斷數組類型
1.typeof():無法判斷null與object,無法區分引用數據類型
2.instanceof:判斷一個變量是否是某個對象的實例,無法對原始類型進行判斷
3.Object.prototype.toString.call():此方法提供了一個通用的數據類型判斷模式,但不能判斷自定義類
4.constructor:constructor屬性返回對創建此對象的數組函數的引用,返回對象相對應的構造函數
在MDN中就比較了Array.isArray和instanceof的區別,當Array.isArray()不可用的使用,MDN做了如下的補丁,說明還是比較推薦使用前面講的第三種方法 Object.prototype.toString.call(obj)。
-
可能發生隱式類型轉換的場景以及轉換原則,應如何避免或巧妙應用
在JS中數據類型隱式轉換分三種情況:
1.轉換爲布爾類型 :
數據類型 | 轉換後的值 |
0 | false |
NaN | false |
空字符 | false |
null | false |
undefined | false |
非0數字 | true |
非空字符串 | true |
非null對象類型 | true |
連續使用兩個非操作符(!!)可以將一個數強制轉換爲boolean類型
2.轉換爲number類型
3.轉換爲string類型
操作符會影響數據的類型轉換
Typescript避免
-
出現小數精度丟失的原因,
JavaScript
可以存儲的最大數字、最大安全數字,JavaScript
處理大數字的方法、避免精度丟失的方法。
由於進制問題導致的小數相乘精度不準確,eg:
let a = 0.00001;
let b = 0.00002;
let c = 0.00003;
a + b === c //false
最大數字2的53次方減一
解決辦法
把小數放到位整數(乘倍數),再縮小回原來倍數(除倍數)
function accMul(arg1,arg2){
var m=0,s1=arg1.toString(),s2=arg2.toString();
try{m+=s1.split(".")[1].length}catch(e){}
try{m+=s2.split(".")[1].length}catch(e){}
return (Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m)).toFixed(2);
}
原型和原型鏈
-
理解原型設計模式以及JS中的原型規則
何爲原型?所有對象有私有字段[prototype],就是對象的原型。讀取一個對象的屬性,如果對象本身沒有就到對象的原型上找,直到原型爲空
設計模式
1.工廠模式:在函數內創建一個對象,給對象賦予屬性及方法再將對象返回
function Person() {
var People = new Object();
People.name = 'CrazyLee';
People.age = '25';
People.sex = function(){
return 'boy';
};
return People;
}
var a = Person();
console.log(a.name);//CrazyLee
console.log(a.sex());//boy
2.構造函數模式:無需在函數內部重新創建對象,而是用this指代
function Person() {
this.name = 'CrazyLee';
this.age = '25';
this.sex = function(){
return 'boy'
};
}
var a = new Person();
console.log(a.name);//CrazyLee
console.log(a.sex());//boy
3.原型模式:函數中部隊屬性進行定義,利用prototype屬性對屬性進行定義,可以讓所有對象實例共享它所包含的屬性及方法
function Parent() {
Parent.prototype.name = 'carzy';
Parent.prototype.age = '24';
Parent.prototype.sex = function() {
var s="女";
console.log(s);
}
}
var x =new Parent();
console.log(x.name); //crazy
console.log(x.sex()); //女
4.混合模式:原型模式 + 構造函數模式。構造函數模式用於定義實例屬性,原型模式用於定義方法和共享屬性
function Parent(){
this.name="CrazyLee";
this.age=24;
};
Parent.prototype.sayname=function(){
return this.name;
};
var x =new Parent();
console.log(x.sayname());
5.動態原型模式:將所有信息封裝在構造函數中,通過構造函數中初始化原型,可以通過判斷該方法是否有效而選擇是否需要初始化原型
function Parent(){
this.name="CrazyLee";
this.age=24;
if(typeof Parent._sayname=="undefined"){
Parent.prototype.sayname=function(){
return this.name;
}
Parent._sayname=true;
}
};
var x =new Parent();
console.log(x.sayname())
原型規則
1.所有引用類型,都具有對象特徵,可自由擴展屬性
2.所有的引用類型,都有一個_proto_屬性(隱式原型),屬性只是一個普通對象
3.所有函數都具有一個prototype(顯式原型),屬性值也是一個普通原型
4.所有的因通用類型,其隱式原型只想其構造函數的顯式原型(obj.proto === Object.prototype)
5.當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那麼會去他的_proto_中去尋找
原型對象:prototype 在js中,函數對象其中一個屬性:原型對象prototype。普通對象沒有prototype屬性,但有_proto_屬性。 原型的作用就是給這個類的每一個對象都添加一個統一的方法,在原型中定義的方法和屬性都是被所以實例對象所共享。
var person = function(name){
this.name = name
};
person.prototype.getName=function(){//通過person.prototype設置函數對象屬性
return this.name;
}
var crazy= new person(‘crazyLee’);
crazy.getName(); //crazyLee//crazy繼承上屬性
原型鏈:當試圖得到一個對象f的某個屬性時,如果這個對象本身沒有這個屬性,那麼會去它的_proto_(即它的構造函數的prototype)obj._proto_中去尋找;當obj._proto也沒有時,便會在obj._proto.proto(即obj的構造函數的prototype的構造函數的prototype)中尋找;
-
instanceof的底層實現原理,手動實現一個instanceof
instanceof 的作用:用於判斷一個引用類型是否屬於某構造函數,還可以在繼承關係中用來判斷一個實例是否屬於它的父類型
instance底層工作原理
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__;
}
}
手動實現instanceof
1.直接使用instanceof工作原理?
2.在方法一的基礎上使用constructor
function instance_of(L, R) {//L 表示左表達式,R 表示右表達式
var O = R;
L = L.__proto__;
while (true) {
if (L === null)
return false;
if (O === L.constructor) // 這裏重點:當 O 嚴格等於 L 時,返回 true
return true;
L = L.__proto__;
}
}
-
實現繼承的幾種方式以及他們的優缺點
在ES6中有了繼承,使用extends關鍵字就能實現,在ES6之前的繼承方式
1.原型鏈
基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
原型鏈雖然很強大,但存在兩個問題:(1)包含引用類型值的原型屬性會被所有實例共享,這回到這對一個實例的修改會影響另一個實例 (2)在創建子類型的實例時,不能向超類型的構造函數中傳遞參數
function SuperType(){
this.prototype=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
}
function SubType(){
this.subproperty=false;
}
//通過創建SuperType的實例繼承了SuperType
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subproperty;
}
var instance=new SubType();
alert(instance.getSuperValue()); //true
function SuperType(){
this.colors=["red", "blue", "green"];
}
function SubType(){
}
//繼承了SuperType
SubType.prototype=new SuperType();
var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
var instance2=new SubType();
alert(instance2.colors); //red,blue,green,black
2.借用構造函數
在子類型構造函數的內部調用超類型構造函數。函數只不過是在特定環境中執行代碼的對象,因此可通過使用apply()和call()方法在新創建的對象上執行構造函數
function SuperType(){
this.colors=["red", "blue", "green"];
}
function SubType(){
//繼承SuperType
SuperType.call(this);
}
var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors); //red,bllue,green,black
var instance2=new SubType();
alert(instance2.colors); //red,blue,green
相比於原型鏈,借用構造函數可在子類型構造函數中向超類型構造函數傳遞參數
function SuperType(name){
this.name=name;
}
function SubType(){
//繼承了SuperType,同時還傳遞了參數
SuperType.call(this,"mary");
//實例屬性
this.age=22;
}
var instance=new SubType();
alert(instance.name); //mary
alert(instance.age);
存在兩個問題:(1)無法避免構造函數模式存在的問題,方法都在構造函數中定義,因此無法複用函數
(2)在超類型的原型中定義的方法,對子類型而言是不可見的
3.組合繼承
將原型鏈和借用構造函數組合一起,使用原型鏈實現對原型方法的繼承,通過借用構造函數來實現對實例屬性的繼承,這樣,既通過在原型上定義方法實現了函數的複用,又能夠保證每個實例都有它自己的屬性
function SuperType(name){
this.name=name;
this.colors=["red", "blue", "green"];
}
SuperType.prototype.sayName=function(){
alert(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(){
alert(this.age);
};
var instance1=new SubType("mary", 22);
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
instance1.sayName(); //mary
instance1.sayAge(); //22
var instance2=new SubType("greg", 25);
alert(instance2.colors); //red,blue,green
instance2.sayName(); //greg
instance2.sayAge(); //25
-
至少說出一種開源項目(如Node)中應用原型繼承的案例
node中util.inherits()
export.inherits = function (ctor, superCtor){
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
util.inherit只是繼承了父類原型鏈裏的方法,還有super_
只是構造函數的一個屬性。而second.prototype = new first();
繼承了所有方法。也就是說util.inherits
相當於second.prototype = first.prototype;
-
可以描述new一個對象的詳細過程,手動實現一個new操作符
new操作發生了什麼?:
1.以構造器的prototype屬性爲原型,創建對象
2.將this和調用參數傳給構造器,執行
3.如果構造器返回的是對象,則放回,否則返回第一步創建的對象
詳細過程: 1.創建一個空對象
var obj = new Object();
2.讓Person中的this指向obj,並執行Person的函數體
var result = Person.call(obj);
3.設置原型鏈,將obj的_proto_成員只想Person函數對象的prototype成員對象
obj.__proto__ = Person.prototype;
4.判斷person的返回值類型,如果是值類型返回obj。如果是引用類型返回這個引用類型的對象
if (typeof(result) == "object")
person = result;
else
person = obj;
手動實現:創建空對象,鏈接到原型,綁定this值,返回新對象
function create(){
//創建一個空對象
let obj = new Object();
//獲取構造函數
let Constructor = [].shift.call(arguments);
//鏈接到原型
obj.__proto__ = Constructor.prototype;
//綁定this值
let result = Constructor.apply(obj,arguments);//使用apply,將構造函數中的this指向新對象,這樣新對象就可以訪問構造函數中的屬性和方法
//返回新對象
return typeof result === "object" ? result : obj;//如果返回值是一個對象就返回該對象,否則返回構造函數的一個實例對象
}
-
理解es6 class構造以及繼承的底層實現原理
ES6中的類,可以看作構造函數的另一個寫法,類的數據類型就是函數,類本身就指向構造函數
function People(name,age){
this.name = name;
this.age = age;
}
People.prototype.say=function(){
console.log("hello)}
People.see=function(){
alert("how are you")}
class People{
constructor(name,age){
this.name = name;
this.age = age}
static see(){alert("how are you")} }
say(){console.log("hello");}}
Class可以通過extends關鍵字實現繼承,實質是先將父類實例對象的屬性和方法加到this上面(所以必須先調用super方法),然後再用子類的構造函數修改this
作用域和閉包
-
理解詞法作用域和動態作用域
作用域是指程序源代碼中定義變量的區域,規定了如何查找代碼。就是變量與函數的可訪問範圍
詞法作用域,也叫靜態作用域,它的作用域是指再詞法分析階段就確定了,不會改變。
動態作用域是在運行時根據程序的流程信息來動態確定的,而不是寫代碼是進行靜態確定的
-
理解Javascript的作用域和作用域鏈
JS中作用域控制着變量與函數的可見性和生命週期。變量的作用域分爲
1.全局作用域:在代碼任何地方都能訪問到的對象擁有全局作用域
(1)在最外層函數和在最外層函數外面定義的變量擁有全局作用域
(2)所有未定義直接賦值的變量自動聲明爲擁有全局作用域
(3)所有window對象的屬性擁有全局作用域
2.局部作用域:函數內部可訪問到
3.塊級作用域:let爲JS新增了塊級作用域
作用域鏈:函數內部屬性[Scope]包含了函數被創建的作用域中的集合,這個集合被稱爲函數的作用域鏈,它決定了哪些數據能被函數訪問。變量都是對象的屬性,而該對象可能又是其它對象的屬性,而所有的對象都是全局對象的屬性,所以這些對象的關係可以看作是一條鏈,鏈頭就是變量所處的對象,鏈尾就是全局對象。
-
理解JavaScript的執行上下文棧,可以應用堆棧信息快速定位問題
當JavaScript代碼執行的時候回進入不同的執行環境(執行上下文),這些執行環境會構成一個執行環境棧(執行上下文棧)
執行上下文棧是執行上下文的活動記錄(數據的出棧和壓棧)。
1. 在全局代碼執行前, JS引擎就會創建一個棧來存儲管理所有的執行上下文對象
2. 在全局執行上下文確定後, 將其添加到棧中(壓棧)
3. 在函數執行上下文創建後, 將其添加到棧中(壓棧)
4. 在當前函數執行完後,將棧頂的對象移除(出棧)
5. 當所有的代碼執行完後, 棧中只剩下在全局執行上下文
Error.captureStackTrace 會捕獲堆棧信息, 並在第一個參數中創建 stack 屬性來存儲捕獲到的堆棧信息. 如果提供了第二個參數, 該函數將作爲堆棧調用的終點. 因此, 捕獲到的堆棧信息將只顯示該函數調用之前的信息.
-
this的原理以及幾種不同使用場景的取值
this指的是函數運行時所在的環境。
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 單獨執行
f() // 1
// obj 環境執行
obj.f() // 2
-
閉包的實現原理和作用,可以列舉幾個開發中閉包的實際應用
閉包就是能夠讀取其他函數內部變量的函數。閉包可以簡單理解成“定義在一個函數內部的函數“,在本質上,閉包是將函數內部和函數外部連接起來的橋樑。
作用:1.可以讀取函數內部的變量。 2.讓這些變量的值始終保持在內存中。
實際應用:1.setTimeout
function func(param) {
return function() {
alert(param);
}
}
var f = func(1)
setTimeout(f, 1000); // 原生的setTimeout有一個缺陷,你傳遞的第一個函數不能帶參數
2.封裝變量。即把變量隱藏起來
function isFirstLoad() {
var _list = []
return function (id) {
if (_list.indexOf(id) >= 0) {
return false
} else {
_list.push(id)
return true
}
}
}
// 使用
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true
-
理解堆棧溢出和內存泄漏的原理,如何防止
程序代碼運行需要計算空間,就是棧,就像小學生算術需要一張演算紙一樣。//這裏不考慮堆,或者堆就是多那幾張草稿紙。
每個子函數都需要一些局部變量,這需要在演算紙上佔用空間。
程序從棧底開始計算,隨着各個子函數的調用(入棧)和返回(出棧),佔用的棧資源也不斷的增加減少。這個過程如果能可視化,就是那個音樂節奏燈,忽閃忽閃的一會高一會兒低的 節奏燈。
棧溢出的意思就是系統分配的演算紙不夠用了,你把數字寫到紙外面了。
遞歸調用容易產生這個,是因爲遞歸的層級是動態的,不像非遞歸程序, 非遞歸程序中存在一個最深的函數調用鏈,只要最深的這個鏈不棧溢出就可以了,而一般遞歸無法給出這個保證,若遞歸層次太深就棧溢出了。尾遞歸可以解決這個問題。
尾調用尾調用由於是函數的最後一步操作,所以不需要保留外層函數的調用記錄。
尾遞歸:函數調用自身,稱爲遞歸。如果尾調用自身,就稱爲尾遞歸。
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
上面代碼是一個階乘函數,計算n的階乘,最多需要保存n個調用記錄,複雜度 O(n) 。
如果改寫成尾遞歸,只保留一個調用記錄,複雜度 O(1) 。
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
-
如何處理循環的異步操作
回調函數,事件監聽,發佈/訂閱,ES6中的Promise和Generator。
-
理解模塊化解決的實際問題,列舉幾個模塊化方案並理解其中原理
前端模塊化主要解決兩個問題:命名空間衝突,文件依賴管理
模塊化構建工具:webpack/requireJS/seaJS等是用來組織前端模塊的構建工具
模塊化規範:AMD/CMD/CommonJS/es6模塊等等規範,規範瞭如何來組織你的代碼
執行機制
-
爲何
try
裏面放return
,finally
還會執行,理解其內部機制
finally是在return後面的表達式運算之後執行的,此時並沒有返回運算之後的值,而是把值保存起來,不管finally對該值做任何的改變,返回的值都不會改變,依然返回保存起來的值。也就是說方法的返回值是在finally運算之前就確定了的。
先執行try中的語句,包括return後面的表達式,
有異常時,先執行catch中的語句,包括return後面的表達式,
然後執行finally中的語句,如果finally裏面有return語句,會提前退出,
最後執行try中的return,有異常時執行catch中的return。
-
JavaScript
如何實現異步編程,可以詳細描述EventLoop
機制
JavaScript是單線程,實現異步操作的方式藉助了瀏覽器的其他線程的幫助。其他線程通過任務隊列(task queue)和事件循環(event loop)。
任務隊列:在JavaScript異步機制中,任務隊列就是用來維護異步任務回調函數的隊列。這樣一個隊列用來存放這些回調函數,它們會等到主線程執行完所有的同步函數之後按照先進先出的方式挨個執行。
事件循環:主線程在執行完同步任務之後,會無限循環地去檢查任務隊列中是否有新的“任務”,如果有則執行。而這些任務包括我們在異步任務中定義的回調函數,也包括用戶交互事件的回調函數。通過事件循環,Javascript不僅很好的處理了異步任務,也很好的完成了與用戶交互事件的處理。因爲在完成異步任務的回調函數之後,任務隊列中的任務都是由事件所產生的,因此我們也把上述的循環過程叫做事件循環。
-
宏任務和微任務分別有哪些
宏任務:包括整體代碼script,setTimeout,setInterval
微任務:Promise,process.nextTick(類似node.js版的"setTimeout")
-
可以快速分析一個複雜的異步嵌套邏輯,並掌握分析方法
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
// 1 4 5 2 3
整體script作爲第一個宏任務進入主線程,輸出1
遇到setTimeout,回調函數分發到宏任務Event Queue中
遇到Promise,直接執行new Promise,輸出4
then備份發到微任務Event Queue
第一輪宏任務事件循環結束,執行微任務輸出5
第一輪事件循環結束,第二輪事件循環從setTimeout宏任務開始,輸出2
遇到process.nextTick(),分發到微任務Event Queue
第輪二宏任務事件循環結束,執行微任務輸出3
-
使用Promise實現串行
使用Promise.all()實現並行
// 生成一個Promise對象的數組
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
Promise實現串行
function promiseQueue (executors) {
return new Promise((resolve, reject) => {
if (!Array.isArray(executors)) { executors = Array.from(executors) }
if (executors.length <= 0) { return resolve([]) }
var res = []
executors = executors.map((x, i) => () => {
var p = typeof x === 'function' ? new Promise(x) : Promise.resolve(x)
p.then(response => {
res[i] = response
if (i === executors.length - 1) {
resolve(res)
} else {
executors[i + 1]()
}
}, reject)
})
executors[0]()
})
}
-
Node與瀏覽器EventLoop的差異
node的事件輪詢與瀏覽器不太一樣
timers階段:執行setTimeout,setInterval的callback回調
I/O callbacks階段:執行除了close事件的callbacks、被timers(定時器,setTimeout、setInterval 等)設定的 callbacks 、setImmediate() 設定的 callbacks 之外的 callbacks
idle,prepare階段:node 內部使用,Process.nextTick 在此階段執行
poll 階段:獲取新的 I/O 事件, 適當的條件下 node 將阻塞在這裏
check 階段:執行 setImmediate() 的回調函數
close callbacks 階段:執行 close 事件的 callback ,例如 socket.on('close', callback);
setTimeout(function () {
console.log('execute in first timeout');
Promise.resolve(3).then(res => {
console.log('execute in third promise');
});
}, 0);
setTimeout(function () {
console.log('execute in second timeout');
Promise.resolve(4).then(res => {
console.log('execute in fourth promise');
});
}, 0);
Promise.resolve(1).then(res => {
console.log('execute in first promise');
});
Promise.resolve(2).then(res => {
console.log('execute in second promise');
});
基於node:
1.promise也就是micor task 在下個階段之前被調用
2.當處於timers階段時,會將對應的timer隊列處理完,故 2個timeout會先被執行,再會進入下面一個狀態,此時纔會調用promise
execute in first promise
execute in second promise
execute in first timeout
execute in second timeout // node 先執行
execute in third promise
execute in fourth promise
-
如何在保證頁面運行流暢的情況下處理海量數據
首選分頁
策略:顯示三屏數據,其他的移除 DOM。
語法和API
-
理解ECMAScript和JavaScript的關係
在阮一峯的《ES6入門》提到:ECMAScript 和 JavaScript 的關係是,前者是後者的規格,後者是前者的一種實現。日常場合,這兩個詞是可以互換的。
-
熟練運用es5、es6提供的語言規範
-
熟練掌握JavaScript提供的全局對象(例如Data、Math)、全局函數、全局屬性
全局對象包括:Array、Boolean、Number、String、RegExp、Data、Math、Function、Events
-
熟練應用map、reduce、filter等高階函數解決問題
-
setInterval需要注意的點,使用settimeout實現setInterval
setInterval() 方法可以按照值定的週期來調用函數或計算表達式。setInterval() 方法會不停地調用函數,直到 clearInterval() 被調用或窗口被關閉。
注意點:1.當使用setInterval時,僅當沒有該定時器的任何其他代碼實例時,纔將定時器代碼添加到隊列中。
2.不能傳遞帶參數的函數
3.
setInterval 週期性的調用函數或計算方法,關閉用clearInterval 。setInterval 和clearInterval 是一對一的關係。比如想要對同一個按鈕在不同場景中,使用週期性的調用不同的函數,那麼需要先關掉上一個setInterval,再設定另一個setInterval不然上一個setInterval仍然在進行着。
迭代setTimeout
爲了避免setInterval()定時器的問題,可以使用鏈式setTimeout()調用
setTimeout(function fn(){
setTimeout(fn,interval);
},interval);
在前一個定時器代碼執行完之前,不會向隊列插入新的定時器代碼,確保不會有任何缺失的間隔。
-
JavaScript提供的正則表達式API、可以使用正則表達式解決常見問題(郵箱校驗、URL解析、去重等)
search()和replace()方法
使用RegExp對象有test()檢測一個字符串是否匹配某個模式,使用exec()檢索字符串中的正則表達式的匹配
/*是否帶有小數*/
function isDecimal(strValue ) {
var objRegExp= /^\d+\.\d+$/;
return objRegExp.test(strValue);
}
/*校驗是否中文名稱組成 */
function ischina(str) {
var reg=/^[\u4E00-\u9FA5]{2,4}$/; /*定義驗證表達式*/
return reg.test(str); /*進行驗證*/
}
/*校驗是否全由8位數字組成 */
function isStudentNo(str) {
var reg=/^[0-9]{8}$/; /*定義驗證表達式*/
return reg.test(str); /*進行驗證*/
}
/*校驗電話碼格式 */
function isTelCode(str) {
var reg= /^((0\d{2,3}-\d{7,8})|(1[3584]\d{9}))$/;
return reg.test(str);
}
/*校驗郵件地址是否合法 */
function IsEmail(str) {
var reg=/^\w+@[a-zA-Z0-9]{2,10}(?:\.[a-z]{2,4}){1,3}$/;
return reg.test(str);
}
/*匹配URL*/
/^(https?:\/\/)([0-9a-z.]+)(:[0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9-a-z]+)?/i
https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]
/*去重*/
var str = "abcabcccddf";
var res = str.replace(/(.).*\1/g,"$1")
-
JavaScript異常處理的方式,統一的異常處理方案
JS可以通過 try...catch..finally語句構造+throw運算符 來處理異常
try {
// 運行代碼
[break;]
}
catch ( e ) {
// 如果發生異常,則運行代碼
[break;]
}
[ finally {
// 無論如何,始終執行的代碼
// 異常發生
}]