0.javascript資料類型
變量有以下資料類型:
- 值類型(基本類型):字元串(String)、數字(Number)、布爾(Boolean)、對空(Null)、未定義(Undefined)、Symbol。
- 引用資料類型:對象(Object)、數組(Array)、函數(Function)。
堆記憶體和棧記憶體
- JS中,所有的變量都是儲存在棧記憶體中的。
- 基本數類型:值類型的值,直接儲存在棧記憶體中。值與值之間是獨立存在,修改一個變量不會影響其他的變量。
- 引用資料類型:對象是儲存到堆記憶體中的。每建立一個新的對象,就會在堆記憶體中開辟出一個新的空間,而變量儲存了對象的記憶體位址(對象的引用)。如果兩個變量儲存了同一個對象的引用,當一個通過一個變量修改屬性時,另一個也會受到影響。
1.數組方法
數組的四個基本方法如下:
方法 | 描述 | 備注 |
---|---|---|
push() | 向數組的最後面插入一個或多個元素,傳回結果為該數組新的長度 | 會改變原數組 |
pop() | 删除數組中的最後一個元素,傳回結果為被删除的元素 | 會改變原數組 |
unshift() | 在數組最前面插入一個或多個元素,傳回結果為該數組新的長度 | 會改變原數組 |
shift() | 删除數組中的第一個元素,傳回結果為被删除的元素 | 會改變原數組 |
周遊數組的方法如下:
方法 | 描述 | 備注 |
---|---|---|
for循環 | 這個大家都懂 | |
forEach() | 和 for循環類似,但需要相容IE8以上 | forEach() 沒有傳回值。也就是說,它的傳回值是 undefined |
filter() | 傳回結果是true的項,将組成新的數組。可以起到過濾的作用 | 不會改變原數組 |
map() | 對原數組中的每一項進行加工 | |
every() | 如果有一項傳回false,則停止周遊 | 意思是,要求每一項都傳回true,最終的結果才傳回true |
some() | 隻要有一項傳回true,則停止周遊 |
數組的常見方法如下:
方法 | 描述 | 備注 |
---|---|---|
slice() | 從數組中提取指定的一個或多個元素,傳回結果為新的數組 | 不會改變原數組 |
splice() | 從數組中删除指定的一個或多個元素,傳回結果為新的數組 | 會改變原數組 |
concat() | 連接配接兩個或多個數組,傳回結果為新的數組 | 不會改變原數組 |
join() | 将數組轉換為字元串,傳回結果為轉換後的字元串 | 不會改變原數組 |
reverse() | 反轉數組,傳回結果為反轉後的數組 | 會改變原數組 |
sort() | 對數組的元素,預設按照Unicode編碼,從小到大進行排序 | 會改變原數組 |
數組的其他方法如下:
方法 | 描述 | 備注 |
---|---|---|
indexOf(value) | 從前往後索引,擷取 value 在數組中的第一個下标 | |
lastIndexOf(value) | 從後往前索引,擷取 value 在數組中的最後一個下标 | |
find(function()) | 找出第一個滿足「指定條件傳回true」的元素。 | |
findIndex(function()) | 找出第一個滿足「指定條件傳回true」的元素的index | |
Array.from(arrayLike) | 将僞數組轉化為真數組 | |
Array.of(value1, value2, value3) | 将一系列值轉換成數組。 | |
isArray(obj) | 用于判斷一個對象是否為數組。 | |
toString() | 把數組轉換成字元串,每一項用 分割 | |
valueOf() | 傳回數組本身 | |
includes(searchElement, fromIndex) | 方法用來判斷一個數組是否包含一個指定的值,如果是傳回 true,否則false。 |
補充知識
- 數組的set集合内部無重複,(隻需要将最後轉化為數組即可array.from)
- reduce() 方法接收一個函數作為累加器,數組中的每個值(從左到右)開始縮減,最終計算為一個值。
數組去重
0.無數組方法原生
// 編寫一個方法 去掉一個數組的重複元素
var arr = [1,2,3,4,5,2,3,4];
console.log(arr);
var aaa = fn(arr);
console.log(aaa);
//思路:建立一個新數組,循環周遊,隻要新數組中有老數組的值,就不用再添加了。
function fn(array){
var newArr = [];
for(var i=0;i<array.length;i++){
//開閉原則
var bool = true;
//每次都要判斷新數組中是否有舊數組中的值。
for(var j=0;j<newArr.length;j++){
if(array[i] === newArr[j]){
bool = false;
}
}
if(bool){
newArr[newArr.length] = array[i];
}
}
return newArr;
}
一、利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
不考慮相容性,這種去重的方法代碼最少。這種方法還無法去掉“{}”空對象,後面的高階方法會添加去掉重複“{}”的方法。
二、利用for嵌套for,然後splice去重(ES5中最常用)
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一個等同于第二個,splice方法删除第二個
arr.splice(j,1);
j--;
}
}
}
return arr;
}
//NaN和{}沒有去重,兩個null直接消失了
雙層循環,外層循環元素,内層循環時比較值。值相同時,則删去這個值。
想快速學習更多常用的ES6文法,可以看我之前的文章《學習ES6筆記──工作中常用到的ES6文法》。
三、利用indexOf去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
//NaN、{}沒有去重
建立一個空的結果數組,for 循環原數組,判斷結果數組是否存在目前元素,如果有相同的值則跳過,不相同則push進數組。
四、利用sort()
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
//NaN、{}沒有去重
利用sort()排序方法,然後根據排序後的結果進行周遊及相鄰元素比對。
五、利用對象的屬性不能相同的特點進行去重(這種數組去重的方法有問題,不建議用,有待改進)
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var arrry= [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
arry.push(arr[i])
obj[arr[i]] = 1
} else {
obj[arr[i]]++
}
}
return arrry;
}
//兩個true直接去掉了,NaN和{}去重
六、利用includes
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 檢測數組是否有某個值
array.push(arr[i]);
}
}
return array
}
//{}沒有去重
七、利用hasOwnProperty
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
//所有的都去重了
利用hasOwnProperty 判斷是否存在對象屬性
八、利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//目前元素,在原始數組中的第一個索引==目前索引值,否則傳回目前元素
return arr.indexOf(item, 0) === index;
});
}
九、利用遞歸去重
function unique(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序後更加友善去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //遞歸loop,然後數組去重
}
}
loop(len-1);
return array;
}
十、利用Map資料結構去重
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 數組用于傳回結果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有該key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果沒有該key值
array .push(arr[i]);
}
}
return array ;
}
建立一個空Map資料結構,周遊需要去重的數組,把數組的每一個元素作為key存到Map中。由于Map中不會出現相同的key值,是以最終得到的就是去重後的結果。
十一、利用reduce+includes
function unique(arr){
return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}
十二、[…new Set(arr)]
[...new Set(arr)]
//代碼就是這麼少----(其實,嚴格來說并不算是一種,相對于第一種方法來說隻是簡化了代碼)
2.原型鍊
原型鍊是面向對象的基礎,是非常重要的部分。有以下幾種知識:
- 建立對象有幾種方法
- 原型、構造函數、執行個體、原型鍊
-
的原理instanceof
- new 運算符
建立對象有幾種方法
方式1:字面量
var obj11 = {name: 'qianguyihao'};
var obj12 = new Object(name: 'qianguyihao'); //内置對象(内置的構造函數)
上面的兩種寫法,效果是一樣的。因為,第一種寫法,
obj11
會指向
Object
。
- 第一種寫法是:字面量的方式。
- 第二種寫法是:内置的構造函數
方式2:通過構造函數
var M = function (name) {
this.name = name;
}
var obj3 = new M('smyhvae');
方法3:Object.create
var p = {name:'smyhvae'};
var obj3 = Object.create(p); //此方法建立的對象,是用原型鍊連接配接的
第三種方法,很少有人能說出來。這種方式裡,obj3是執行個體,p是obj3的原型(name是p原型裡的屬性),構造函數是
Objecet
。

