2023最新web前端面試題大全供跳槽使用(持續(xù)更新中)

考察優(yōu)先級: 最高

let条辟、const精盅、var的區(qū)別[1]

ES6篇:

1.let、const奇适、var的區(qū)別

(1)塊級作用域:塊作用域由 { }包括坟比,let和const具有塊級作用域,var不存在塊級作用域嚷往。塊級作用域解決了ES5中的兩個問題:

  • 內層變量可能覆蓋外層變量
  • 用來計數(shù)的循環(huán)變量泄露為全局變量

(2)變量提升:var存在變量提升葛账,let和const不存在變量提升,即在變量只能在聲明之后使用皮仁,否則會報錯籍琳。

(3)給全局添加屬性:瀏覽器的全局對象是window,Node的全局對象是global贷祈。var聲明的變量為全局變量趋急,并且會將該變量添加為全局對象的屬性,但是let和const不會势誊。

(4)重復聲明:var聲明變量時呜达,可以重復聲明變量,后聲明的同名變量會覆蓋之前聲明的遍歷键科。const和let不允許重復聲明變量闻丑。

(5)暫時性死區(qū):在使用let、const命令聲明變量之前勋颖,該變量都是不可用的嗦嗡。這在語法上,稱為暫時性死區(qū)饭玲。使用var聲明的變量不存在暫時性死區(qū)侥祭。

(6)初始值設置:在變量聲明時,var 和 let 可以不用設置初始值茄厘。而const聲明變量必須設置初始值矮冬。

(7)指針指向:let和const都是ES6新增的用于創(chuàng)建變量的語法。 let創(chuàng)建的變量是可以更改指針指向(可以重新賦值)次哈。但const聲明的變量是不允許改變指針的指向胎署。

區(qū)別 var let const
是否有塊級作用域 × ?? ??
是否存在變量提升 ?? × ×
是否添加全局屬性 ?? × ×
能否重復聲明變量 ?? × ×
是否存在暫時性死區(qū) × ?? ??
是否必須設置初始值 × × ??
能否改變指針指向 ?? ?? ×

2.箭頭函數(shù)與普通函數(shù)的區(qū)別

(1)箭頭函數(shù)比普通函數(shù)更加簡潔

  • 如果沒有參數(shù),就直接寫一個空括號即可
  • 如果只有一個參數(shù)窑滞,可以省去參數(shù)的括號
  • 如果有多個參數(shù)琼牧,用逗號分割
  • 如果函數(shù)體的返回值只有一句恢筝,可以省略大括號
  • 如果函數(shù)體不需要返回值,且只有一句話巨坊,可以給這個語句前面加一個void關鍵字撬槽。最常見的就是調用一個函數(shù):
let fn = () => void doesNotReturn();

(2)箭頭函數(shù)沒有自己的this

箭頭函數(shù)不會創(chuàng)建自己的this, 所以它沒有自己的this趾撵,它只會在自己作用域的上一層繼承this侄柔。所以箭頭函數(shù)中this的指向在它在定義時已經(jīng)確定了,之后不會改變占调。

(3)箭頭函數(shù)繼承來的this指向永遠不會改變

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor

對象obj的方法b是使用箭頭函數(shù)定義的暂题,這個函數(shù)中的this就永遠指向它定義時所處的全局執(zhí)行環(huán)境中的this,即便這個函數(shù)是作為對象obj的方法調用妈候,this依舊指向Window對象敢靡。需要注意,定義對象的大括號{}是無法形成一個單獨的執(zhí)行環(huán)境的苦银,它依舊是處于全局執(zhí)行環(huán)境中啸胧。

(4)call()、apply()幔虏、bind()等方法不能改變箭頭函數(shù)中this的指向

var id = 'Global';
let fun1 = () => {
    console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'

(5)箭頭函數(shù)不能作為構造函數(shù)使用

構造函數(shù)在new的步驟在上面已經(jīng)說過了纺念,實際上第二步就是將函數(shù)中的this指向該對象。 但是由于箭頭函數(shù)時沒有自己的this的想括,且this指向外層的執(zhí)行環(huán)境陷谱,且不能改變指向,所以不能當做構造函數(shù)使用瑟蜈。

(6)箭頭函數(shù)沒有自己的arguments

箭頭函數(shù)沒有自己的arguments對象烟逊。在箭頭函數(shù)中訪問arguments實際上獲得的是它外層函數(shù)的arguments值。

(7)箭頭函數(shù)沒有prototype

(8)箭頭函數(shù)不能用作Generator函數(shù)铺根,不能使用yeild關鍵字

原型篇:

1. 對原型宪躯、原型鏈的理解

在JavaScript中是使用構造函數(shù)來新建一個對象的,每一個構造函數(shù)的內部都有一個 prototype 屬性位迂,它的屬性值是一個對象访雪,這個對象包含了可以由該構造函數(shù)的所有實例共享的屬性和方法。當使用構造函數(shù)新建一個對象后掂林,在這個對象的內部將包含一個指針臣缀,這個指針指向構造函數(shù)的 prototype 屬性對應的值,在 ES5 中這個指針被稱為對象的原型泻帮。一般來說不應該能夠獲取到這個值的精置,但是現(xiàn)在瀏覽器中都實現(xiàn)了 __proto__ 屬性來訪問這個屬性,但是最好不要使用這個屬性锣杂,因為它不是規(guī)范中規(guī)定的氯窍。ES5 中新增了一個 Object.getPrototypeOf() 方法饲常,可以通過這個方法來獲取對象的原型。

當訪問一個對象的屬性時狼讨,如果這個對象內部不存在這個屬性,那么它就會去它的原型對象里找這個屬性柒竞,這個原型對象又會有自己的原型政供,于是就這樣一直找下去,也就是原型鏈的概念朽基。原型鏈的盡頭一般來說都是 Object.prototype 所以這就是新建的對象為什么能夠使用 toString() 等方法的原因布隔。

特點:JavaScript 對象是通過引用來傳遞的,創(chuàng)建的每個新對象實體中并沒有一份屬于自己的原型副本稼虎。當修改原型時衅檀,與之相關的對象也會繼承這一改變。

1615475711487-c474af95-b5e0-4778-a90b-9484208d724d.png

2. 原型修改霎俩、重寫

function Person(name) {
    this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重寫原型
Person.prototype = {
    getName: function() {}
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // false

可以看到修改原型的時候p的構造函數(shù)不是指向Person了哀军,因為直接給Person的原型對象直接用對象賦值時,它的構造函數(shù)指向了根構造函數(shù)Object打却,所以這時候p.constructor === Object 杉适,而不是p.constructor === Person。要想成立柳击,就要用constructor指回來:

Person.prototype = {
    getName: function() {}
}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // true

3. 原型鏈指向

p.__proto__  // Person.prototype
Person.prototype.__proto__  // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor  // Person
p.__proto__  // Person.prototype
Person.prototype.__proto__  // Object.prototype
p.__proto__.__proto__ //Object.prototype
p.__proto__.constructor.prototype.__proto__ // Object.prototype
Person.prototype.constructor.prototype.__proto__ // Object.prototype
p1.__proto__.constructor // Person
Person.prototype.constructor  // Person

4. 原型鏈的終點是什么猿推?如何打印出原型鏈的終點?

由于Object是構造函數(shù)捌肴,原型鏈終點是Object.prototype.__proto__蹬叭,而Object.prototype.__proto__=== null // true,所以状知,原型鏈的終點是null秽五。原型鏈上的所有原型都是對象,所有的對象最終都是由Object構造的试幽,而Object.prototype的下一級是Object.prototype.__proto__筝蚕。

1605247722640-5bcb9156-a8b4-4d7c-83d7-9ff80930e1de.jpeg

5. 如何獲得對象非原型鏈上的屬性?

使用hasOwnProperty()方法來判斷屬性是否屬于原型鏈的屬性:

function iterate(obj){
   var res=[];
   for(var key in obj){
        if(obj.hasOwnProperty(key))
           res.push(key+': '+obj[key]);
   }
   return res;
} 

構造函數(shù)的理解

Js構造函數(shù)

let a = new(function Test(name){
    this.name = name
})('張三1')
console.log("a",a.name);

異步編程篇:

1.對promise的理解:

Promise是異步編程的一種解決方案铺坞,它是一個對象起宽,可以獲取異步操作的消息,他的出現(xiàn)大大改善了異步編程的困境济榨,避免了地獄回調坯沪,它比傳統(tǒng)的解決方案回調函數(shù)和事件更合理和更強大。

所謂Promise擒滑,簡單說就是一個容器腐晾,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果叉弦。從語法上說,Promise 是一個對象藻糖,從它可以獲取異步操作的消息淹冰。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理巨柒。

(1)Promise的實例有三個狀態(tài):

  • Pending(進行中)
  • Resolved(已完成)
  • Rejected(已拒絕)

當把一件事情交給promise時樱拴,它的狀態(tài)就是Pending,任務完成了狀態(tài)就變成了Resolved洋满、沒有完成失敗了就變成了Rejected晶乔。

(2)Promise的實例有兩個過程

  • pending -> fulfilled : Resolved(已完成)
  • pending -> rejected:Rejected(已拒絕)

注意:一旦從進行狀態(tài)變成為其他狀態(tài)就永遠不能更改狀態(tài)了。

Promise的特點:

  • 對象的狀態(tài)不受外界影響牺勾。promise對象代表一個異步操作正罢,有三種狀態(tài),pending(進行中)驻民、fulfilled(已成功)翻具、rejected(已失敗)川无。只有異步操作的結果呛占,可以決定當前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)懦趋,這也是promise這個名字的由來——“承諾”晾虑;
  • 一旦狀態(tài)改變就不會再變,任何時候都可以得到這個結果仅叫。promise對象的狀態(tài)改變帜篇,只有兩種可能:從pending變?yōu)?code>fulfilled,從pending變?yōu)?code>rejected诫咱。這時就稱為resolved(已定型)笙隙。如果改變已經(jīng)發(fā)生了,你再對promise對象添加回調函數(shù)坎缭,也會立即得到這個結果竟痰。這與事件(event)完全不同,事件的特點是:如果你錯過了它掏呼,再去監(jiān)聽是得不到結果的坏快。

