前端面試複習之JS複習

前排提示:感謝@程序員小鹿 寫的系列文章 這是我作的他文章的摘抄而已 用於學習html

JS基礎

原始類型和引用類型(對象類型)

  • 原始類型(6個)
    • String
    • Number
    • Null
    • Undefined
    • Symbol
    • Boolean
  • 引用類型(對象類型)(5個)
    • Object
    • Array
    • Function
    • Date
    • RegExp

數據類型的存儲形式

  1. 棧內存(stack)和堆內存(heap)
  2. Stack自動分配內存,Heap動態分配內存
  3. 通常在項目中將對象類型置爲null,減小內存消耗
    • 圖示:
  4. 原始類型按值存在stack中,可按值直接訪問
  5. 對象類型在stack中存放引用地址,在heap中存放具體對象

Null

  1. typeof null === Object
    • javaScript中數據二進制前三位都是0的話,系統就會斷定爲Object類型。
前三位 類型
000 對象
1 整型
010 雙精度
100 字符串
110 布爾

數據類型的判斷typeof/instanceof

  • typeof是一元運算符,返回String類型。
  • typeof除了null類型和對象類型不能準確判斷,其餘都能返回正確類型。
    • 例外:function能夠
  • instanceof判斷某個對象是否是另外一個對象的實例,返回boolean
  • instanceof用來測試一個對象在其原型鏈中是否存在一個構造函數的prototype屬性。

類型轉換

  • javaScript是弱類型語言,因此特定狀況咱們須要類型轉換
  • 分爲顯式類型轉換和隱式類型轉換

顯式類型轉換

即強制類型轉換java

String類型程序員

對於原始類型,轉String會默認調用toString()方法web

String(123)              //"123"
String(true)             //"true"
String(null)             //"null"
String(undefined)        //"undefined"
String([1,2,3])          //"1,2,3"
String(function(){111})  //"function(){111}"
String({})               //[Object Object]
複製代碼

轉Boolean類型ajax

除了幾個falsy值,''undefinednullNAN0false,其餘都轉爲true編程

轉Number類型json

其餘類型 數字類型
字符串 1. 數字轉化爲數字
2.其餘轉化爲NaN
布爾類型 true=>1,false=>0
null 0
undefined NaN
數組 1.數組爲空轉化爲0
2.數組只有一個元素轉爲該元素
3.其餘轉化爲NaN
空字符串 0
Number(10);        // 10 
Number('10');      // 10 
Number(null);      // 0 
Number('');        // 0 
Number(true);      // 1 
Number(false);     // 0 
Number([]);        // 0 
Number([1,2]);     // NaN
Number('10a');     // NaN
Number(undefined); // NaN
複製代碼

對象類型轉原始類型數組

  • 對象類型轉原始類型會調用內置的valueOf()toString()方法
  • 轉化原始類型又分爲:轉化爲字符串類型和其餘原始類型
    • 若是已是原始類型,就無須再轉化
    • 若是轉String,就調用toString()
    • 若是是其餘原始類型,就調用valueOf()
    • 若是返回的不是原始類型,繼續調用toString()
    • 若是尚未返回原始類型,就報錯

隱式類型轉換

加法運算瀏覽器

加法運算符是在運行時決定相加仍是鏈接,這被稱爲"重載"。bash

若是雙方都不是String

  • Boolean+Boolean會轉化爲數字相加
  • Boolean+Number,布爾轉化爲數字再加
  • Object+Number,對象類型調用valueOf,若是不是StringBoolean或者Number類型,則繼續調用toString()轉化爲字符串
true + true  // 2
1 + true     // 2
[1] + 3      // '13' 
複製代碼

數組的valueOf()返回的仍是數組自己,因此會繼續調用toString()

字符串和字符串以及字符串與非字符串相加都會變鏈接

1 + 'b'     // '1b'
false + 'b' // 'falseb'
'1' + 1     //'11'
複製代碼

其餘運算

其餘算術運算符,減法除法乘法一概所有轉爲數字,再運算

