【進階】javascript

做用域

  • let\const 聲明的變量,在塊級做用域中可見
  • var聲明的變量,在函數做用域中可見
  • 最外層是全局做用域,node環境中全局對象爲global,瀏覽器環境中全局對象爲window
  • 做用域鏈,噹噹前做用域內查找不到同名變量時,會去外層做用域中查找
  • 函數做用域是靜態做用域(詞法做用域),也就是說函數做用域的嵌套父子關係是在函數定義的時候,就肯定了,而不是在函數調用的時候才肯定
var scope = 'top';
var f1 = function() { 
    console.log(scope);
};
f1(); // 輸出 top
var f2 = function() { 
    var scope = 'f2'; 
    f1();
};
f2(); // 輸出 top
複製代碼

閉包

當一個函數返回它內部定義的一個函數時,就產生了一個閉包, 閉包不但包括被返回的函數,還包括這個函數的定義環境node

var generateClosure = function() { 
    var count = 0;
    var get = function() {
        count ++;
        return count; 
    };
    return get; 
};
var counter1 = generateClosure(); 
var counter2 = generateClosure(); 
console.log(counter1()); // 輸出 1 
console.log(counter2()); // 輸出 1 
console.log(counter1()); // 輸出 2 
console.log(counter1()); // 輸出 3 
console.log(counter2()); // 輸出 2
複製代碼

上面這個例子解釋了閉包是如何產生的:counter1 和 counter2 分別調用了 generate- Closure() 函數,生成了兩個閉包的實例,它們內部引用的 count 變量分別屬於各自的 運行環境。咱們能夠理解爲,在 generateClosure() 返回 get 函數時,私下將 get 可 能引用到的 generateClosure() 函數的內部變量(也就是 count 變量)也返回了,並在內存中生成了一個副本,以後 generateClosure() 返回的函數的兩個實例 counter1 和 counter2 就是相互獨立的了。數組

閉包的用途:實現對象的私有成員

var generateClosure = function() { 
    var count = 0; //私有變量
    var get = function() {
        count ++;
        return count; 
        };
    return get;
};
var counter = generateClosure(); 
console.log(counter()); // 輸出 1 
console.log(counter()); // 輸出 2 
console.log(counter()); // 輸出 3

複製代碼

在函數中經過返回一個子函數,對外部暴露一個訪問和更新內部變量的接口,外部不能直接訪問函數做用域內的變量,完成了對變量私有化的隱藏瀏覽器

對象

訪問對象屬性成員方式

  • 點運算符(obj.a)
  • 關聯數組(obj['a']),使用關聯數組的優點是可使用變量做爲索引,這對於咱們前期不知道對象屬性key的時候很是有用

上下文對象this

  • 上下文對象就是 this 指針,即被調用函數所處的環境閉包

  • JavaScript 的任何函數都是被某個對象調用的,在最外部全局做用域中,調用它的是全局對象app

  • this 指針不屬於某個函數,而是函數調用時所屬的對象。也就是說誰調用的函數,this就指向誰函數

    var someuser = { 
        name: 'Tom', 
        func: function() {
          console.log(this.name); 
          }
      }; 
      var foo = { 
          name: 'foobar'
      };
      someuser.func(); // 輸出 Tom
    
      foo.func = someuser.func; 
      foo.func(); // 輸出 foobar
    
      name = 'global';
      func = someuser.func; 
      func(); // 輸出 global
    複製代碼

    在 JavaScript 中,本質上,函數類型的變量是指向這個函數實體的一個引用,在引用之間賦值不會對對象產生複製行爲。咱們能夠經過函數的任何一個引用調用這個函數,不一樣之處僅僅在於上下文ui

    • 改變函數中上下文對象的方式:
      • call,以依次列舉參數形式,傳遞給被調函數參數 func.call(thisArg[, arg1[, arg2[, ...]]]),返回被調函數執行結果
      • apply,以傳遞數組的形式,傳遞給被調函數參數 func.apply(thisArg[, argsArray]),返回被調函數執行結果
      • bind,func.bind(thisArg[, arg1[, arg2[, ...]]]) 返回綁定新的上下文對象的函數,之後調用時都是固定綁定的上下文對象,會重複調用時適合使用bind方法

原型

使用原型和構造函數初始化對象屬性的區別:this

//原型方式
function Person() {
}
Person.prototype.name = 'Tom'; 
Person.prototype.showName = function () {
    console.log(this.name); 
};
var person = new Person(); 
person.showName();