Promise的缺點:

  • 無法取消Promise,一旦新建它就會立即執(zhí)行憎夷,無法中途取消莽鸿。
  • 如果不設置回調函數(shù),Promise內部拋出的錯誤,不會反應到外部祥得。
  • 當處于pending狀態(tài)時兔沃,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

總結:

Promise 對象是異步編程的一種解決方案级及,最早由社區(qū)提出乒疏。Promise 是一個構造函數(shù),接收一個函數(shù)作為參數(shù)创千,返回一個 Promise 實例缰雇。一個 Promise 實例有三種狀態(tài),分別是pending追驴、resolved 和 rejected,分別代表了進行中疏之、已成功和已失敗殿雪。實例的狀態(tài)只能由 pending 轉變 resolved 或者rejected 狀態(tài),并且狀態(tài)一經(jīng)改變锋爪,就凝固了丙曙,無法再被改變了。

狀態(tài)的改變是通過 resolve() 和 reject() 函數(shù)來實現(xiàn)的其骄,可以在異步操作結束后調用這兩個函數(shù)改變 Promise 實例的狀態(tài)亏镰,它的原型上定義了一個 then 方法,使用這個 then 方法可以為兩個狀態(tài)的改變注冊回調函數(shù)拯爽。這個回調函數(shù)屬于微任務索抓,會在本輪事件循環(huán)的末尾執(zhí)行。

注意:在構造 Promise 的時候毯炮,構造函數(shù)內部的代碼是立即執(zhí)行的

2.promise的基本用法:

(1)創(chuàng)建Promise對象

Promise對象代表一個異步操作逼肯,有三種狀態(tài):pending(進行中)、fulfilled(已成功)和rejected(已失斕壹濉)篮幢。

Promise構造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是resolvereject为迈。

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

一般情況下都會使用new Promise()來創(chuàng)建promise對象三椿,但是也可以使用promise.resolve promise.reject這兩個方法:

  • Promise.resolve

Promise.resolve(value)的返回值也是一個promise對象,可以對返回值進行.then調用葫辐,代碼如下:

Promise.resolve(11).then(function(value){
  console.log(value); // 打印出11
});

resolve(11)代碼中搜锰,會讓promise對象進入確定(resolve狀態(tài)),并將參數(shù)11傳遞給后面的then所指定的onFulfilled 函數(shù)另患;

創(chuàng)建promise對象可以使用new Promise的形式創(chuàng)建對象纽乱,也可以使用Promise.resolve(value)的形式創(chuàng)建promise對象;

  • Promise.reject

Promise.reject 也是new Promise的快捷形式昆箕,也創(chuàng)建一個promise對象鸦列。代碼如下:

Promise.reject(new Error(“我錯了租冠,請原諒俺!薯嗤!”));

就是下面的代碼new Promise的簡單形式:

new Promise(function(resolve,reject){
   reject(new Error("我錯了顽爹,請原諒俺!骆姐!"));
});

下面是使用resolve方法和reject方法:

function testPromise(ready) {
  return new Promise(function(resolve,reject){
    if(ready) {
      resolve("hello world");
    }else {
      reject("No thanks");
    }
  });
};
// 方法調用
testPromise(true).then(function(msg){
  console.log(msg);
},function(error){
  console.log(error);
});

上面的代碼的含義是給testPromise方法傳遞一個參數(shù)镜粤,返回一個promise對象,如果為true的話玻褪,那么調用promise對象中的resolve()方法肉渴,并且把其中的參數(shù)傳遞給后面的then第一個函數(shù)內,因此打印出 “hello world”, 如果為false的話带射,會調用promise對象中的reject()方法同规,則會進入then的第二個函數(shù)內,會打印No thanks窟社;

(2)Promise方法

Promise有五個常用的方法:then()券勺、catch()阳柔、all()欠肾、race()、finally贸桶。下面就來看一下這些方法匣吊。

  1. then()

當Promise執(zhí)行的內容符合成功條件時儒拂,調用resolve函數(shù),失敗就調用reject函數(shù)缀去。Promise創(chuàng)建完了侣灶,那該如何調用呢?

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受兩個回調函數(shù)作為參數(shù)缕碎。第一個回調函數(shù)是Promise對象的狀態(tài)變?yōu)?code>resolved時調用褥影,第二個回調函數(shù)是Promise對象的狀態(tài)變?yōu)?code>rejected時調用。其中第二個參數(shù)可以省略咏雌。

then方法返回的是一個新的Promise實例(不是原來那個Promise實例)凡怎。因此可以采用鏈式寫法,即then方法后面再調用另一個then方法赊抖。

當要寫有順序的異步事件時统倒,需要串行時,可以這樣寫:

let promise = new Promise((resolve,reject)=>{
    ajax('first').success(function(res){
        resolve(res);
    })
})
promise.then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{
    return new Promise((resovle,reject)=>{
        ajax('second').success(function(res){
            resolve(res)
        })
    })
}).then(res=>{
    
})

那當要寫的事件沒有順序或者關系時氛雪,還如何寫呢房匆?可以使用all 方法來解決。

2. catch()