1 * '2'  // 2
1 * []   // 0
複製代碼

邏輯運算符

條件判斷

  • &&:全部條件爲真,才爲真
  • ||:只要一個條件真,就真

賦值操做

  • A&&B

若是A真就過,賦值爲B;若是A假就不過,直接就是A

  • A||B

若是A爲真就直接是A,若是A爲假就爲B

比較運算符 =====

===是嚴格意義上的相等,必須類型和值都相等。

==先判斷兩邊類型是否相等,若是不等,先轉換類型,再判斷值是否相等。

this

this就是一個對象,不一樣狀況this指向不一樣。

  • 對象調用,this指向該對象
var obj = {
    name:'小鹿',
    age: '21',
    print: function(){
        console.log(this)
        console.log(this.name + ':' + this.age)
    }
}

// 經過對象的方式調用函數
obj.print();        // this 指向 obj
複製代碼
  • 直接調用的函數,this指向window對象
function print(){
	console.log(this);
}
// 全局調用函數
print();   // this 指向 window
複製代碼
  • 經過new 的方式,this永遠指向新建立的對象
function Person(name,age){
  this.name = name
  this.age = age
  console.log(this)
}

var xiaohu = new Person('小胡',24)// this = > xiaohu
複製代碼
  • 箭頭函數中的this

箭頭函數沒有單獨的this值,其this與聲明所在的上下文相同。也就是說調用箭頭函數的時候,不會隱式調用this參數,而是從定義時的函數繼承上下文

const obj = {
    a:()=>{
        console.log(this);
    }
}
// 對象調用箭頭函數
obj.a(); // window
複製代碼

改變this的指向

能夠經過調用函數的callapplybind來改變this指向

var obj = {
    name:'小鹿',
    age:'22',
    adress:'小鹿動畫學編程'
}

function print(){
    console.log(this);       // 打印 this 的指向
    console.log(arguments);  // 打印傳遞的參數
}

// 經過 call 改變 this 指向
print.call(obj,1,2,3);   

// 經過 apply 改變 this 指向
print.apply(obj,[1,2,3]);

// 經過 bind 改變 this 的指向
let fn = print.bind(obj,1,2,3);
fn();
複製代碼

共同點:

  • 三者都能改變this指向,且第一個參數都是this指向的對象
  • 三者都採用後續傳參的形式

不一樣點:

  • call的傳參是單個傳遞,apply後續參數是數組形式,bind均可以
  • callapply是直接執行,bind會返回一個函數,在調用時纔會執行

箭頭函數不能用callapply改變this指向,由於他沒有本身的this指向,若是用callapply只會傳遞後續參數。

new關鍵字

new的過程包括四個階段:

  • 建立一個新對象。
  • 新對象的__proto__屬性指向原函數的prototype屬性(繼承原函數的原型)
  • 將新對象綁定到此函數的this上。
  • 返回新對象
// new 生成對象的過程
// 一、生成新對象
// 二、連接到原型
// 三、綁定 this
// 四、返回新對象
// 參數:
// 一、Con: 接收一個構造函數
// 二、args:傳入構造函數的參數
function create(Con, ...args){
    // 建立空對象
    let obj = {};
    // 設置空對象的原型(連接對象的原型)
    obj._proto_ = Con.prototype;
    // 綁定 this 並執行構造函數(爲對象設置屬性)
    let result = Con.apply(obj,args)
    // 若是 result 沒有其餘選擇的對象,就返回 obj 對象
    return result instanceof Object ?  result : obj;
}
// 構造函數
function Test(name, age) {
    this.name = name
    this.age = age
}
Test.prototype.sayName = function () {
    console.log(this.name)
}

// 實現一個 new 操做符
const a = create(Test,'小鹿','23')
console.log(a.age)
複製代碼

建立對象的方式

經常使用建立對象的方式:

  • new
  • 字面量

其餘建立對象的方式:

  • Object.create()

