謂無規矩不成方圓,前端時間在團隊 code-review 中發現,不同時期不同開發人員寫的代碼可謂五花八門。因此我們提出了一些相關代碼方面的規范,希望日后能形成團隊的編碼規范。
本文主要針對一些 JavaScript 進行優化,使之更加健壯,可讀性更強,更以維護。
gitthub地址:github.com/Michael-lzg…
上一篇:code-review之前端代碼規范
JavaScript 條件語句在我們平時的開發中是不可避免要用到的,但是很多時候我們的代碼寫的并不好,一連串的 if-else 或者多重嵌套判斷都會使得代碼很臃腫,下面舉例進行優化。
需求:現在有 4 個產品,分別是手機、電腦、電視機、游戲機,當然每個產品顯示的價格不一樣。
1、最簡單的方法:if 判斷
let commodity = {
phone: '手機',
computer: '電腦',
television: '電視',
gameBoy: '游戲機',
}
function price(name) {
if (name === commodity.phone) {
console.log(1999)
} else if (name === commodity.computer) {
console.log(9999)
} else if (name === commodity.television) {
console.log(2999)
} else if (name === commodity.gameBoy) {
console.log(3999)
}
}
price('手機') // 9999
缺點:代碼太長了,維護和閱讀都很不友好
2、好一點的方法:Switch
let commodity = {
phone: '手機',
computer: '電腦',
television: '電視',
gameBoy: '游戲機',
}
const price = (name) => {
switch (name) {
case commodity.phone:
console.log(1999)
break
case commodity.computer:
console.log(9999)
break
case commodity.television:
console.log(2999)
break
case commodity.gameBoy:
console.log(3999)
break
}
}
price('手機') // 9999
3、更優的方法: 策略模式
策略模式利用組合、委托和多態等技術和思想,可以有效地避免多重條件選擇語句。它提供了對開放—封閉原則的完美支持,將算法封裝在獨立的 strategy 中,使得它們易于切換,易于理解,易于擴展。
const commodity = new Map([
['phone', 1999],
['computer', 9999],
['television', 2999],
['gameBoy', 3999],
])
const price = (name) => {
return commodity.get(name)
}
price('phone') // 1999
includes 是 ES7 新增的 API,與 indexOf 不同的是 includes 直接返回的是 Boolean 值,indexOf 則 返回的索引值, 數組和字符串都有 includes 方法。
需求:我們來實現一個身份認證方法,通過傳入身份 Id 返回對應的驗證結果
傳統方法
function verifyIdentity(identityId) {
if (identityId == 1 || identityId == 2 || identityId == 3 || identityId == 4) {
return '你的身份合法,請通行!'
} else {
return '你的身份不合法'
}
}
includes 優化
function verifyIdentity(identityId) {
if ([1, 2, 3, 4].includes(identityId)) {
return '你的身份合法,請通行!'
} else {
return '你的身份不合法'
}
}
在 JavaScript 中,我們可以使用 for(), while(), for(in),for(in)幾種循環,事實上,這三種循環中 for(in) 的效率極差,因為他需要查詢散列鍵,所以應該盡量少用。
for 循環是最傳統的語句,它以變量 i 作為索引,以跟蹤訪問的位置,對數組進行操作。
var arr = ['a', 'b', 'c']
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]) //結果依次a,b,c
}
以上的方法有一個問題:就是當數組的長度到達百萬級時,arr.length 就要計算一百萬次,這是相當耗性能的。所以可以采用以下方法就行改良。
var arr = ['a', 'b', 'c']
for (var i = 0, length = arr.length; i < length; i++) {
console.log(arr[i]) //結果依次a,b,c
}
此時 arr.length 只需要計算一次,優化了性能。
for-in 一般用來來遍歷對象的屬性的,不過屬性需要 enumerable(可枚舉)才能被讀取到。同時 for-in 也可以遍歷數組,遍歷數組的時候遍歷的是數組的下標值。
var obj = { 0: 'a', 1: 'b', 2: 'c' }
for (var key in obj) {
console.log(key) //結果為依次為0,1,2
}
var arr = ['a', 'b', 'c']
for (var key in a) {
console.log(key) //結果為依次為0,1,2
}
for-of 語句看著有點像 for-in 語句,但是和 for-of 語句不同的是它不可以循環對象,只能循環數組。
var arr = ['a', 'b', 'c']
for (var value of arr) {
console.log(value) // 結果依次為a,b,c
}
for-of 比 for-in 循環遍歷數組更好。for-of 只要具有 Iterator 接口的數據結構,都可以使用它迭代成員。它直接讀取的是鍵值。for-in 需要窮舉對象的所有屬性,包括自定義的添加的屬性也能遍歷到。且 for-in 的 key 是 String 類型,有轉換過程,開銷比較大。
所以在開發過程中循環數組盡量避免使用 for-in。
數組去重是實際開發處理數據中經常遇到的,方法有很多,這里就不一一例舉了。
1、最傳統的方法:利用數組的 indexOf 下標屬性來查詢。
function unique4(arr) {
var newArr = []
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i])
}
}
return newArr
}
console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
// [1, 2, 3, 5, 6, 7, 4]
2、優化:利用 ES6 的 Set 方法。
Set 本身是一個構造函數,用來生成 Set 數據結構。Set 函數可以接受一個數組(或者具有 iterable 接口的其他數據結構)作為參數,用來初始化。Set 對象允許你存儲任何類型的值,無論是原始值或者是對象引用。它類似于數組,但是成員的值都是唯一的,沒有重復的值。
function unique4(arr) {
return Array.from(new Set(arr)) // 利用Array.from將Set結構轉換成數組
}
console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
// [1, 2, 3, 5, 6, 7, 4]
箭頭函數表達式的語法比函數表達式更簡潔。所以在開發中更推薦使用箭頭函數。特別是在 vue 項目中,使用箭頭函數不需要在更 this 重新賦一個變量。
// 使用functions
var arr = [5, 3, 2, 9, 1]
var arrFunc = arr.map(function (x) {
return x * x
})
console.log(arrFunc)
// 使用箭頭函數
var arr = [5, 3, 2, 9, 1]
var arrFunc = arr.map((x) => x * x)
要注意的是,箭頭函數不綁定 arguments,取而代之用 rest 參數…解決。
// 不能使用 arguments
let fun1 = (b) => {
console.log(arguments)
}
fun1(2, 92, 32, 32) // Uncaught ReferenceError: arguments is not defined
// 使用rest 參數
let fun2 = (...c) => {
console.log(c)
}
fun2(3, 82, 32, 11323) // [3, 82, 32, 11323]
創建多個 dom 元素時,先將元素 append 到 DocumentFragment 中,最后統一將 DocumentFragment 添加到頁面。
常規方法;
for (var i = 0; i < 1000; i++) {
var el = document.createElement('p')
el.innerHTML = i
document.body.appendChild(el)
}
使用 DocumentFragment 優化多次 append
var frag = document.createDocumentFragment()
for (var i = 0; i < 1000; i++) {
var el = document.createElement('p')
el.innerHTML = i
frag.appendChild(el)
}
document.body.appendChild(frag)
更優的方法:使用一次 innerHTML 賦值代替構建 dom 元素
var html = []
for (var i = 0; i < 1000; i++) {
html.push('<p>' + i + '</p>')
}
document.body.innerHTML = html.join('')
系統進程不再用到的內存,沒有及時釋放,就叫做內存泄漏(memory leak)。當內存占用越來越高,輕則影響系統性能,重則導致進程崩潰。
引起內存泄漏的原因
1、未聲明變量或者使用 this 創建的變量(this 的指向是 window)都會引起內存泄漏
function fn() {
a = "Actually, I'm a global variable"
}
fn()
function fn() {
this.a = "Actually, I'm a global variable"
}
fn()
解決方法:
2、在 vue 單頁面應用,聲明的全局變量在切換頁面的時候沒有清空
<template>
<div id="home">
這里是首頁
</div>
</template>
<script>
export default {
mounted() {
window.test = {
// 此處在全局window對象中引用了本頁面的dom對象
name: 'home',
node: document.getElementById('home')
}
}
}
</script>
解決方案: 在頁面卸載的時候順便處理掉該引用。
destroyed () {
window.test = null // 頁面卸載的時候解除引用
}
閉包引起的內存泄漏原因:閉包可以維持函數內局部變量,使其得不到釋放。
function fn() {
var a = "I'm a"
return function () {
console.log(a)
}
}
解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對 dom 的引用。
由于項目中有些頁面難免會碰到需要定時器或者事件監聽。但是在離開當前頁面的時候,定時器如果不及時合理地清除,會造成業務邏輯混亂甚至應用卡死的情況,這個時就需要清除定時器事件監聽,即在頁面卸載(關閉)的生命周期函數里,清除定時器。
methods:{
resizeFun () {
this.tableHeight = window.innerHeight - document.getElementById('table').offsetTop - 128
},
setTimer() {
this.timer = setInterval(() => { })
},
clearTimer() {//清除定時器
clearInterval(this.timer)
this.timer = null
}
},
mounted() {
this.setTimer()
window.addEventListener('resize', this.resizeFun)
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeFun)
this.clearTimer()
}
在前端開發的過程中,我們經常會需要綁定一些持續觸發的事件,如 resize、scroll、mousemove 等等,但有些時候我們并不希望在事件持續觸發的過程中那么頻繁地去執行函數。這時候就用到防抖與節流。
案例 1:遠程搜索時需要通過接口動態的獲取數據,若是每次用戶輸入都接口請求,是浪費帶寬和性能的。
<Select :remote-method="remoteMethod">
<Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}</Option>
</Select>
<script>
function debounce(fn, wait) {
let timeout = null
return function () {
if (timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
export default {
methods:{
remoteMethod:debounce(function (query) {
// to do ...
}, 200),
}
}
<script>
案例 2:持續觸發 scroll 事件時,并不立即執行 handle 函數,當 1000 毫秒內沒有觸發 scroll 事件時,才會延時觸發一次 handle 函數。
function debounce(fn, wait) {
let 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))
默認情況下,瀏覽器是同步加載 js 腳本,解析 html 過程中,遇到 <script> 標簽就會停下來,等腳本下載、解析、執行完后,再繼續向下解析渲染。
如果 js 文件體積比較大,下載時間就會很長,容易造成瀏覽器堵塞,瀏覽器頁面會呈現出“白屏”效果,用戶會感覺瀏覽器“卡死了”,沒有響應。此時,我們可以讓 js 腳本異步加載、執行。
<script src="path/to/home.js" defer></script>
<script src="path/to/home.js" async></script>
上面代碼中,<script> 標簽分別有 defer 和 async 屬性,瀏覽器識別到這 2 個屬性時 js 就會異步加載。也就是說,瀏覽器不會等待這個腳本下載、執行完畢后再向后執行,而是直接繼續向后執行
defer 與 async 區別:
作者:lzg9527 鏈接:https://segmentfault.com/a/1190000023254297 來源:SegmentFault 思否 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。Java學習、面試;文檔、視頻資源免費獲取作者:lzg9527 鏈接:https://segmentfault.com/a/1190000023254297 來源:SegmentFault 思否 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
avaScript 語句
JavaScript 語句向瀏覽器發出的命令。語句的作用是告訴瀏覽器該做什么。
下面的 JavaScript 語句向 id="demo"的 HTML 元素輸出文本 "Hello World":
分號 ;
分號用于分隔 JavaScript 語句。
通常我們在每條可執行的語句結尾添加分號。
使用分號的另一用處是在一行中編寫多條語句。
提示:您也可能看到不帶有分號的案例。
在 JavaScript 中,用分號來結束語句是可選的。
JavaScript 代碼
JavaScript 代碼(或者只有 JavaScript)是 JavaScript 語句的序列。
瀏覽器會按照編寫順序來執行每條語句。
本例將操作兩個 HTML 元素:
document.getElementById("demo").innerHTML="Hello World"; document.getElementById("myDIV").innerHTML="How are you?";
JavaScript 代碼塊
JavaScript 語句通過代碼塊的形式進行組合。
塊由左花括號開始,由右花括號結束。
塊的作用是使語句序列一起執行。
JavaScript 函數是將語句組合在塊中的典型例子。
下面的例子將運行可操作兩個 HTML 元素的函數:
function myFunction() { document.getElementById("demo").innerHTML="Hello World"; document.getElementById("myDIV").innerHTML="How are you?"; }
JavaScript 對大小寫敏感。
JavaScript 對大小寫是敏感的。
當編寫 JavaScript 語句時,請留意是否關閉大小寫切換鍵。
函數 getElementById 與 getElementbyID 是不同的。
同樣,變量 myVariable 與 MyVariable 也是不同的。
空格
注:JavaScript 會忽略多余的空格。您可以向腳本添加空格,來提高其可讀性。下面的兩行代碼是等效的:
var name="Hello"; var name = "Hello";
對代碼行進行折行
您可以在文本字符串中使用反斜杠對代碼行進行換行。下面的例子會正確地顯示:
document.write("Hello \ World!"); //正確的的折行 document.write \ ("Hello World!"); //錯誤的折行
提示:JavaScript 是腳本語言。瀏覽器會在讀取代碼時,逐行地執行腳本代碼。而對于傳統編程來說,會在執行前對所有代碼進行編譯。
變量
變量是存儲信息的容器
變量可以使用短名稱(比如 x 和 y),也可以使用描述性更好的名稱(比如 age, sum, totalvolume)。
提示:JavaScript 語句和 JavaScript 變量都對大小寫敏感。
一個好的編程習慣是,在代碼開始處,統一對需要的變量進行聲明。
您可以在一條語句中聲明很多變量。該語句以 var 開頭,并使用逗號分隔變量即可:
var name="Gates", age=56, job="CEO"; //也可以多行 var name="Gates", age=56, job="CEO";
var carname;
var carname="Volvo"; var carname;
數據類型
字符串、數字、布爾、數組、對象、Null、Undefined
當您向變量分配文本值時,應該用雙引號或單引號包圍這個值。
當您向變量賦的值是數值時,不要使用引號。如果您用引號包圍數值,該值會被作為文本來處理。
1.字符串
字符串是存儲字符(比如 "Bill Gates")的變量。
字符串可以是引號中的任意文本。您可以使用單引號或雙引號:
var answer="Nice to meet you!";
var answer="He is called 'Bill'";
var answer='He is called "Bill"'; //內有引號的字符串
2.數字 數字可以帶小數點,也可以不帶;極大或極小的數字可以通過科學(指數)計數法來書寫:var z=123e-5;
3.布爾 布爾(邏輯)只能有兩個值:true 或 false。布爾常用在條件測試中
4.數組
var cars=new Array(); cars[0]="Audi"; cars[1]="BMW"; cars[2]="Volvo"; var cars=new Array("Audi","BMW","Volvo");//(condensed array) var cars=["Audi","BMW","Volvo"];//(literal array)
5.對象
對象由花括號分隔。在括號內部,對象的屬性以名稱和值對的形式 (name : value) 來定義。屬性由逗號分隔:
//person 對象有三個屬性 var person={firstname:"Bill", lastname:"Gates", id:5566};
6.Null
7.Undefined
JavaScript 擁有動態類型。這意味著相同的變量可用作不同的類型:
JavaScript共有八種數據類型,分別是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是ES6 中新增的數據類型:
這些數據可以分為原始數據類型和引用數據類型(復雜數據類型),他們在內存中的存儲方式不同。
Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。
null == undefined // true
null === undefined //false
instanceof運算符適用于檢測構造函數的prototype屬性上是否出現在某個實例對象的原型鏈上
instanceof 運算符的原理是基于原型鏈的查找。當使用 obj instanceof Constructor 進行判斷時,JavaScript 引擎會從 obj 的原型鏈上查找 Constructor.prototype 是否存在,如果存在則返回 true,否則繼續在原型鏈上查找。如果查找到原型鏈的頂端仍然沒有找到,則返回 false。
instanceof運算符只能用于檢查某個對象是否是某個構造函數的實例,不能用于基本類型的檢查,如string、number等
typeof與instanceof 都是判斷數據類型的方法,區別如下:
這是 JavaScript 語言的一個歷史遺留問題,在第一版JS代碼中用32位比特來存儲值,通過值的1-3位來識別類型,前三位為000表示對象類型。而null是一個空值,二進制表示都為0,所以前三位也就是000,所以導致 typeof null 返回 "object"
因為浮點數運算的精度問題。在計算機運行過程中,需要將數據轉化成二進制,然后再進行計算。 因為浮點數自身小數位數的限制而截斷的二進制在轉化為十進制,就變成0.30000000000000004,所以在計算時會產生誤差。
解決方案
let x=(0.1*10+0.2*10)/10;
console.log(x===0.3)
(n1 + n2).toFixed(2)
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
obj.__proto__ === Array.prototype;
Array.isArrray(obj);
obj instanceof Array
類數組也叫偽數組,類數組和數組類似,但不能調用數組方法,常見的類數組有arguments、通過document.getElements獲取到的內容等,這些類數組具有length屬性。
轉換方法
Array.prototype.slice.call(arrayLike)
Array.prototype.splice.call(arrayLike, 0)
Array.prototype.concat.apply([], arrayLike)
Array.from(arrayLike)
Array.propotype.slice.call()是什么 比如Array.prototype.slice.call(arguments)這句里,就是把 arguments 當做當前對象。
也就是說 要調用的是 arguments 的 slice 方法,而typeof arguments="Object" 而不是 Array
它沒有slice這個方法,通過這么Array.prototype.slice.call調用,JS的內部機制應該是 把arguments對象轉化為Array
它們都是字符串方法,用于截取字符串的一部分,主要區別在于參數不同
const str = "Hello, World!";
console.log(str.substring(0, 5)); // 輸出: "Hello"
console.log(str.substr(7, 5)); // 輸出: "World"
都是淺拷貝
new操作符用來創建一個對象,并將該對象綁定到構造函數的this上。
new操作符的執行過程:
「手寫代碼-實現一個new操作符」
for...in和for...of都是JavaScript中的循環語句,而for…of 是ES6新增的遍歷方式,允許遍歷一個含有iterator接口的數據結構(數組、對象等)并且返回各項的值,和ES3中的for…in的區別如下
總結:for...in 循環主要是為了遍歷對象而生,不適用于遍歷數組;for...of 循環可以用來遍歷數組、類數組對象,字符串、Set、Map 以及 Generator 對象。
為什么不能遍歷對象
for…of是作為ES6新增的遍歷方式,能被其遍歷的數據內部都有一個遍歷器iterator接口,而數組、字符串、Map、Set內部已經實現,普通對象內部沒有,所以在遍歷的時候會報錯。想要遍歷對象,可以給對象添加一個Symbol.iterator屬性,并指向一個迭代器即可
在迭代器里面,通過Object.keys獲取對象所有的key,然后遍歷返回key 、value。
var obj = {
a:1,
b:2,
c:3
};
obj[Symbol.iterator] = function*(){
var keys = Object.keys(obj);
for(var k of keys){
yield [k,obj[k]]
}
};
for(var [k,v] of obj){
console.log(k,v);
}
AJAX是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的 異步通信,從服務器獲取 XML 文檔從中提取數據,再更新當前網頁的對應部分,而不用刷新整個網頁。 創建AJAX請求的步驟:
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 創建 Http 請求
xhr.open("GET", url, true);
// 設置狀態監聽函數
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當請求成功時
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 設置請求失敗時的監聽函數
xhr.onerror = function() {
console.error(this.statusText);
};
// 設置請求頭信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 發送 Http 請求
xhr.send(null);
ajax
axios
fetch
兩個方法都是用來遍歷循環數組,區別如下:
尾調用就是在函數的最后一步調用函數,在一個函數里調用另外一個函數會保留當前執行的上下文,如果在函數尾部調用,因為已經是函數最后一步,所以這時可以不用保留當前的執行上下文,從而節省內存。但是ES6的尾調用只能在嚴格模式下開啟,正常模式是無效的。
「手寫代碼-手寫深拷貝」
「手寫代碼-手寫淺拷貝」
Set
Map
map和Object都是用鍵值對來存儲數據,區別如下:
它們是 JavaScript 中的兩種不同的鍵值對集合,主要區別如下:
Promise是異步編程的一種解決方案,將異步操作以同步操作的流程表達出來,避免了地獄回調。
Promise的實例有三個狀態:
Promise的實例有兩個過程:
Promise構造函數接收一個帶有resolve和reject參數的回調函數。
Promise的缺點:
Promise.all() 和 Promise.allSettled() 都是用來處理多個 Promise 實例的方法,它們的區別在于以下幾點:
async/await其實是Generator 的語法糖,它能實現的效果都能用then鏈來實現,它是為優化then鏈而開發出來的。通過async關鍵字聲明一個異步函數, await 用于等待一個異步方法執行完成,并且會阻塞執行。 async 函數返回的是一個 Promise 對象,如果在函數中 return 一個變量,async 會把這個直接量通過 Promise.resolve() 封裝成 Promise 對象。如果沒有返回值,返回 Promise.resolve(undefined)
// ES6 模塊
import { foo } from './module';
export const bar = 'bar';
// CommonJS 模塊
const foo = require('./commonjs');
exports.bar = 'bar';
每個實例對象都有一個__proto__屬性指向它的構造函數的原型對象,而這個原型對象也會有自己的原型對象,一層一層向上,直到頂級原型對象null,這樣就形成了一個原型鏈。
當訪問對象的一個屬性或方法時,當對象身上不存在該屬性方法時,就會沿著原型鏈向上查找,直到查找到該屬性方法位置。
原型鏈的頂層原型是Object.prototype,如果這里沒有就只指向null
利用Object.create()方法,將子類的原型指向父類,實現繼承父類的方法屬性,修改時也不影響父類。
function Parent(name) {
this.name = name;
this.colors = ['red', 'green', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
// 執行父類構造函數
Parent.call(this, name);
this.age = age;
}
// 將子類的原型 指向父類
Child.prototype = Object.create(Parent.prototype);
// 此時的狗早函數為父類的 需要指回自己
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
console.log(this.age);
};
var child1 = new Child('Tom', 18);
child1.sayName(); // 'Tom'
child1.sayAge(); // 18
閉包是指有權訪問另一個函數作用域中變量的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,創建的函數可以訪問到當前函數的局部變量。
閉包優點:
閉包缺點:
使用場景:
閉包并不一定會造成內存泄漏,如果在使用閉包后變量沒有及時銷毀,可能會造成內存泄漏的風險。只要合理的使用閉包,就不會造成內存泄漏。
作用域是一個變量或函數的可訪問范圍,作用域控制著變量或函數的可見性和生命周期。
作用域鏈: 變量在指定的作用域中沒有找到,會依次向一層作用域進行查找,直到全局作用域。這個查找的過程被稱為作用域鏈。
在 JavaScript 中,連續多次調用 bind 方法,最終函數的 this 上下文是由第一次調用 bind 方法的參數決定的
const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };
const obj3 = { name: 'obj3' };
function getName() {
console.log(this.name);
}
const fn1 = getName.bind(obj1).bind(obj2).bind(obj3);
fn1(); // 輸出 "obj1"
垃圾回收:JavaScript代碼運行時,需要分配內存空間來儲存變量和值。當變量不再參與運行時,就需要系統收回被占用的內存空間。如果不及時清理,會造成系統卡頓、內存溢出,這就是垃圾回收。
在 V8 中,會把堆分為新生代和老生代兩個區域,新生代中存放的是生存時間短的對象,老生代中存放生存時間久的對象:
副垃圾回收器主要負責新?代的垃圾回收。大多數的對象最開始都會被分配在新生代,該存儲空間相對較小,分為兩個空間:from 空間(對象區)和 to 空間(空閑區)。
主垃圾回收器主要負責??代中的垃圾回收。存儲一些占用空間大、存活時間長的數據,采用標記清除算法進行垃圾回收。
主要分為標記、清除兩個階段。
對?塊內存多次執?標記清除算法后,會產??量不連續的內存碎?。?碎?過多會導致?對象?法分配到?夠的連續內存,于是?引?了另外?種算法——標記整理。
標記整理的標記過程仍然與標記清除算法?的是?樣的,先標記可回收對象,但后續步驟不是直接對可回收對象進?清理,?是讓所有存活的對象都向?端移動,然后直接清理掉這?端之外的內存。
一個對象被引用一次,引用數就+1,反之就-1。當引用為0,就會出發垃圾回收。
這種方式會產生一個問題,在循環引用時,引用數永遠不會為0,無法回收。
作者:wakaka378
鏈接:https://juejin.cn/post/7270471613547249699
*請認真填寫需求信息,我們會在24小時內與您取得聯系。