Promise對象除了有then方法,還有一個catch方法浴鸿,該方法相當于then方法的第二個參數(shù)井氢,指向reject的回調函數(shù)。不過catch方法還有一個作用岳链,就是在執(zhí)行resolve回調函數(shù)時花竞,如果出現(xiàn)錯誤,拋出異常掸哑,不會停止運行约急,而是進入catch方法中。

p.then((data) => {
     console.log('resolved',data);
},(err) => {
     console.log('rejected',err);
     }
); 
p.then((data) => {
    console.log('resolved',data);
}).catch((err) => {
    console.log('rejected',err);
});

3. all()

all方法可以完成并行任務苗分, 它接收一個數(shù)組厌蔽,數(shù)組的每一項都是一個promise對象。當數(shù)組中所有的promise的狀態(tài)都達到resolved的時候摔癣,all方法的狀態(tài)就會變成resolved躺枕,如果有一個狀態(tài)變成了rejected,那么all方法的狀態(tài)就會變成rejected供填。

javascript
let promise1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(3);
    },3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{
    console.log(res);
    //結果為:[1,2,3] 
})

調用all方法時的結果成功的時候是回調函數(shù)的參數(shù)也是一個數(shù)組,這個數(shù)組按順序保存著每一個promise對象resolve執(zhí)行時的值罢猪。

(4)race()

race方法和all一樣近她,接受的參數(shù)是一個每項都是promise的數(shù)組,但是與all不同的是膳帕,當最先執(zhí)行完的事件執(zhí)行完之后粘捎,就直接返回該promise對象的值。如果第一個promise對象狀態(tài)變成resolved危彩,那自身的狀態(tài)變成了resolved攒磨;反之第一個promise變成rejected,那自身狀態(tài)就會變成rejected汤徽。

let promise1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       reject(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
       resolve(3);
    },3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{
    console.log(res);
    //結果:2
},rej=>{
    console.log(rej)};
)

那么race方法有什么實際作用呢娩缰?當要做一件事,超過多長時間就不做了谒府,可以用這個方法來解決:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally方法用于指定不管 Promise 對象最后狀態(tài)如何拼坎,都會執(zhí)行的操作。該方法是 ES2018 引入標準的完疫。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代碼中泰鸡,不管promise最后的狀態(tài),在執(zhí)行完thencatch指定的回調函數(shù)以后壳鹤,都會執(zhí)行finally方法指定的回調函數(shù)盛龄。

下面是一個例子,服務器使用 Promise 處理請求,然后使用finally方法關掉服務器余舶。

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

finally方法的回調函數(shù)不接受任何參數(shù)啊鸭,這意味著沒有辦法知道,前面的 Promise 狀態(tài)到底是fulfilled還是rejected欧芽。這表明莉掂,finally方法里面的操作,應該是與狀態(tài)無關的千扔,不依賴于 Promise 的執(zhí)行結果憎妙。finally本質上是then方法的特例:

promise
.finally(() => {
  // 語句
});
// 等同于
promise
.then(
  result => {
    // 語句
    return result;
  },
  error => {
    // 語句
    throw error;
  }
);

上面代碼中,如果不使用finally方法曲楚,同樣的語句需要為成功和失敗兩種情況各寫一次厘唾。有了finally方法,則只需要寫一次龙誊。

3.對async/await的理解

chatgpt解釋:

async/await是一種用于簡化異步操作的語法糖抚垃。通過async關鍵字聲明的函數(shù)可以使用await關鍵字來等待一個異步操作的完成,并且在異步操作完成后繼續(xù)執(zhí)行剩余的代碼趟大。

async函數(shù)在執(zhí)行時總是返回一個Promise對象鹤树,可以通過使用await關鍵字,等待一個異步操作的結果逊朽。當遇到這個await表達式時罕伯,async函數(shù)會暫停當前代碼的執(zhí)行,直到等待的異步操作完成并返回結果后才會繼續(xù)執(zhí)行叽讳。

使用async/await的優(yōu)勢在于它提供了更加直觀追他、簡潔的方式來處理異步操作,避免了傳統(tǒng)的回調函數(shù)嵌套岛蚤,使異步代碼的編寫和維護更加清晰易懂邑狸。同時,使用try/catch結構可以方便地捕獲和處理異常涤妒。

需要注意的是单雾,在使用async/await時,異步操作必須返回一個Promise對象或者是具有then方法的對象届腐,以便能夠被await等待铁坎。此外,async/await只是一種語法糖犁苏,本質上還是基于Promise來實現(xiàn)的

—————————————————— 分割

async/await其實是Generator 的語法糖硬萍,它能實現(xiàn)的效果都能用then鏈來實現(xiàn),它是為優(yōu)化then鏈而開發(fā)出來的围详。從字面上來看朴乖,async是“異步”的簡寫祖屏,await則為等待,所以很好理解async 用于申明一個 function 是異步的买羞,而 await 用于等待一個異步方法執(zhí)行完成袁勺。當然語法上強制規(guī)定await只能出現(xiàn)在asnyc函數(shù)中,先來看看async函數(shù)返回了什么:

async function testAsy(){
   return 'hello world';
}
let result = testAsy(); 
console.log(result)
1605099411873-d2eac25a-5d8c-4586-bc36-769bce79010e.png

所以畜普,async 函數(shù)返回的是一個 Promise 對象期丰。async 函數(shù)(包含函數(shù)語句、函數(shù)表達式吃挑、Lambda表達式)會返回一個 Promise 對象钝荡,如果在函數(shù)中 return 一個直接量,async 會把這個直接量通過 Promise.resolve() 封裝成 Promise 對象舶衬。

async 函數(shù)返回的是一個 Promise 對象埠通,所以在最外層不能用 await 獲取其返回值的情況下,當然應該用原來的方式:then() 鏈來處理這個 Promise 對象逛犹,就像這樣:

async function testAsy(){
   return 'hello world'
}
let result = testAsy() 
console.log(result)
result.then(v=>{
    console.log(v)   // hello world
})

那如果 async 函數(shù)沒有返回值端辱,又該如何?很容易想到虽画,它會返回 Promise.resolve(undefined)舞蔽。

聯(lián)想一下 Promise 的特點——無等待,所以在沒有 await 的情況下執(zhí)行 async 函數(shù)码撰,它會立即執(zhí)行喷鸽,返回一個 Promise 對象,并且灸拍,絕不會阻塞后面的語句。這和普通返回 Promise 對象的函數(shù)并無二致砾省。

注意:Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的簡寫鸡岗,可以用于快速封裝字面量對象或其他對象,將其封裝成 Promise 實例编兄。

4.async/await對比promise的優(yōu)勢

chatgpt回答:
async/await 和 Promise 是 JavaScript 中用于處理異步操作的兩種方法轩性,它們有各自的優(yōu)勢。

可讀性高:async/await 可以使異步代碼的編寫更加清晰易懂狠鸳,避免了Promise 鏈式調用的嵌套和冗長揣苏,使代碼更具可讀性和維護性。

異常處理方便:使用 async/await 可以使用 try/catch 結構方便地捕獲和處理異常件舵。而使用 Promise卸察,則需要通過 .catch() 方法來處理異常,可能會導致回調地獄铅祸。

錯誤堆棧追蹤:使用異步函數(shù)時坑质,錯誤堆棧追蹤更加清晰明了合武。如果使用 Promise,錯誤堆棧追蹤通常不會指向到異步函數(shù)中的具體位置涡扼,而是指向到 Promise 的回調鏈中稼跳。

更直觀的代碼流程控制:使用 async/await 可以編寫類似同步代碼的流程,并且能夠在異步操作中使用傳統(tǒng)的控制流程結構(如條件語句吃沪、循環(huán)語句)來實現(xiàn)更加靈活的邏輯控制汤善。

然而,在某些場景下票彪,Promise 仍然是非常有用的:

并行執(zhí)行:當有多個獨立的異步任務需要同時執(zhí)行時红淡,Promise 可以很方便地使用 Promise.all() 或 Promise.race() 方法來并行執(zhí)行這些任務。

更細粒度的控制:Promise 提供了更強大的異步處理能力抹镊,可以更細粒度地控制異步操作的狀態(tài)锉屈、進度等。

總的來說垮耳,async/await 讓異步代碼的編寫更加簡潔颈渊、易讀,特別適合在編寫順序執(zhí)行的異步流程中使用终佛。而 Promise 則更適合處理并行執(zhí)行俊嗽、細粒度控制等異步操作。在實際開發(fā)中铃彰,可以根據(jù)需求選擇合適的方法來處理異步操作绍豁。
————————————————————分割

  • 代碼讀起來更加同步,Promise雖然擺脫了回調地獄牙捉,但是then的鏈式調?也會帶來額外的閱讀負擔
  • Promise傳遞中間值?常麻煩竹揍,?async/await?乎是同步的寫法,?常優(yōu)雅
  • 錯誤處理友好邪铲,async/await可以?成熟的try/catch芬位,Promise的錯誤捕獲?常冗余
  • 調試友好,Promise的調試很差带到,由于沒有代碼塊昧碉,你不能在?個返回表達式的箭頭函數(shù)中設置斷點,如果你在?個.then代碼塊中使?調試器的步進(step-over)功能揽惹,調試器并不會進?后續(xù)的.then代碼塊被饿,因為調試器只能跟蹤同步代碼的每?步。

執(zhí)行上下文/作用域鏈/閉包篇:

1.對閉包的理解

閉包是指一個函數(shù)及其相關的引用環(huán)境搪搏,包含了函數(shù)自身的定義以及在定義時所處的詞法環(huán)境狭握,這個引用環(huán)境使得函數(shù)能夠訪問其外部作用域中的變量。

具體來說疯溺,閉包由兩部分組成:

函數(shù):閉包是由一個函數(shù)創(chuàng)建的哥牍,它可以訪問自己定義時所在的詞法作用域以及全局作用域毕泌。

引用環(huán)境:閉包包含了函數(shù)定義時所在的詞法環(huán)境,其中包括函數(shù)外部作用域中的變量和參數(shù)嗅辣。這些變量和參數(shù)可以在函數(shù)執(zhí)行時被訪問和使用撼泛,即使在函數(shù)定義的位置已經(jīng)離開。

閉包的核心特點是函數(shù)能夠訪問其定義時所在的詞法環(huán)境中的變量澡谭,即使這些變量在函數(shù)執(zhí)行時已經(jīng)不可見愿题。這使得函數(shù)可以記住并引用外部變量,可以延長對這些變量的訪問和使用蛙奖。

閉包在 JavaScript 中具有廣泛的應用潘酗,例如:

保存私有狀態(tài):閉包可以用來創(chuàng)建具有私有狀態(tài)的函數(shù),通過在閉包中保存私有變量雁仲,外部無法直接訪問和修改這些變量仔夺。

實現(xiàn)模塊化:通過使用閉包,可以將一組相關的函數(shù)和數(shù)據(jù)封裝在一個作用域內攒砖,實現(xiàn)模塊化的代碼結構缸兔。

延遲執(zhí)行:通過使用閉包,可以創(chuàng)建一個函數(shù)吹艇,該函數(shù)在外部作用域中的某些條件滿足時才執(zhí)行惰蜜,實現(xiàn)延遲執(zhí)行的效果。

需要注意的是受神,閉包在使用時需要注意內存管理抛猖,因為閉包中引用的變量在函數(shù)執(zhí)行完之后仍然存在于內存中,可能導致內存泄漏鼻听〔浦可以通過主動解除對閉包的引用來釋放內存。

2.對作用域撑碴、作用域鏈的理解

作用域是指在程序中定義變量的區(qū)域瓢宦,它決定了變量的可訪問性和可見性。

在 JavaScript 中灰羽,有三種作用域:

全局作用域:在任何函數(shù)外部定義的變量都屬于全局作用域,可以被程序中的任何地方訪問鱼辙。

函數(shù)作用域:在函數(shù)內部定義的變量只在函數(shù)內部有效廉嚼,稱為函數(shù)作用域。這意味著函數(shù)內部的變量對于外部是不可見的倒戏,但可以訪問外部作用域的變量怠噪。

塊級作用域:在 ES6 中引入了塊級作用域,通過使用 let 或 const 關鍵字在塊(例如杜跷,if 條件傍念、for 循環(huán)等)內部定義的變量只在塊內部有效矫夷。這樣,塊級作用域允許在更小的范圍內控制變量的可見性和生命周期憋槐。

作用域鏈是由作用域嵌套關系所形成的鏈式結構双藕。在變量訪問時,JavaScript 引擎會沿著作用域鏈向上查找變量的定義阳仔,直到找到第一個匹配的變量或者到達全局作用域忧陪。

當訪問一個變量時,JavaScript 引擎會先搜索當前作用域中是否有該變量的聲明近范,如果沒有找到嘶摊,則繼續(xù)向上一級的作用域中查找,直到找到該變量或者到達全局作用域评矩。這個搜索變量的過程就是通過作用域鏈來實現(xiàn)的叶堆。

作用域鏈有助于解決變量的詞法解析、變量訪問和變量查找的問題斥杜。通過作用域鏈虱颗,內部作用域可以訪問外部作用域中的變量,但外部作用域不能訪問內部作用域的變量果录。

————————————————————分割

閉包是指有權訪問另一個函數(shù)作用域中變量的函數(shù)上枕,創(chuàng)建閉包的最常見的方式就是在一個函數(shù)內創(chuàng)建另一個函數(shù),創(chuàng)建的函數(shù)可以訪問到當前函數(shù)的局部變量弱恒。

閉包有兩個常用的用途辨萍;

  • 閉包的第一個用途是使我們在函數(shù)外部能夠訪問到函數(shù)內部的變量。通過使用閉包返弹,可以通過在外部調用閉包函數(shù)锈玉,從而在外部訪問到函數(shù)內部的變量,可以使用這種方法來創(chuàng)建私有變量义起。
  • 閉包的另一個用途是使已經(jīng)運行結束的函數(shù)上下文中的變量對象繼續(xù)留在內存中拉背,因為閉包函數(shù)保留了這個變量對象的引用,所以這個變量對象不會被回收默终。

比如椅棺,函數(shù) A 內部有一個函數(shù) B,函數(shù) B 可以訪問到函數(shù) A 中的變量齐蔽,那么函數(shù) B 就是閉包两疚。

function A() {
  let a = 1
  window.B = function () {
      console.log(a)
  }
}
A()
B() // 1

在 JS 中,閉包存在的意義就是讓我們可以間接訪問函數(shù)內部的變量含滴。經(jīng)典面試題:循環(huán)中使用閉包解決 var 定義函數(shù)的問題

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

首先因為 setTimeout 是個異步函數(shù)诱渤,所以會先把循環(huán)全部執(zhí)行完畢,這時候 i 就是 6 了谈况,所以會輸出一堆 6勺美。解決辦法有三種:

  • 第一種是使用閉包的方式
for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j)
    }, j * 1000)
  })(i)
}

在上述代碼中递胧,首先使用了立即執(zhí)行函數(shù)將 i 傳入函數(shù)內部,這個時候值就被固定在了參數(shù) j 上面不會改變赡茸,當下次執(zhí)行 timer 這個閉包的時候缎脾,就可以使用外部函數(shù)的變量 j,從而達到目的坛掠。

  • 第二種就是使用 setTimeout 的第三個參數(shù)赊锚,這個參數(shù)會被當成 timer 函數(shù)的參數(shù)傳入。