字面量建立對象的優點:

  • 代碼量少,易讀
  • 運行速度更快,能夠在解析時被優化,不像new同樣,解析器須要順着做用域鏈從當前做用域開始查找,若是在當前做用域找到了名爲Object()的函數就執行,若是沒找到就順着繼續向上找,直到找到全局Object()構造函數爲止。
  • Object()構造函數能夠接受參數,經過這個參數能夠吧對象實例的建立過程委託給另外一個內置構造函數,並返回另一個對象實例,而這每每不是想要的。對於Object.create()方式:Object.create(proto,[propertiesObject])
    • proto:新建立對象的原型對象
    • propertiesObject:可選,可爲建立的新對象設置屬性和值 通常用於繼承:
var People = function (name){
  this.name = name;
};

People.prototype.sayName = function (){
  console.log(this.name);
}

function Person(name, age){
  this.age = age;
  People.call(this, name);  // 使用call,實現了People屬性的繼承
};

// 使用Object.create()方法,實現People原型方法的繼承,而且修改了constructor指向
Person.prototype = Object.create(People.prototype, {
  constructor: {
    configurable: true,
    enumerable: true,
    value: Person,
    writable: true
  }
});

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

var p1 = new Person('person1', 25);
 
p1.sayName();  //'person1'
p1.sayAge();   //25
複製代碼

new/字面量與Object.create(null)建立對象

  • new和字面量建立的對象的原型指向Object.prototype,會繼承Object的屬性和方法
  • Object.create(null)建立的對象,其原型指向nullnull做爲原型鏈的頂端,沒有也不會繼承任何屬性和方法

閉包

做用域

規定變量和函數的可以使用範圍叫作做用域。

每一個函數都會有一個做用域,查找變量或函數時,由局部做用域到全局做用域依次查找,這些做用域的集合就叫作做用域鏈。

閉包

函數執行,造成一個私有的做用域,保護裏面的私有變量不受外界干擾,除了保護私有變量外,還能夠保存一些內容,這種模式叫作閉包。

內存回收機制

再也不用到的內存,系統就回收。

內部函數引用着外部的函數的變量,外部函數儘管執行完畢,做用域也不會銷燬。從而造成了一種不銷燬的私有做用域。

做用域繼承

內部函數能夠訪問外部函數做用域,外部函數不能獲取內部函數的做用於變量。

通俗的閉包

一個函數裏邊再定義一個函數,內部函數一直保持有對外部函數做用域的訪問權限。

兩個做用:保護和保存。

保護的應用
  • 團隊開發,把本身的代碼放在私有做用域中,防止變量命名衝突;把須要提供給別人的方法,經過return或者window.xxx暴露在全局。
  • 封裝私有變量
  • jQuery就使用了保護機制
保存的應用
  • 選項卡閉包

循環綁定事件

// 事件綁定引起的索引問題
var btnBox = document.getElementById('btnBox'),
    inputs = btnBox.getElementsByTagName('input')
var len = inputs.length;
for(var i = 0; i < len; i++){
    inputs[i].onclick = function () {
        alert(i)
    }
}
複製代碼

運行程序得出的結果都是len的數值。

由於全部事件綁定都是異步的,當觸發點擊事件,執行方法的時候,循環早就結束了。

同步:JS中當前這個任務沒完成,下面的任務都不會執行,只有等當前完全完成,纔會執行下面的任務。 異步:JS當前任務沒有完成,須要等一會再完成,但此時咱們能夠繼續執行下面的任務。

解決方案:

當點擊事件執行的時候,就會在私有做用域查找i的值,此時私有做用域沒有i,就會去全局做用域查找,此時全局做用於的i已經被改變了,因此要建立一個私有做用域的i。

for(var i = 0;i<length;i++){
    ~function(i){
        inputs[i].onclick = function(){
            alert(this.myIndex)
        }
    }(i)
}
複製代碼

閉包既有優勢也有缺點。

優勢是經過閉包解決循環幾回就建立幾個私有做用域,而後每一個私有做用域都有一個私有變量i。

缺點就是生成多個不銷燬的私有做用域,對性能有影響。

原型和原型鏈