//構造函數方式
function Person(){
    this.name = 'Tom';
    this.showNmae = function(){
        console.log(this.name);
    }
}
var person = new Person(); 
person.showName();
複製代碼
  1. 繼承方式不一樣:原型方式,子對象經過原型鏈能夠直接訪問父對象的屬性;構造函數內定義的屬性,子對象必須顯式調用父對象才能訪問
  2. 原型定義的屬性和方法是子對象實例共用一套,減小重複開銷;構造函數內定義的屬性,每次新建一個對象實例,都會在內存中從新建立一套,實例之間不共享
  3. 對象的成員函數最好使用原型方式定義,能夠減小內存開銷;定義在構造函數內部,多個對象會重複建立,同時可能會有運行時閉包開銷
  4. 通常的變量屬性成員,在構造函數中定義,保證每一個實例對象數據的獨立性,由於在原型上定義意味着全部實例共享這一個,任何對象的主動更改會影響到其餘實例對象

原型鏈

  1. JavaScript 中有兩個特殊的對象: Object 與 Function,它們都是構造函數,用於生 成對象
  2. Object.prototype 是全部對象的祖先,Function.prototype 是全部函數的原型,包括構造函數
  3. JavaScript 中的對象分爲三類,一類是用戶建立的對象,一類是構造函數對象,一類是原型對象
    • 用戶建立的對象,即通常意義上用 new 語句顯式構造得到的對象
    • 構造函數對象指的是普通的構造函數,即經過 new 調用生成普通對象的那個函數
    • 原型對象 特指構造函數 prototype 屬性指向的對象
  4. 這三類對象中每一類都有一個 proto 屬 性,它指向該對象的原型,從任何對象沿着它開始遍歷均可以追溯到 Object.prototype
  5. 構造函數對象有 prototype 屬性,指向原型對象;經過該構造函數建立對象時,被建立的普通對象的 proto 屬性將會指向構造函數的 prototype 屬性,也就是該普通對象的原型對象
  6. 原型對象有 constructor 屬性,指向它對應的構造函數
function Foo() {
}
Object.prototype.name = 'My Object'; Foo.prototype.name = 'Bar';
var obj = new Object();
var foo = new Foo();
console.log(obj.name); // 輸出 My Object
console.log(foo.name); // 輸出 Bar
console.log(foo.__proto__.name); // 輸出 Bar 
console.log(foo.__proto__.__proto__.name); // 輸出 My Object 
console.log(foo. __proto__.constructor.prototype.name); // 輸出 Bar
複製代碼

  1. 在 JavaScript 中,繼承是依靠一套叫作原型鏈(prototype chain)的機制實現的。
  2. 屬性 繼承的本質就是一個對象能夠訪問到它的原型鏈上任何一個原型對象的屬性

原型的深拷貝

平常開發中,若是隻須要複製基本的屬性時(基本類型、對象、數組等,不包含函數與特殊對象new Date()、正則等),使用JSON序列化再反序列化的方法是最便捷的方式spa

JSON.parse(JSON.stringify(obj))
複製代碼

若是想要支持成員函數的拷貝:

Object.prototype.clone = function() { 
    var newObj = {};
    for (var i in this) {
    if (typeof(this[i]) == 'object' || typeof(this[i]) == 'function') { 
        newObj[i] = this[i].clone();
    } else {
        newObj[i] = this[i];
    } }
    return newObj; 
};

Array.prototype.clone = function() {
    var newArray = [];
    for (var i = 0; i < this.length; i++) {
    if (typeof(this[i]) == 'object' || typeof(this[i]) == 'function') { 
        newArray[i] = this[i].clone();
    } else {
        newArray[i] = this[i];
    } }
    return newArray;
}
Function.prototype.clone = function () {
  var that = this;
  var newFun = function newFun() {
    return that.apply(this, arguments); //這裏構成了閉包
  };
  for (var i in this) {
    newFun[i] = this[i];
  }
  return newFun;
}
複製代碼

循環引用的深拷貝

  • 使用JSON序列化反序列化會報錯
  • 使用上面常規的遞歸方式會出現死循環,最後棧溢出

解決方案:引入WeakMap結構

WeakMap對象是一組鍵/值對的集合,其中的鍵是弱引用的。其鍵必須是對象,而值能夠是任意的 遍歷對象時使用WeakMap結構存儲,遇到循環引用的對象,經過WeakMap.prototype.has(key)與WeakMap.prototype.get(key)來終止循環遍歷問題prototype

function isObj(obj) {
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}

function deepCopy(obj, mapObj = new WeakMap()) {
    if(mapObj.has(obj)) return mapObj.get(obj)
    let cloneObj = Array.isArray(obj) ? [] : {}
    mapObj.set(obj, cloneObj)
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], mapObj) : obj[key];
    }
    return cloneObj
}

複製代碼
相關文章
相關標籤/搜索