for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j)
    },
    i * 1000,
    i
  )
}
  • 第三種就是使用 let 定義 i 了來解決問題了屉栓,這個也是最為推薦的方式
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

3.對執(zhí)行上下文的理解

執(zhí)行上下文是指在程序執(zhí)行過程中舷蒲,用于跟蹤和管理變量、函數(shù)和對象的環(huán)境友多。它包含了當前代碼執(zhí)行的所有信息牲平,包括變量的值、函數(shù)的引用域滥、作用域鏈等纵柿。

每當執(zhí)行一個函數(shù)或者代碼塊時,都會創(chuàng)建一個新的執(zhí)行上下文启绰。執(zhí)行上下文可以分為三種類型:

全局執(zhí)行上下文:在程序開始執(zhí)行時創(chuàng)建昂儒,全局執(zhí)行上下文是最外層的執(zhí)行上下文。它包含了全局變量和全局函數(shù)委可,并且是所有其他執(zhí)行上下文的父級渊跋。

函數(shù)執(zhí)行上下文:每當調用一個函數(shù)時,都會創(chuàng)建一個新的函數(shù)執(zhí)行上下文着倾。函數(shù)執(zhí)行上下文與函數(shù)相關聯(lián)拾酝,包含了函數(shù)的參數(shù)、局部變量和內部函數(shù)等卡者。

eval執(zhí)行上下文:當使用eval函數(shù)執(zhí)行動態(tài)代碼時蒿囤,會創(chuàng)建一個eval執(zhí)行上下文。eval執(zhí)行上下文與eval函數(shù)相關聯(lián)崇决,包含了eval函數(shù)中的變量和函數(shù)材诽。

執(zhí)行上下文的主要組成部分有:

變量對象(Variable Object):用于存儲變量、函數(shù)聲明和函數(shù)參數(shù)恒傻。在全局執(zhí)行上下文中脸侥,變量對象就是全局對象(如window對象)。在函數(shù)執(zhí)行上下文中碌冶,變量對象包含了函數(shù)的參數(shù)、函數(shù)聲明和內部變量涝缝。

作用域鏈(Scope Chain):用于解析變量和函數(shù)的訪問權限扑庞。作用域鏈是一個由當前執(zhí)行上下文的變量對象和所有父級執(zhí)行上下文的變量對象組成的鏈表譬重。

this值:指向當前執(zhí)行上下文所屬的對象。在全局執(zhí)行上下文中罐氨,this指向全局對象臀规。在函數(shù)執(zhí)行上下文中,this的值取決于函數(shù)的調用方式栅隐。

當代碼執(zhí)行到一個新的執(zhí)行上下文時塔嬉,會按照以下步驟進行處理:

創(chuàng)建變量對象(包括函數(shù)參數(shù)、函數(shù)聲明和內部變量)租悄。
建立作用域鏈谨究,將當前執(zhí)行上下文的變量對象添加到作用域鏈的前端。
確定this的值泣棋。
執(zhí)行代碼胶哲。
當一個執(zhí)行上下文執(zhí)行完畢后,會被銷毀潭辈,控制權會返回到父級執(zhí)行上下文鸯屿。這樣,程序就可以在不同的執(zhí)行上下文之間進行切換和管理變量把敢、函數(shù)和對象寄摆。

——————————————分割

1. 執(zhí)行上下文類型

(1)全局執(zhí)行上下文

任何不在函數(shù)內部的都是全局執(zhí)行上下文,它首先會創(chuàng)建一個全局的window對象修赞,并且設置this的值等于這個全局對象婶恼,一個程序中只有一個全局執(zhí)行上下文。

(2)函數(shù)執(zhí)行上下文

當一個函數(shù)被調用時,就會為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文娘荡,函數(shù)的上下文可以有任意多個臀晃。

(3)eval函數(shù)執(zhí)行上下文

執(zhí)行在eval函數(shù)中的代碼會有屬于他自己的執(zhí)行上下文,不過eval函數(shù)不常使用检痰,不做介紹。

2. 執(zhí)行上下文棧
  • JavaScript引擎使用執(zhí)行上下文棧來管理執(zhí)行上下文
  • 當JavaScript執(zhí)行代碼時锨推,首先遇到全局代碼铅歼,會創(chuàng)建一個全局執(zhí)行上下文并且壓入執(zhí)行棧中,每當遇到一個函數(shù)調用换可,就會為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文并壓入棧頂椎椰,引擎會執(zhí)行位于執(zhí)行上下文棧頂?shù)暮瘮?shù),當函數(shù)執(zhí)行完成之后沾鳄,執(zhí)行上下文從棧中彈出慨飘,繼續(xù)執(zhí)行下一個上下文。當所有的代碼都執(zhí)行完畢之后,從棧中彈出全局執(zhí)行上下文瓤的。
let a = 'Hello World!';
function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}
function second() {
  console.log('Inside second function');
}
first();
//執(zhí)行順序
//先執(zhí)行second(),z執(zhí)行first()
3. 創(chuàng)建執(zhí)行上下文

創(chuàng)建執(zhí)行上下文有兩個階段:創(chuàng)建階段執(zhí)行階段

1)創(chuàng)建階段

(1)this綁定

  • 在全局執(zhí)行上下文中休弃,this指向全局對象(window對象)
  • 在函數(shù)執(zhí)行上下文中,this指向取決于函數(shù)如何調用圈膏。如果它被一個引用對象調用塔猾,那么 this 會被設置成那個對象,否則 this 的值被設置為全局對象或者 undefined

(2)創(chuàng)建詞法環(huán)境組件

  • 詞法環(huán)境是一種有標識符——變量映射的數(shù)據(jù)結構稽坤,標識符是指變量/函數(shù)名丈甸,變量是對實際對象或原始數(shù)據(jù)的引用。
  • 詞法環(huán)境的內部有兩個組件:加粗樣式:環(huán)境記錄器:用來儲存變量個函數(shù)聲明的實際位置外部環(huán)境的引用:可以訪問父級作用域

(3)創(chuàng)建變量環(huán)境組件

  • 變量環(huán)境也是一個詞法環(huán)境尿褪,其環(huán)境記錄器持有變量聲明語句在執(zhí)行上下文中創(chuàng)建的綁定關系睦擂。

2)執(zhí)行階段

此階段會完成對變量的分配,最后執(zhí)行完代碼茫多。

簡單來說執(zhí)行上下文就是指:

在執(zhí)行一點JS代碼之前祈匙,需要先解析代碼。解析的時候會先創(chuàng)建一個全局執(zhí)行上下文環(huán)境天揖,先把代碼中即將執(zhí)行的變量夺欲、函數(shù)聲明都拿出來,變量先賦值為undefined今膊,函數(shù)先聲明好可使用些阅。這一步執(zhí)行完了,才開始正式的執(zhí)行程序斑唬。

在一個函數(shù)執(zhí)行之前市埋,也會創(chuàng)建一個函數(shù)執(zhí)行上下文環(huán)境,跟全局執(zhí)行上下文類似恕刘,不過函數(shù)執(zhí)行上下文會多出this缤谎、arguments和函數(shù)的參數(shù)。

  • 全局上下文:變量定義褐着,函數(shù)聲明
  • 函數(shù)上下文:變量定義坷澡,函數(shù)聲明,this含蓉,arguments

this/call/apply/bind篇:

1.對this對象的理解

this是JavaScript中的一個特殊對象频敛,它在每個執(zhí)行上下文中都存在,并且指向當前執(zhí)行上下文所屬的對象馅扣。this的值是在函數(shù)調用時確定的斟赚,它的指向取決于函數(shù)的調用方式。

在不同的情況下差油,this的值可能會有所不同:

