重學前端(7)詞法做用域,原型鏈

基礎語法

目標

  • 知道函數有原型對象javascript

  • 構造函數,原型對象和實例三者的關係java

  • 原型鏈面試

  • 會給內置的對象添加自定義的方法數組

  • 會使用更簡單的原型使用方式瀏覽器

1. 做用域問題

1.1 做用域:

####1.1.1 全局做用域函數

整個js執行的環境就是一個全局做用域ui

####1.1.2 局部做用域this

es5規範中: 只有函數才能構成一個局部做用域es5

####1.1.3 做用域鏈spa

將js執行時變量查找的方式,以鏈式形式表示出來

var num = 0;
function fn(){
    var num1;
    num1 = 1;
    console.log(num1);
    function fnSon(){
        var num2 = 2;
        console.log(2)
    }
}
複製代碼

將上面的代碼用鏈式的形式展現出來

1.2 詞法做用域規則

詞法做用域又叫靜態做用域.

  • 做用域是在代碼書寫完畢以後就造成了,與代碼執行無關

  • 內部做用域能夠訪問外部做用域的變量,可是外部不能夠訪問內部的

  • 函數的形參就至關於在當前函數的做用域中申明瞭這個變量

  • 訪問變量時,先在本身的做用域中查找,若是沒有則沿着做用域鏈往上找,直到全局.若是全局也沒有就報錯

  • 給變量賦值以前,要先找變量.查找變量也是沿着做用域鏈查找,直到全局,若是全局也沒有,則會再全局做用域建立這個變量(隱式全局)

  • 代碼執行以前先考慮預解析規則,調用函數時,執行函數裏的代碼以前,函數內也要先執行預解析規則

###面試題:

var a;
if ("a" in window) {
    var a = 10;
}
alert(a);  //
//=============================================
var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo); //
}
 bar();
//================================================
var num = 123;
function f1(num) {
    console.log(num); // 
}
function f2() {

    var num = 456;
    f1(num);
}
f2();

//======================================================
 function fn(){
   var a = 1, b = 1, c = 1;
 }
 fn();
 console.log(c); //
 console.log(b); //
 console.log(a); //
  
 function fn1(){
   var a = b = c = 1;
 }
 fn1();
 console.log(c); //
 console.log(b); //
 console.log(a); //
//========================================================
var a = 1;
function fn(){
    var a = 2;
    function fnSon(a){
        a = 3;
        console.log(a); //
    }
    fnSon();
    console.log(a);  // 
}
console.log(a);  // 
fn();
console.log(a); // 

//==========================================================
var a ;
function a(){
    console.log('呵呵')
    function a(){
        a = 4;
        console.log('哈哈')
    }
    a();
    console.log(a);  //
}
a();
console.log(a); //
//=================================================================
var a = 1;
function a(){
    a++;
}
console.log(a) //

//==================================================================
 var a = { x : 1 }
 var b = a;
 a.x = a = { n : 1};
 console.log(a.x); //
 console.log(b.x); //
 
 //本身把代碼運行下,看看和本身想的結果有啥差異
複製代碼

###1.3. 建立對象的方式

####1.3.1 簡單方式

咱們能夠直接經過 new Object() 建立:

var person = new Object()
person.name = 'Jack';
person.age = 18;
person.sayName = function () {
  console.log(this.name);   //jack
}
複製代碼

每次建立經過 new Object() 比較麻煩,因此能夠經過它的簡寫形式對象字面量來建立:

字面量形式的建立方式,底層也是new Object建立出來的

var person = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name);
  }
}
複製代碼

上面的寫法比較簡單,可是若是咱們要建立多個person對象呢?

var person1 = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name);
  }
}

var person2 = {
  name: 'Mike',
  age: 16,
  sayName: function () {
    console.log(this.name);
  }
}

var person3 = {
  name: 'zs',
  age: 17,
  sayName: function () {
    console.log(this.name);
  }
}
複製代碼

經過上面的代碼咱們不難看出,這樣寫的代碼太過冗餘。

####1.3.2 簡單方式的改進:工廠函數

咱們能夠寫一個函數,解決代碼重複問題:

function createPerson (name, age) {
  return {
    name: name,
    age: age,
    sayName: function () {
      console.log(this.name);
    }
  }
}
複製代碼

而後生成對象:

var p1 = createPerson('Jack', 18);
p1.sayName(); //jack
var p2 = createPerson('Mike', 18);
p2.sayName(); // Mike
複製代碼

####1.3.3 更優雅的方式(更推薦使用的一種方式):構造函數

一種更優雅的工廠函數就是下面這樣,構造函數:

function Person (name, age) {
  this.name = name;
  this.age = age;
  this.sayName = function () {
    console.log(this.name);
  }
}