原型:每一個JS對象都有__proto__屬性,這個屬性指向了原型。

原型鏈:多個對象經過__proto__的方式鏈接起來。

instanceof的原理

經過判斷該對象的原型鏈中是否能夠找到該構造類型的prototype類型

繼承

經典繼承(構造函數)

//一、當用調用 call 方法時,this 帶邊 son 。
//二、此時 Father 構造函數中的 this 指向 son。
//三、也就是說 son 有了 colors 的屬性。
//四、每 new 一個 son ,都會產生不一樣的對象,每一個對象的屬性都是相互獨立的。
function Father(){
	this.colors = ["red","blue","green"];
}

function Son(){
    // this 是經過 new 操做內部的新對象 {} ,
    // 此時 Father 中的 this 就是爲 Son 中的新對象{}
    // 新對象就有了新的屬性,並返回獲得 new 的新對象實例
    // 繼承了Father,且向父類型傳遞參數
	Father.call(this);
}

let s = new Son();
console.log(s.color)
複製代碼

基本思想:在子類的構造函數的內部調用父類的構造函數。 優勢:

  • 保證了原型鏈中引用類型的獨立,不被全部實例共享。
  • 子類建立的時候能夠向父類傳參。

缺點:

  • 繼承的方法都在構造函數中定義,構造函數不能複用。
  • 父類中的方法對子類而言是不可見的,子類全部屬性都定義在父類的構造函數中。

組合繼承

function Father(name){
	this.name = name;
	this.colors = ["red","blue","green"];
}

// 方法定義在原型對象上(共享)
Father.prototype.sayName = function(){
	alert(this.name);
};

function Son(name,age){
    // 子類繼承父類的屬性 
	Father.call(this,name);     //繼承實例屬性,第一次調用 Father()
    // 每一個實例都有本身的屬性
	this.age = age;
}

// 子類和父類共享的方法(實現了父類屬性和方法的複用) 
Son.prototype = new Father();   //繼承父類方法,第二次調用 Father()

// 子類實例對象共享的方法
Son.prototype.sayAge = function(){
	alert(this.age);
}

var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5

var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10
複製代碼

基本思想:

  • 使用原型鏈實現對原型對象屬性和方法的繼承
  • 借用構造函數來實現對實例屬性的繼承

優勢:

  • 在原型兌現上定義的方法實現了函數的複用。
  • 每一個實例都有屬於本身的屬性。

缺點:組合繼承調用了兩次父類的構造函數,形成了沒必要要的消耗。

原型繼承

function object(o){
	function F(){}
	F.prototype = o;
    // 每次返回的 new 是不一樣的
	return new F();
}

var person = {
	friends : ["Van","Louis","Nick"]
};

// 實例 1
var anotherPerson = object(person);
anotherPerson.friends.push("Rob");

// 實例 2
var yetAnotherPerson = object(person);
yetAnotherPerson.friends.push("Style");

// 都添加至原型對象的屬性(所共享)
alert(person.friends); // "Van,Louis,Nick,Rob,Style"
複製代碼

基本思想:建立臨時的構造函數,將傳入的對象做爲該構造函數的原型對象,而後返回新構造函數的實例。

淺拷貝:object產生的對象是不相同的,可是原型對象都是person對象,所改變存在原型對象的屬性被全部生成的實例所共享,不只Person擁有,並且子類生成的實例也共享。

Object.create():在ECMAScript5中新增了此方法。

  • 參數一:新對象的原型對象
  • 參數二:新對象定義的額外的屬性。(可選)

寄生式繼承

function createAnother(original){
	var clone = object(original); // 經過調用object函數建立一個新對象
	clone.sayHi = function(){ // 以某種方式來加強這個對象
		alert("hi");
	};
	return clone; //返回這個對象
}
複製代碼

基本思想:沒必要爲了指定子類的原型而調用父類的構造函數。

優勢:解決組合繼承中兩次調用構造函數的開銷。

垃圾回收機制

內存泄漏

再也不用到的內存,沒有及時釋放,就是內存泄漏。