1.全局上下文中:在全局上下文中拗军,this指向全局對象(在瀏覽器環(huán)境中通常是window對象)。

2.函數(shù)上下文中:在函數(shù)內部,this的值取決于函數(shù)的調用方式发侵。

函數(shù)作為普通函數(shù)調用時侈咕,this指向全局對象(非嚴格模式下)或undefined(嚴格模式下)。
函數(shù)作為對象的方法調用時器紧,this指向調用該方法的對象。
函數(shù)作為構造函數(shù)調用時楼眷,this指向新創(chuàng)建的對象铲汪。
使用call()、apply()或bind()方法調用函數(shù)時罐柳,可以顯式地指定this的值掌腰。
3.箭頭函數(shù)中:箭頭函數(shù)沒有自己的this值,它會繼承外部函數(shù)的this值张吉。

總結起來齿梁,this的值是在函數(shù)調用時確定的,它指向當前執(zhí)行上下文所屬的對象肮蛹。具體的指向取決于函數(shù)的調用方式勺择。理解this的指向對于正確理解和使用JavaScript中的對象和函數(shù)非常重要。

——————————————————————分割

this 是執(zhí)行上下文中的一個屬性伦忠,它指向最后一次調用這個方法的對象省核。在實際開發(fā)中,this 的指向可以通過四種調用模式來判斷昆码。

  • 第一種是函數(shù)調用模式气忠,當一個函數(shù)不是一個對象的屬性時,直接作為函數(shù)來調用時赋咽,this 指向全局對象旧噪。
  • 第二種是方法調用模式,如果一個函數(shù)作為一個對象的方法來調用時脓匿,this 指向這個對象淘钟。
  • 第三種是構造器調用模式,如果一個函數(shù)用 new 調用時亦镶,函數(shù)執(zhí)行前會新創(chuàng)建一個對象日月,this 指向這個新創(chuàng)建的對象。
  • 第四種是 apply 缤骨、 call 和 bind 調用模式爱咬,這三個方法都可以顯示的指定調用函數(shù)的 this 指向。其中 apply 方法接收兩個參數(shù):一個是 this 綁定的對象绊起,一個是參數(shù)數(shù)組精拟。call 方法接收的參數(shù),第一個是 this 綁定的對象,后面的其余參數(shù)是傳入函數(shù)執(zhí)行的參數(shù)蜂绎。也就是說栅表,在使用 call() 方法時,傳遞給函數(shù)的參數(shù)必須逐個列舉出來师枣。bind 方法通過傳入一個對象怪瓶,返回一個 this 綁定了傳入對象的新函數(shù)。這個函數(shù)的 this 指向除了使用 new 時會被改變践美,其他情況下都不會改變洗贰。

這四種方式,使用構造器調用模式的優(yōu)先級最高陨倡,然后是 apply敛滋、call 和 bind 調用模式兴革,然后是方法調用模式,然后是函數(shù)調用模式庶艾。

2.call和apply的區(qū)別

call 和 apply 都是 JavaScript 中用于調用函數(shù)的方法擎勘,它們之間的區(qū)別在于參數(shù)的傳遞方式不同。

call 方法接受一個指定的 this 值和單獨的參數(shù)(一個參數(shù)列表)來調用函數(shù)述召。這意味著你可以將每個參數(shù)以逗號分隔的形式傳遞給函數(shù)蟹地。

示例代碼:

function greet(name) {
  console.log(`Hello, ${name}!`);
}

greet.call(null, 'John');

apply 方法與 call 方法相似,接受一個指定的 this 值和一個數(shù)組(或類數(shù)組對象)作為參數(shù)怪与。在調用函數(shù)時夺刑,會將數(shù)組中的每個元素作為單獨的參數(shù)傳遞給函數(shù)。

示例代碼:

function greet(name) {
  console.log(`Hello, ${name}!`);
}

greet.apply(null, ['John']);

總結來說分别,call 和 apply 的作用都是改變函數(shù)內部的 this 指向遍愿,只是參數(shù)的傳遞方式不同。你可以根據(jù)你的需求選擇使用其中之一耘斩。

————————————————分割

它們的作用一模一樣沼填,區(qū)別僅在于傳入?yún)?shù)的形式的不同。

  • apply 接受兩個參數(shù)括授,第一個參數(shù)指定了函數(shù)體內 this 對象的指向坞笙,第二個參數(shù)為一個帶下標的集合岩饼,這個集合可以為數(shù)組,也可以為類數(shù)組薛夜,apply 方法把這個集合中的元素作為參數(shù)傳遞給被調用的函數(shù)籍茧。
  • call 傳入的參數(shù)數(shù)量不固定,跟 apply 相同的是梯澜,第一個參數(shù)也是代表函數(shù)體內的 this 指向寞冯,從第二個參數(shù)開始往后,每個參數(shù)被依次傳入函數(shù)晚伙。

3.實現(xiàn)call简十、apply及bind函數(shù)

(1)call 函數(shù)的實現(xiàn)步驟:

  • 判斷調用對象是否為函數(shù),即使是定義在函數(shù)的原型上的撬腾,但是可能出現(xiàn)使用 call 等方式調用的情況民傻。
  • 判斷傳入上下文對象是否存在漓踢,如果不存在喧半,則設置為 window 挺据。
  • 處理傳入的參數(shù)扁耐,截取第一個參數(shù)后的所有參數(shù)婉称。
  • 將函數(shù)作為上下文對象的一個屬性。
  • 使用上下文對象來調用這個方法俗壹,并保存返回結果策肝。
  • 刪除剛才新增的屬性。
  • 返回結果拙毫。
Function.prototype.myCall = function(context) {
  // 判斷調用對象
  if (typeof this !== "function") {
    console.error("type error");
  }
  // 獲取參數(shù)
  let args = [...arguments].slice(1),
    result = null;
  // 判斷 context 是否傳入,如果未傳入則設置為 window
  context = context || window;
  // 將調用函數(shù)設為對象的方法
  context.fn = this;
  // 調用函數(shù)
  result = context.fn(...args);
  // 將屬性刪除
  delete context.fn;
  return result;
};

(2)apply 函數(shù)的實現(xiàn)步驟:

  • 判斷調用對象是否為函數(shù)缺前,即使是定義在函數(shù)的原型上的衅码,但是可能出現(xiàn)使用 call 等方式調用的情況逝段。
  • 判斷傳入上下文對象是否存在,如果不存在嘹黔,則設置為 window 儡蔓。
  • 將函數(shù)作為上下文對象的一個屬性浙值。
  • 判斷參數(shù)值是否傳入
  • 使用上下文對象來調用這個方法开呐,并保存返回結果筐付。
  • 刪除剛才新增的屬性
  • 返回結果
Function.prototype.myApply = function(context) {
  // 判斷調用對象是否為函數(shù)
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判斷 context 是否存在,如果未傳入則為 window
  context = context || window;
  // 將函數(shù)設為對象的方法
  context.fn = this;
  // 調用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 將屬性刪除
  delete context.fn;
  return result;
};

(3)bind 函數(shù)的實現(xiàn)步驟:

  • 判斷調用對象是否為函數(shù)较解,即使是定義在函數(shù)的原型上的印衔,但是可能出現(xiàn)使用 call 等方式調用的情況奸焙。
  • 保存當前函數(shù)的引用了赌,獲取其余傳入?yún)?shù)值勿她。
  • 創(chuàng)建一個函數(shù)返回
  • 函數(shù)內部使用 apply 來綁定函數(shù)調用逢并,需要判斷函數(shù)作為構造函數(shù)的情況,這個時候需要傳入當前函數(shù)的 this 給 apply 調用箱沦,其余情況都傳入指定的上下文對象。
