天天看點

前端面試之JavaScript總結(作業幫一面)

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

前端面試之JavaScript總結(作業幫一面)

原型、構造函數、執行個體,以及原型鍊

前端面試之JavaScript總結(作業幫一面)

PS:任何一個函數,如果在前面加了new,那就是構造函數。

原型、構造函數、執行個體三者之間的關系

前端面試之JavaScript總結(作業幫一面)
  • 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

前端面試之JavaScript總結(作業幫一面)
  • 4、執行個體的

    __proto__

    指向原型。也就是說,

    foo.__proto__ === Foo.prototype

聲明:所有的引用類型(數組、對象、函數)都有

__proto__

這個屬性。

Foo.__proto__ === Function.prototype

的結果為true,說明Foo這個普通的函數,是Function構造函數的一個執行個體。

原型鍊

原型鍊的基本原理:任何一個執行個體,通過原型鍊,找到它上面的原型,該原型對象中的方法和屬性,可以被所有的原型執行個體共享。

Object是原型鍊的頂端。

原型可以起到繼承的作用。原型裡的方法都可以被不同的執行個體共享:

//給Foo的原型添加 say 函數
    Foo.prototype.say = function () {
        console.log('');
    }
           

原型鍊的關鍵:在通路一個執行個體的時候,如果執行個體本身沒找到此方法或屬性,就往原型上找。如果還是找不到,繼續往上一級的原型上找。

instanceof

的原理

前端面試之JavaScript總結(作業幫一面)

instanceof

的作用:用于判斷執行個體屬于哪個構造函數。

instanceof

的原理:判斷執行個體對象的

__proto__

屬性,和構造函數的

prototype

屬性,是否為同一個引用(是否指向同一個位址)。

注意1:雖然說,執行個體是由構造函數 new 出來的,但是執行個體的

__proto__

屬性引用的是構造函數的

prototype

。也就是說,執行個體的

__proto__

屬性與構造函數本身無關。

注意2:在原型鍊上,原型的上面可能還會有原型,以此類推往上走,繼續找

__proto__

屬性。這條鍊上如果能找到, instanceof 的傳回結果也是 true。

比如說:

  • foo instance of Foo

    的結果為true,因為

    foo.__proto__ === Foo.prototype

    為true。
  • foo instance of Objecet

    的結果也為true,因為

    Foo.prototype.__proto__ === Object.prototype

    為true。

但我們不能輕易的說:

foo 一定是 由Object建立的執行個體

。這句話是錯誤的。我們來看下一個問題就明白了。

例題

問題:已知A繼承了B,B繼承了C。怎麼判斷 a 是由A直接生成的執行個體,還是B直接生成的執行個體呢?還是C直接生成的執行個體呢?

分析:這就要用到原型的

constructor

屬性了。

  • foo.__proto__.constructor === Foo

    的結果為true,但是

    foo.__proto__.constructor === Object

    的結果為false。

是以,用 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());
           

列印結果:

前端面試之JavaScript總結(作業幫一面)

【重要】把

Parent

的執行個體指派給了

Child

prototye

,進而實作繼承。此時,

Child

構造函數、

Parent

的執行個體、

Child

的執行個體構成一個三角關系:

  • new Child.__proto__ === new Parent()

    的結果為true

**分析:**這種繼承方式,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. 将一個函數作為另一個函數的傳回值
    1. 将函數作為實參傳遞給另一個函數調用。

閉包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. 在函數外部能直接通路函數内部的局部變量嗎?

不能,但我們可以通過閉包讓外部操作它。

閉包的生命周期

  1. 産生: 嵌套内部函數fn2被聲明時就産生了(不是在調用)
  2. 死亡: 嵌套的内部函數成為垃圾對象時。(比如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 //讓内部函數成為垃圾對象–>回收閉包

繼續閱讀