var p1 = new Person('Jack', 18);
p1.sayName() // => Jack

var p2 = new Person('Mike', 23);
p2.sayName() // => Mike
複製代碼

**構造函數中new關鍵字作什麼事在上一篇博客裏寫的很詳細有須要能夠看看

2. 構造函數的問題

使用構造函數帶來的最大的好處就是建立對象更方便了,可是其自己也存在一個浪費內存的問題:

function Person (name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function () {
    console.log('hello ' + this.name);
  }
}
var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 16);

複製代碼

以上代碼的圖示:

經過上面的圖示,咱們發現,每個對象都引用了一個函數,咱們建立了多少個對象,對應的就會在內存中建立出對應數量的同樣的函數.這樣形成了內存的極大浪費

3 解決構造函數浪費內存的方法:

3.1 把對象的行爲定義在構造函數外面

function Person (name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = say;
}
  
function say (){
   console.log('hello ' + this.name);
}
    
var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 16);
p1.sayHello(); // hello Tom
p2.sayHello(); // hello Jack
複製代碼

**注意: ** 這種方式,能夠解決構造函數浪費內存的問題,可是,同時又出現了一個新的問題,咱們把函數定義在了全局,

全局的函數,很容易被別人寫的代碼覆蓋.

###3.2 利用函數的原型對象(更優雅的解決方案)

js給每個函數,提供了一個對應的原型對象.能夠經過函數的prototype屬性訪問到這個原型對象.

原型對象有一個constructor的屬性會指向本身對應的函數

而咱們經過 new 函數 建立出來的實例對象,默承認以訪問到函數對應的原型對象上的屬性

function Person (name, age) {
  this.name = name;
  this.age = age;
}
  
Person.prototype.sayName = function (){
   console.log('hello ' + this.name);
}
    
var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 16);
p1.sayHello(); // hello Tom
p2.sayHello(); // hello Jack
複製代碼

**注意: **爲了咱們方便查看,實例和原型的關係.瀏覽器很貼心的幫咱們實現了一個屬性 __proto__, 經過這個屬性,咱們能夠在控制檯上清楚的看到原型.可是 __proto__不是w3c標準的屬性,因此不要在生產環境(上線)下使用.

function Person (name, age) {
  this.name = name;
  this.age = age;
}

var p1 = new Person();

console.log(Perosn.prototype === p1.__proto__) //true
複製代碼

3.3 小結:

  • 利用原型對象,能夠更加優雅的解決構造函數浪費內存的問題

  • 通常對象私有的屬性直接寫在構造函數中,而對象公有的屬性寫在原型對象上

  • 函數對應有一個本身的原型對象 , 經過prototype屬性能夠訪問

  • 原型對象有一個constructor屬性,能夠指回本身的對應的函數

  • 經過函數new出來的實例,默承認以訪問到原型對象的屬性 ,咱們能夠經過__proto__在控制檯看到

3. 原型鏈

原型對象也是對象,那麼這個對象的是被誰建立出來的呢?

function Person (name, age) {
  this.name = name;
  this.age = age;
}

var p1 = new Person();

console.log(Person.prototype) // 指向Person的原型對象
console.log(Person.prototype.__proto__.constructor) // 咱們能夠看到Person的原型對象是Object的實例
複製代碼

4. js中對象屬性的查找規則

由於這個查找規則,因此Object函數原型對象上的全部屬性均可以被其餘對象訪問到

  • 訪問對象的屬性時,先在對象本身身上找,找到就直接返回
  • 若是對象的身上沒有這個屬性,就會往原型上面找,若是找 到就直接返回
  • 若是原型上也沒有,就往原型的原型上面找(沿着原型鏈一直往上找),找到就當即返回
  • 若是最終都沒有找到,則返回undefined
function Student(){}
var s1 = new Student();
s1.toString()  //[object Object] 
複製代碼

5. 內置對象的原型

  • Array.prototype

  • String.prototype

    ...

    經過觀察內置函數的原型,咱們發現咱們在數組/字符串經常使用的API,其實都定義在他們對應的原型上的.因此全部的數組對象/字符串,都能使用這些API.

##6. 更簡單的原型使用方式

若是咱們有不少公用的屬性,那麼一個一個的添加在函數的原型上就比較麻煩,咱們還能夠有一種更簡單的方式

直接新建一個對象賦值給函數的prototype屬性

function Person (name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype = {
  constructor: Person, // => 手動定義一個 constructor 屬性, 指向正確的構造函數
  sayHello: function () {
    console.log('我叫' + this.name + ',我今年' + this.age + '歲了');
  }
  eat : function(){
    console.log('吃飯ing...');
  }
}

var p1 = new Person('zs', 18);
複製代碼

相關文章
相關標籤/搜索