Function.prototype.myBind = function(context) {
  // 判斷調用對象是否為函數(shù)
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 獲取參數(shù)
  var args = [...arguments].slice(1),
    fn = this;
  return function Fn() {
    // 根據(jù)調用方式疆前,傳入不同綁定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

面向對象篇:

1.對象創(chuàng)建的方式有那些

一般使用字面量的形式直接創(chuàng)建對象童太,但是這種創(chuàng)建方式對于創(chuàng)建大量相似對象的時候,會產(chǎn)生大量的重復代碼爆惧。但 js和一般的面向對象的語言不同扯再,在 ES6 之前它沒有類的概念熄阻。但是可以使用函數(shù)來進行模擬窃页,從而產(chǎn)生出可復用的對象創(chuàng)建方式脖卖,常見的有以下幾種:

(1)第一種是工廠模式畦木,工廠模式的主要工作原理是用函數(shù)來封裝創(chuàng)建對象的細節(jié)十籍,從而通過調用函數(shù)來達到復用的目的。但是它有一個很大的問題就是創(chuàng)建出來的對象無法和某個類型聯(lián)系起來围俘,它只是簡單的封裝了復用代碼界牡,而沒有建立起對象和類型間的關系。

(2)第二種是構造函數(shù)模式纳令。js 中每一個函數(shù)都可以作為構造函數(shù)坤按,只要一個函數(shù)是通過 new 來調用的臭脓,那么就可以把它稱為構造函數(shù)来累。執(zhí)行構造函數(shù)首先會創(chuàng)建一個對象葫录,然后將對象的原型指向構造函數(shù)的 prototype 屬性米同,然后將執(zhí)行上下文中的 this 指向這個對象,最后再執(zhí)行整個函數(shù)熬苍,如果返回值不是對象柴底,則返回新建的對象。因為 this 的值指向了新建的對象鸿脓,因此可以使用 this 給對象賦值答憔。構造函數(shù)模式相對于工廠模式的優(yōu)點是,所創(chuàng)建的對象和構造函數(shù)建立起了聯(lián)系傲武,因此可以通過原型來識別對象的類型揪利。但是構造函數(shù)存在一個缺點就是疟位,造成了不必要的函數(shù)對象的創(chuàng)建绍撞,因為在 js 中函數(shù)也是一個對象傻铣,因此如果對象屬性中如果包含函數(shù)的話,那么每次都會新建一個函數(shù)對象两踏,浪費了不必要的內存空間,因為函數(shù)是所有的實例都可以通用的弓坞。

(3)第三種模式是原型模式渡冻,因為每一個函數(shù)都有一個 prototype 屬性,這個屬性是一個對象超歌,它包含了通過構造函數(shù)創(chuàng)建的所有實例都能共享的屬性和方法。因此可以使用原型對象來添加公用屬性和方法懊悯,從而實現(xiàn)代碼的復用炭分。這種方式相對于構造函數(shù)模式來說,解決了函數(shù)對象的復用問題呀忧。但是這種模式也存在一些問題七兜,一個是沒有辦法通過傳入?yún)?shù)來初始化值,另一個是如果存在一個引用類型如 Array 這樣的值狠裹,那么所有的實例將共享一個對象,一個實例對引用類型值的改變會影響所有的實例俗冻。

(4)第四種模式是組合使用構造函數(shù)模式和原型模式,這是創(chuàng)建自定義類型的最常見方式煮岁。因為構造函數(shù)模式和原型模式分開使用都存在一些問題冶伞,因此可以組合使用這兩種模式响禽,通過構造函數(shù)來初始化對象的屬性,通過原型對象來實現(xiàn)函數(shù)方法的復用戳护。這種方法很好的解決了兩種模式單獨使用時的缺點金抡,但是有一點不足的就是瀑焦,因為使用了兩種不同的模式腌且,所以對于代碼的封裝性不夠好。

(5)第五種模式是動態(tài)原型模式榛瓮,這一種模式將原型方法賦值的創(chuàng)建過程移動到了構造函數(shù)的內部坝锰,通過對屬性是否存在的判斷弓颈,可以實現(xiàn)僅在第一次調用函數(shù)時對原型對象賦值一次的效果。這一種方式很好地對上面的混合模式進行了封裝。

(6)第六種模式是寄生構造函數(shù)模式,這一種模式和工廠模式的實現(xiàn)基本相同,我對這個模式的理解是埂伦,它主要是基于一個已有的類型基跑,在實例化時對實例化的對象進行擴展力图。這樣既不用修改原來的構造函數(shù),也達到了擴展對象的目的。它的一個缺點和工廠模式一樣,無法實現(xiàn)對象的識別钮热。

2.對象繼承的方式有哪些

(1)第一種是以原型鏈的方式來實現(xiàn)繼承宏蛉,但是這種實現(xiàn)方式存在的缺點是,在包含有引用類型的數(shù)據(jù)時凰萨,會被所有的實例對象所共享,容易造成修改的混亂。還有就是在創(chuàng)建子類型的時候不能向超類型傳遞參數(shù)沛鸵。

(2)第二種方式是使用借用構造函數(shù)的方式乱豆,這種方式是通過在子類型的函數(shù)中調用超類型的構造函數(shù)來實現(xiàn)的疲酌,這一種方法解決了不能向超類型傳遞參數(shù)的缺點粥诫,但是它存在的一個問題就是無法實現(xiàn)函數(shù)方法的復用镰踏,并且超類型原型定義的方法子類型也沒有辦法訪問到滤否。

(3)第三種方式是組合繼承耀石,組合繼承是將原型鏈和借用構造函數(shù)組合起來使用的一種方式称开。通過借用構造函數(shù)的方式來實現(xiàn)類型的屬性的繼承清酥,通過將子類型的原型設置為超類型的實例來實現(xiàn)方法的繼承旭从。這種方式解決了上面的兩種模式單獨使用時的問題馍忽,但是由于我們是以超類型的實例來作為子類型的原型,所以調用了兩次超類的構造函數(shù)饲宿,造成了子類型的原型中多了很多不必要的屬性剧蚣。

(4)第四種方式是原型式繼承馒吴,原型式繼承的主要思路就是基于已有的對象來創(chuàng)建新的對象,實現(xiàn)的原理是,向函數(shù)中傳入一個對象波桩,然后返回一個以這個對象為原型的對象戒努。這種繼承的思路主要不是為了實現(xiàn)創(chuàng)造一種新的類型,只是對某個對象實現(xiàn)一種簡單繼承镐躲,ES5 中定義的 Object.create() 方法就是原型式繼承的實現(xiàn)储玫。缺點與原型鏈方式相同侍筛。

(5)第五種方式是寄生式繼承,寄生式繼承的思路是創(chuàng)建一個用于封裝繼承過程的函數(shù)撒穷,通過傳入一個對象匣椰,然后復制一個對象的副本,然后對象進行擴展端礼,最后返回這個對象禽笑。這個擴展的過程就可以理解是一種繼承。這種繼承的優(yōu)點就是對一個簡單對象實現(xiàn)繼承蛤奥,如果這個對象不是自定義類型時佳镜。缺點是沒有辦法實現(xiàn)函數(shù)的復用。

(6)第六種方式是寄生式組合繼承喻括,組合繼承的缺點就是使用超類型的實例做為子類型的原型邀杏,導致添加了不必要的原型屬性贫奠。寄生式組合繼承的方式是使用超類型的原型的副本來作為子類型的原型唬血,這樣就避免了創(chuàng)建不必要的屬性。

垃圾回收與內存泄露篇:

1.瀏覽器的垃圾回收機制

(1)垃圾回收的概念

垃圾回收:JavaScript代碼運行時唤崭,需要分配內存空間來儲存變量和值拷恨。當變量不在參與運行時,就需要系統(tǒng)收回被占用的內存空間谢肾,這就是垃圾回收腕侄。