之因此會有垃圾回收機制,是由於js中字符串、對象、數組等只有肯定固定大小時,纔會動態分配內存,而使用完必須釋放,不然會消耗完全部內存。

js與其餘語言不一樣,具備自動垃圾收集機制。

兩種垃圾回收策略

找出那些再也不使用的變量,而後釋放內存,垃圾回收器會按照固定的時間間隔,週期性的執行垃圾回收。

標記清除法

垃圾回收器在運行的時候,會給存儲在內存中的全部變量都加上標記,而後會去掉環境中變量以及被環境中的變量引用的變量的標記。剩下的就被視爲要刪除的變量。

其實現原理就是經過判斷一個變量是否在執行環境中被引用,來進行標記刪除。

引用計數法

跟蹤記錄每一個值被引用的次數。

當聲明變量並將一個引用類型的值賦值給該變量時,則這個值引用次數加1,同一值被賦予另外一個變量,該值引用次數加1。當引用該值的變量被另外一個值取代,則引用計數減1,當計數爲0時,就沒法訪問了,就會收回。

缺陷:兩個對象的相互循環引用時,在函數執行完成後,兩個對象相互引用計數並未歸零,全部沒法回收。

常見的就是在IE BOM和DOM中,使用的對象並非js對象,因此垃圾回收是基於計數策略。

如何管理內存

雖然js內存都是自動管理,但仍是有問題,好比分配給web瀏覽器的可用內存數量一般比分配給桌面應用的少。

爲了能讓頁面得到最好的性能,必須確保js變量佔用最少的內存,因此最好將不用的變量引用釋放,也叫解除引用。

  • 對於局部變量,函數執行完成離開環境變量,變量將自動解除。
  • 對於全局變量,須要手動解除。此時只是解除引用,下一次垃圾回收將其回收。
var a = 20;
alert(a + 100);
var a = null;
複製代碼

只有與環境變量失去引用的變量纔會被標記回收,將對象引用設置爲null,就失去引用,等待被回收。

深拷貝和淺拷貝

對基本類型的拷貝就是對值進行拷貝,而對於引用類型來講,拷貝的不是值,而是值的地址,最後兩個變量的地址指向的是同一個值。

var a = 10;
var b = a;
b = 30;
console.log(a); // 10值
console.log(b); // 30值

var obj1 = new Object();
var obj2 = obj1;
obj2.name = "小鹿";
console.log(obj1.name); // 小鹿
複製代碼

要想將obj1,obj2的關係斷開,不讓其指向同一個地址,分爲淺拷貝和深拷貝。

  • 淺拷貝:只進行一層關係的拷貝。
  • 深拷貝:進行無限層的拷貝。

本身實現一個淺拷貝:

function shallowClone(o){
    const obj = {};
    for(let i in o){
        obj[i] = o[i]
    }
    return obj;
}
複製代碼
  • 擴展運算符實現:
let a = {c: 1}
let b = {...a}
a.c = 2
console.log(b.c) // 1
複製代碼
  • Object.assign()實現
let a = {c: 1}
let b = Object.assign({}, a)
a.c = 2
console.log(b.c) // 1
複製代碼

深拷貝須要在淺拷貝的基礎上加上遞歸

比較簡單的實現方法:利用JSON.parse(JSON.stringify(obj))

function clonebyJSON(source){
    return JSON.parse(JSON.stringify(source))
}
複製代碼

但他內部也是使用的遞歸,遞歸到必定深度會爆棧,但不會出現循環引用問題。

異步編程

因爲JavaScript是單線程,因此會有阻塞問題,當一個任務執行完成後才能執行下一個任務,這樣就會出現頁面卡死。

單線程是由一些與用戶的互動以及操做DOM相關的操做決定了JS要使用單線程,不然會帶來複雜的同步問題。須要加鎖。

H5標準規定容許js建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。

實現異步編程

最先是使用回調函數,回調函數不是直接調用,而是在特定的事件或條件發生時另外一方調用,用於對該事件或條件進行響應。

好比Ajax回調:

$.ajax({
    type:'post',
    url:'test.json',
    dataType:'json',
    success:function(res){
        //成功回調
    },
    fail:function(err){
        //響應失敗回調
    }
})
複製代碼

可是若是某個請求存在依賴性:

$.ajax({
    type:'post',
    url:'test.json',
    dataType:'json',
    success:function(res){
        $.ajax({
            type:'post',
            url:'xxx?id='+res.id,
            success:function(res){
                $.ajax({
                    ...//循環
                })
            }
        })
    },
    fail:function(err){
        //響應失敗回調
    }
})
複製代碼

這樣就會不斷循環嵌套,稱爲回調地獄。

缺點:

  • 嵌套函數存在耦合性,一旦有改動就都得改。
  • 嵌套函數多,很難處理錯誤。
  • 回調函數不能使用try catch捕獲異常(異常捕獲只能在函數執行的時候才能捕獲到)
  • 回調函數不能直接return

爲何不能捕獲異常?

這和js運行機制相關,異步任務執行完成會加入任務隊列,當執行棧中沒有可執行任務了,主線程去除任務隊列中的異步任務併入棧執行,當異步任務執行時,捕獲異常的函數已經在執行棧內退出了,因此異常沒法捕獲。

爲何不能return

return只能終止回調函數的執行,而不能終止外部代碼的執行。

解決回調地獄

ES6給了三種解決方案:Generator、Promise、async/await

EventLoop事件循環機制

執行上下文

能夠理解爲代碼執行的環境。

JS執行上下文分爲三種:

  • 全局執行上下文:全局this指向的window,能夠是外部加載的js文件或者本地script標籤中的代碼
  • 函數執行上下文:局部上下文,每一個函數被調用的時候,都會建立一個局部上下文
  • Eval執行上下文
執行棧

代碼執行的時候,遇到一個執行上下文就將其依次壓入執行棧。

當代碼執行時,先執行位於棧頂的執行上下文中的代碼,當棧頂的執行上下文代碼執行完畢後就出棧,而後執行下一個位於棧頂的執行上下文。

宏任務

通常包括:

  • 總體script標籤內的代碼
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
微任務

通常包括:

  • Promise
  • process.nextTick(Node)
  • MutationObserver

nextTick隊列會比Promise先執行

事件循環運行機制
  1. 事件循環機制是從script標籤內的代碼開始,做爲一個宏任務處理。
  2. 在代碼執行過程當中,若是遇到宏任務,好比setTimeout,就將當前任務分發到對應的執行隊列中去。
  3. 遇到微任務,如Promise,在建立Promise實例對象時,代碼順序執行,若是到了執行then操做,該任務就被分發到微任務隊列中去。
  4. script標籤內的代碼執行完畢,執行過程當中所涉及的宏任務和微任務也都被分配到相應的隊列中去。
  5. 宏任務執行完畢,去微任務隊列執行全部存在的微任務。
  6. 微任務執行完畢,第一輪消息循環執行完畢,頁面渲染一次。
  7. 開始第二輪消息循環,從宏任務隊列中取出任務執行。
  8. 若是兩個任務隊列沒有任務執行了,全部任務執行完畢。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>消息運行機制</title>
</head>
<body>

</body>
    <script>
        console.log('1');
        setTimeout(() => {
            console.log('2')
        }, 1000);
        new Promise((resolve, reject) => {
            console.log('3');
            resolve();
            console.log('4');
        }).then(() => {
            console.log('5');
        });
        console.log('6');// 1,3,4,6,5,2
    </script>
</html>
複製代碼
  • 首先script代碼宏任務
  • 輸出1
  • 遇到setTimeout 加入宏任務隊伍
  • 遇到Promise,構造時是同步的,輸出3,4,到then加入微任務
  • 繼續 輸出6
  • script執行完,開始執行微任務隊列
  • 只有一個 輸出5
  • 微任務執行完頁面渲染依次
  • 下一次循環開始,從宏任務開始,setTimeout 輸出2
  • 全爲空
相關文章
相關標籤/搜索