原型、構造函數、執行個體,以及原型鍊
PS:任何一個函數,如果在前面加了new,那就是構造函數。
原型、構造函數、執行個體三者之間的關系
- 1、構造函數通過 new 生成執行個體
- 2、構造函數也是函數,構造函數的
指向原型。(所有的函數有prototype
屬性,但執行個體沒有prototype
屬性)prototype
- 3、原型對象中有 constructor,指向該原型的構造函數。
上面的三行,代碼示範:
var Foo = function (name) {
this.name = name;
}
var foo = new Foo('smyhvae');
上面的代碼中,
Foo.prototype.constructor === Foo
的結果是
true
:
- 4、執行個體的
指向原型。也就是說,__proto__
。foo.__proto__ === Foo.prototype
聲明:所有的引用類型(數組、對象、函數)都有
__proto__
這個屬性。
Foo.__proto__ === Function.prototype
的結果為true,說明Foo這個普通的函數,是Function構造函數的一個執行個體。
原型鍊
原型鍊的基本原理:任何一個執行個體,通過原型鍊,找到它上面的原型,該原型對象中的方法和屬性,可以被所有的原型執行個體共享。
Object是原型鍊的頂端。
原型可以起到繼承的作用。原型裡的方法都可以被不同的執行個體共享:
//給Foo的原型添加 say 函數
Foo.prototype.say = function () {
console.log('');
}
原型鍊的關鍵:在通路一個執行個體的時候,如果執行個體本身沒找到此方法或屬性,就往原型上找。如果還是找不到,繼續往上一級的原型上找。
instanceof
的原理
instanceof
instanceof
的作用:用于判斷執行個體屬于哪個構造函數。
instanceof
的原理:判斷執行個體對象的
__proto__
屬性,和構造函數的
prototype
屬性,是否為同一個引用(是否指向同一個位址)。
注意1:雖然說,執行個體是由構造函數 new 出來的,但是執行個體的
__proto__
屬性引用的是構造函數的
prototype
。也就是說,執行個體的
__proto__
屬性與構造函數本身無關。
注意2:在原型鍊上,原型的上面可能還會有原型,以此類推往上走,繼續找
__proto__
屬性。這條鍊上如果能找到, instanceof 的傳回結果也是 true。
比如說:
-
的結果為true,因為foo instance of Foo
為true。foo.__proto__ === Foo.prototype
-
的結果也為true,因為foo instance of Objecet
為true。Foo.prototype.__proto__ === Object.prototype
但我們不能輕易的說:
foo 一定是 由Object建立的執行個體
。這句話是錯誤的。我們來看下一個問題就明白了。
例題
問題:已知A繼承了B,B繼承了C。怎麼判斷 a 是由A直接生成的執行個體,還是B直接生成的執行個體呢?還是C直接生成的執行個體呢?
分析:這就要用到原型的
constructor
屬性了。
-
的結果為true,但是foo.__proto__.constructor === Foo
的結果為false。foo.__proto__.constructor === Object
是以,用 consturctor判斷就比用 instanceof判斷,更為嚴謹。
new 運算符
當new Foo()時發生了什麼:
(1)建立一個新的空對象執行個體。
(2)将此空對象的隐式原型指向其構造函數的顯示原型。
(3)執行構造函數(傳入相應的參數,如果沒有參數就不用傳),同時 this 指向這個新執行個體。
(4)如果傳回值是一個新對象,那麼直接傳回該對象;如果無傳回值或者傳回一個非對象值,那麼就将步驟(1)建立的對象傳回。
3.繼承的幾種方式
繼承的本質就是原型鍊。繼承的方式有幾種?每種形式的優缺點是?
方式1:借助構造函數
function Parent() {
this.name = 'Parent 的屬性';
}
function Child1() {
Parent1.call(this); //【重要】此處用 call 或 apply 都行:改變 this 的指向
this.type = 'child1 的屬性';
}
console.log(new Child1);
【重要】Parent1.call(this) 意思是:讓Parent的構造函數在child的構造函數中執行。改變this的指向,parent的執行個體 --> 改為指向child的執行個體。導緻 parent的執行個體的屬性挂在到了child的執行個體上,這就實作了繼承。
結果表明:child先有了 parent 執行個體的屬性(繼承得以實作),再有了child 執行個體的屬性。
分析:這種方式,雖然改變了 this 的指向,但是,Child1 無法繼承 Parent1 的原型。也就是說,如果我給 Parent1 的原型增加一個方法:這個方法是無法被 Child1 繼承的。
方法2:通過原型鍊實作繼承
/* 通過原型鍊實作繼承*/
function Parent2() {
this.name = 'Parent 的屬性';
}
function Child2() {
this.type = 'Child 的屬性';
}
Child2.prototype = new Parent2(); //【重要】
console.log(new Child2());
列印結果:
【重要】把
Parent
的執行個體指派給了
Child
的
prototye
,進而實作繼承。此時,
Child
構造函數、
Parent
的執行個體、
Child
的執行個體構成一個三角關系:
-
的結果為truenew Child.__proto__ === new Parent()
**分析:**這種繼承方式,Child 可以繼承 Parent 的原型,
缺點:如果修改 child1執行個體的name屬性,child2執行個體中的name屬性也會跟着改變。
造成這種缺點的原因是:child1和child2共用原型。即:
chi1d1.__proto__ === child2__proto__
是嚴格相同。而 arr方法是在 Parent 的執行個體上(即 Child執行個體的原型)的。
方式3:組合的方式:構造函數 + 原型鍊
就是把上面的兩種方式組合起來:
/*組合方式實作繼承:構造函數、原型鍊 */
function Parent3() {
this.name = 'Parent 的屬性';
this.arr = [1, 2, 3];
}
function Child3() {
Parent3.call(this); //【重要1】執行 parent方法,實作繼承的方式Father.call(this)
this.type = 'Child 的屬性';//parent3的執行個體屬性挂載到child上
}
Child3.prototype = new Parent3(); //【重要2】第二次執行parent方法
var child = new Child3();
這種方式,能解決之前兩種方式的問題:既可以繼承父類原型的内容,也不會造成原型裡屬性的修改。
這種方式的缺點是:讓父親Parent的構造方法執行了兩次。
方式4:prototype賦等
方式5:Object.create
Child5.prototype=Object.create(Parent5.prototype);
Child5.prototype.constructor=Child5
Object.create建立對象的方法,是用原型鍊對接
4.this指向問題
this指向
解析器在調用函數每次都會向函數内部傳遞進一個隐含的參數,這個隐含的參數就是this,this指向的是一個對象,這個對象我們稱為函數執行的 上下文對象。
根據函數的調用方式的不同,this會指向不同的對象:【重要】
- 1.以函數的形式調用時,this永遠都是window。比如
相當于fun();
window.fun();
- 2.以方法的形式調用時,this是調用方法的那個對象
- 3.以構造函數的形式調用時,this是新建立的那個對象
- 4.使用call()和apply()調用時,this是指定的那個對象
箭頭函數中this的指向:
ES6中的箭頭函數并不會使用上面四條标準的綁定規則,而是會繼承外層函數調用的this綁定(無論this綁定到什麼)。
call()和apply()
這兩個方法都是函數對象的方法,需要通過函數對象來調用。
當函數調用call()和apply()時,函數都會立即執行。
- 改變this的指向
- 實作繼承。Father.call(this)
- 顯示綁定:第一個參數都是this要指向的對象(函數執行時,this将指向這個對象),後續參數用來傳實參。
第一個參數的傳遞
1、thisObj不傳或者為null、undefined時,函數中的this會指向window對象(非嚴格模式)。
2、傳遞一個别的函數名時,函數中的this将指向這個函數的引用。
3、傳遞的值為數字、布爾值、字元串時,this會指向這些基本類型的包裝對象Number、Boolean、String。
4、傳遞一個對象時,函數中的this則指向傳遞的這個對象。
call()和apply()的差別
call()和apply()方法都可以将實參在對象之後依次傳遞,但是apply()方法需要将實參封裝到一個數組中統一傳遞(即使隻有實參隻有一個,也要放到數組中)。
如果是通過call的參數進行傳參,是這樣的:
如果是通過apply的參數進行傳參,是這樣的:
bind()
- 都能改變this的指向
- call()/apply()是立即調用函數
- bind()是将函數傳回,是以後面還需要加
才能調用。()
bind 方法不會立即執行,而是傳回一個改變了上下文 this 後的函數。而原函數 func 中的 this 并沒有被改變,依舊指向全局對象 window。
使用方法:
function func(a, b, c) {
console.log(a, b, c);
}
var func1 = func.bind(null,'zoexyf');
func('A', 'B', 'C'); // A B C
func1('A', 'B', 'C'); // zoexyf A B
func1('B', 'C'); // zoexyf B C
func.call(null, 'zoexyf'); // zoexyf undefined undefined
在低版本浏覽器沒有 bind 方法,我們也可以自己實作一個。
手寫bind方法實作(重要)
if (!Function.prototype.bind) {
Function.prototype.bind = function () {
var self = this, // 儲存原函數
context = [].shift.call(arguments), // 儲存需要綁定的this上下文
args = [].slice.call(arguments); // 剩餘的參數轉為數組
return function () { // 傳回一個新函數
self.apply(context,[].concat.call(args, [].slice.call(arguments)));
}
}
}
5.閉包
閉包了解
閉包概念
閉包就是能夠讀取其他函數内部資料(變量/函數)的函數。
隻有函數内部的子函數才能讀取局部變量,是以可以把閉包簡單了解成"定義在一個函數内部的函數"。
當一個嵌套的内部(子)函數引用了嵌套的外部(父)函數的變量或函數時, 就産生了閉包。
- 閉包是嵌套的内部函數 ,包含被引用外部變量 or 函數的對象
産生閉包的條件
1.函數嵌套
2.内部函數引用了外部函數的資料(變量/函數)。
一種情況:外部函數被調用,内部函數被聲明。比如:
- 内部函數被提前聲明,就會産生閉包(不用調用内部函數)
- 采用的是“函數表達式”建立的函數,此時内部函數的聲明并沒有提前
常見的閉包
-
- 将一個函數作為另一個函數的傳回值
-
- 将函數作為實參傳遞給另一個函數調用。
閉包1:函數作為另一函數的傳回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1(); //執行外部函數fn1,傳回的是内部函數fn2
f() // 3 //執行fn2
f() // 4 //再次執行fn2
當f()第二次執行的時候,a加1了,也就說明了:閉包裡的資料沒有消失,而是儲存在了記憶體中。如果沒有閉包,代碼執行完倒數第三行後,變量a就消失了。
上面的代碼中,雖然調用了内部函數兩次,但是,閉包對象隻建立了一個。
也就是說,要看閉包對象建立了一個,就看:外部函數執行了幾次(與内部函數執行幾次無關)。
閉包2. 函數作為實參傳給另一函數調用
function showDelay(msg, time) {
setTimeout(function() { alert(msg) }, time)
}
showDelay('atguigu', 2000)
上面的代碼中,閉包是裡面的funciton,因為它是嵌套的子函數,而且引用了外部函數的變量msg。
閉包的作用
- 作用1. 使用函數内部的變量在函數執行完後, 仍然存活在記憶體中(延長了局部變量的生命周期)
- 作用2. 讓函數外部可以操作(讀寫)到函數内部的資料(變量/函數)
回答幾個問題:
- 問題1. 函數執行完後, 函數内部聲明的局部變量是否還存在?
答案:一般是不存在, 存在于閉包中的變量才可能存在。
閉包能夠一直存在的根本原因是
f
(執行個體),因為
f
接收了
fn1()
,這個是閉包,閉包裡有a。注意,此時,fn2并不存在了,但是裡面的對象(即閉包)依然存在,因為用
f
接收了。
- 問題2. 在函數外部能直接通路函數内部的局部變量嗎?
不能,但我們可以通過閉包讓外部操作它。
閉包的生命周期
- 産生: 嵌套内部函數fn2被聲明時就産生了(不是在調用)
- 死亡: 嵌套的内部函數成為垃圾對象時。(比如f = null,就可以讓f成為垃圾對象。意思是,此時f不再引用閉包這個對象了)
閉包的應用:定義具有特定功能的js子產品
- 将所有的資料和功能都封裝在一個函數内部(私有的),隻向外暴露一個包含n個方法的對象或函數。
- 子產品的使用者, 隻需要通過子產品暴露的對象調用方法來實作對應的功能。
方式1:向外暴露含多個函數的對象
(1)myModule.js:(定義一個子產品,向外暴露多個函數,供外界調用)
function myModule() {
//私有資料
var msg = 'Zoexyf Haha'
//操作私有資料的函數
function doSomething() {
console.log('doSomething() ' + msg.toUpperCase()); //字元串大寫
}
function doOtherthing() {
console.log('doOtherthing() ' + msg.toLowerCase()) //字元串小寫
}
//通過【對象字面量】的形式進行包裹,向外暴露多個函數
return {
doSomething1: doSomething,
doOtherthing2: doOtherthing
}
}
上方代碼中,外界可以通過doSomething1和doOtherthing2來操作裡面的資料,但不讓外界看到。
(2)index.html:
<!--閉包的應用 : 定義JS子產品
* 具有特定功能的js檔案
* 将所有的資料和功能都封裝在一個函數内部(私有的)
* 【重要】隻向外暴露一個包含n個方法的對象或函數
* 子產品的使用者, 隻需要通過子產品暴露的對象調用方法來實作對應的功能-->
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
var module = myModule();
module.doSomething1();
module.doOtherthing2();
</script>
方式2:匿名函數直接傳給window對象
同樣是實作方式一種的功能,這裡我們采取另外一種方式。
(1)myModule2.js:(是一個立即執行的匿名函數)
(function () {
var msg = 'Zoexyf Haha' //私有資料
//操作私有資料的函數
function doSomething() {
console.log('doSomething() ' + msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing() ' + msg.toLowerCase())
}
//外部函數是即使運作的匿名函數,我們可以把兩個方法直接傳給window對象
window.myModule = {
doSomething1: doSomething,
doOtherthing2: doOtherthing
}
})()
(2)index.html:
<!--閉包的應用2 : 定義JS子產品
* 具有特定功能的js檔案
* 将所有的資料和功能都封裝在一個函數内部(私有的)
* 隻向外暴露一個包信n個方法的對象或函數
* 子產品的使用者, 隻需要通過子產品暴露的對象調用方法來實作對應的功能-->
<!--引入myModule檔案-->
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
myModule.doSomething1()
myModule.doOtherthing2()
</script>
上方兩個檔案中,我們在
myModule2.js
裡直接把兩個方法直接傳遞給window對象了。于是,在index.html中引入這個js檔案後,會立即執行裡面的匿名函數。在index.html中把myModule直接拿來用即可。
匿名函數更友善
閉包的缺點及解決
缺點:函數執行完後, 函數内的局部變量沒有釋放,占用記憶體時間會變長,容易造成記憶體洩露。
解決:能不用閉包就不用,及時釋放。比如:
總而言之,你需要它,就是優點;你不需要它,就成了缺點。
記憶體溢出和記憶體洩露
記憶體溢出:一種程式運作出現的錯誤。當程式運作需要的記憶體超過了剩餘的記憶體時, 就出抛出記憶體溢出的錯誤。
記憶體洩漏:占用的記憶體沒有及時釋放。
注意,記憶體洩露的次數積累多了,就容易導緻記憶體溢出。
常見的記憶體洩露:
- 1.意外的全局變量
- 2.沒有及時清理的計時器或回調函數—>clearInterval()
- 3.閉包—>f = null //讓内部函數成為垃圾對象–>回收閉包
個函數内部(私有的)
- 隻向外暴露一個包信n個方法的對象或函數
- 子產品的使用者, 隻需要通過子產品暴露的對象調用方法來實作對應的功能–>
上方兩個檔案中,我們在`myModule2.js`裡直接把兩個方法直接傳遞給window對象了。于是,在index.html中引入這個js檔案後,會立即執行裡面的匿名函數。在index.html中把myModule直接拿來用即可。
匿名函數更友善
### 閉包的缺點及解決
缺點:函數執行完後, 函數内的局部變量沒有釋放,占用記憶體時間會變長,容易造成記憶體洩露。
解決:能不用閉包就不用,及時釋放。比如:
```javascript
f = null; // 讓内部函數成為垃圾對象 -->回收閉包
總而言之,你需要它,就是優點;你不需要它,就成了缺點。
記憶體溢出和記憶體洩露
記憶體溢出:一種程式運作出現的錯誤。當程式運作需要的記憶體超過了剩餘的記憶體時, 就出抛出記憶體溢出的錯誤。
記憶體洩漏:占用的記憶體沒有及時釋放。
注意,記憶體洩露的次數積累多了,就容易導緻記憶體溢出。
常見的記憶體洩露:
- 1.意外的全局變量
- 2.沒有及時清理的計時器或回調函數—>clearInterval()
- 3.閉包—>f = null //讓内部函數成為垃圾對象–>回收閉包