回收機制

  • Javascript 具有自動垃圾回收機制,會定期對那些不再使用的變量芦疏、對象所占用的內存進行釋放冕杠,原理就是找到不再使用的變量,然后釋放掉其占用的內存酸茴。
  • JavaScript中存在兩種變量:局部變量和全局變量分预。全局變量的生命周期會持續(xù)要頁面卸載;而局部變量聲明在函數(shù)中薪捍,它的生命周期從函數(shù)執(zhí)行開始笼痹,直到函數(shù)執(zhí)行結束,在這個過程中酪穿,局部變量會在堆或棧中存儲它們的值凳干,當函數(shù)執(zhí)行結束后,這些局部變量不再被使用被济,它們所占有的空間就會被釋放救赐。
  • 不過,當局部變量被外部函數(shù)使用時只磷,其中一種情況就是閉包经磅,在函數(shù)執(zhí)行結束后少欺,函數(shù)外部的變量依然指向函數(shù)內部的局部變量,此時局部變量依然在被使用馋贤,所以不會回收赞别。
(2)垃圾回收的方式

瀏覽器通常使用的垃圾回收方法有兩種:標記清除,引用計數(shù)配乓。

1)標記清除

  • 標記清除是瀏覽器常見的垃圾回收方式仿滔,當變量進入執(zhí)行環(huán)境時,就標記這個變量“進入環(huán)境”犹芹,被標記為“進入環(huán)境”的變量是不能被回收的崎页,因為他們正在被使用。當變量離開環(huán)境時腰埂,就會被標記為“離開環(huán)境”飒焦,被標記為“離開環(huán)境”的變量會被內存釋放。
  • 垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記屿笼。然后牺荠,它會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的標記。而在此之后再被加上標記的變量將被視為準備刪除的變量驴一,原因是環(huán)境中的變量已經(jīng)無法訪問到這些變量了休雌。最后。垃圾收集器完成內存清除工作肝断,銷毀那些帶標記的值杈曲,并回收他們所占用的內存空間。

2)引用計數(shù)

  • 另外一種垃圾回收機制就是引用計數(shù)胸懈,這個用的相對較少担扑。引用計數(shù)就是跟蹤記錄每個值被引用的次數(shù)。當聲明了一個變量并將一個引用類型賦值給該變量時趣钱,則這個值的引用次數(shù)就是1涌献。相反,如果包含對這個值引用的變量又取得了另外一個值羔挡,則這個值的引用次數(shù)就減1洁奈。當這個引用次數(shù)變?yōu)?時,說明這個變量已經(jīng)沒有價值绞灼,因此利术,在在機回收期下次再運行時,這個變量所占有的內存空間就會被釋放出來低矮。
  • 這種方法會引起循環(huán)引用的問題:例如:obj1obj2通過屬性進行相互引用印叁,兩個對象的引用次數(shù)都是2。當使用循環(huán)計數(shù)時,由于函數(shù)執(zhí)行完后轮蜕,兩個對象都離開作用域昨悼,函數(shù)執(zhí)行結束,obj1obj2還將會繼續(xù)存在跃洛,因此它們的引用次數(shù)永遠不會是0率触,就會引起循環(huán)引用。
function fun() {
    let obj1 = {};
    let obj2 = {};
    obj1.a = obj2; // obj1 引用 obj2
    obj2.a = obj1; // obj2 引用 obj1
}

這種情況下汇竭,就要手動釋放變量占用的內存:

obj1.a =  null
obj2.a =  null
(3)減少垃圾回收

雖然瀏覽器可以進行垃圾自動回收葱蝗,但是當代碼比較復雜時,垃圾回收所帶來的代價比較大细燎,所以應該盡量減少垃圾回收两曼。

  • 對數(shù)組進行優(yōu)化:在清空一個數(shù)組時,最簡單的方法就是給其賦值為[ ]玻驻,但是與此同時會創(chuàng)建一個新的空對象悼凑,可以將數(shù)組的長度設置為0毯焕,以此來達到清空數(shù)組的目的埋市。
  • object進行優(yōu)化:對象盡量復用往产,對于不再使用的對象锐秦,就將其設置為null,盡快被回收殉了。
  • 對函數(shù)進行優(yōu)化:在循環(huán)中的函數(shù)表達式尖滚,如果可以復用,盡量放在函數(shù)的外面档冬。

2. 哪些情況會導致內存泄漏

以下四種情況會造成內存的泄漏:

  • 意外的全局變量:由于使用未聲明的變量,而意外的創(chuàng)建了一個全局變量桃纯,而使這個變量一直留在內存中無法被回收酷誓。
  • 被遺忘的計時器或回調函數(shù):設置了 setInterval 定時器,而忘記取消它态坦,如果循環(huán)函數(shù)有對外部變量的引用的話盐数,那么這個變量會被一直留在內存中,而無法被回收伞梯。
  • 脫離 DOM 的引用:獲取一個 DOM 元素的引用玫氢,而后面這個元素被刪除,由于一直保留了對這個元素的引用谜诫,所以它也無法被回收漾峡。
  • 閉包:不合理的使用閉包,從而導致某些變量一直被留在內存當中喻旷。

javascript基礎篇:

1.new操作符的實現(xiàn)原理

2.數(shù)組有哪些原生方法

3.什么是DOM和BOM

4.對類數(shù)組對象的理解生逸,如何轉化為數(shù)組

5.對AJAX的理解,實現(xiàn)一個AJAX請求

6.Javascript為什么要進行變量提升,它導致了什么問題

數(shù)據(jù)類型篇:

1.javaScript有哪些數(shù)據(jù)類型槽袄,他們的區(qū)別

2.數(shù)據(jù)類型檢測的方式有哪些

3.判斷數(shù)組的方式有哪些

4.null和undefined區(qū)別

5.intanceof 操作符的實現(xiàn)原理及實現(xiàn)

6.為什么0.1+0.2烙无!==0.3,如何讓其相等

7.==操作符的強制類型轉換規(guī)則

8.Object.is()與比較操作符“===”遍尺、“==”的區(qū)別


  1. let截酷、const、var的區(qū)別 ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末乾戏,一起剝皮案震驚了整個濱河市合搅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歧蕉,老刑警劉巖灾部,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惯退,居然都是意外死亡赌髓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門催跪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锁蠕,“玉大人,你說我怎么就攤上這事懊蒸∪偾悖” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵骑丸,是天一觀的道長舌仍。 經(jīng)常有香客問我,道長通危,這世上最難降的妖魔是什么铸豁? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮菊碟,結果婚禮上节芥,老公的妹妹穿的比我還像新娘。我一直安慰自己逆害,他們只是感情好头镊,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魄幕,像睡著了一般相艇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梅垄,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天厂捞,我揣著相機與錄音输玷,去河邊找鬼。 笑死靡馁,一個胖子當著我的面吹牛欲鹏,可吹牛的內容都是我干的。 我是一名探鬼主播臭墨,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赔嚎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胧弛?” 一聲冷哼從身側響起尤误,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎结缚,沒想到半個月后损晤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡红竭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年尤勋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茵宪。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡最冰,死狀恐怖,靈堂內的尸體忽然破棺而出稀火,到底是詐尸還是另有隱情暖哨,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布凰狞,位于F島的核電站篇裁,受9級特大地震影響,放射性物質發(fā)生泄漏服球。R本人自食惡果不足惜茴恰,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斩熊。 院中可真熱鬧,春花似錦伐庭、人聲如沸粉渠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霸株。三九已至,卻和暖如春集乔,著一層夾襖步出監(jiān)牢的瞬間去件,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尤溜,地道東北人倔叼。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像宫莱,于是被迫代替她去往敵國和親丈攒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容