1. 程式人生 > >js閉包與原型鏈

js閉包與原型鏈

js constructor 和prototype

深入理解js後這兩個核心概念非常重要

我們在定義函式的時候,函式本身就會預設有一個prototype的屬性,而我們如果用new運算子來生成一個物件的時候就沒有prototpye屬性。

!function a(){

  varo = new Object();

 function b(){

  }

 return

}()

var a = new a();

js 原型物件與原型鏈

原型物件

  javascript 物件都有一個原型物件,在不同的直譯器下的實現不同。比如firefox下,每個物件都有一個隱藏的__proto__屬性,這就是

‘原型’物件的引用。

原型鏈

由於原型物件的本身也是物件,它也有自己的原型,而它的原型物件又可以有自己的原型,這樣就組成了一條鏈,就是原型鏈,在訪問物件的屬性時,如果在物件本身中沒有找到,則回去原型鏈中查詢,如果找到返回值,否則返回Undefined

var base {

  name : “base”,

  getInfo : function (){

     return this.name;

  }

}

 varext1 = {

   id: 0,

  __proto__ : base

}

var ext2 = {

   id: 9,

  __proto__ : base

}

print(ext1.id); 

print(ext1.getInfo()); 

print(ext2.id); 

print(ext2.getInfo());

輸出:0

       base

       9

       base

可以看到,如果自身找到了屬性就回輸出,如果找不到屬性就回去子項的原型中去找

js中關鍵詞 in 的使用方法:

除了 for .. in 迴圈陣列或集合之外

in當‘物件’是陣列時,‘變數’指引索引

當‘物件’是物件時,‘變數’指引屬性

vararr =["a","b","2","3","str"];

"b"in arr

 4  in arr  

輸出: false

            true

varobj={ 

         w:"wen", 

         j:"jian", 

         b:"bao"

        }

 2   in obj

"bao"in obj

輸出: false

       true

閉包的概念:

通過閉包可以使我們的程式碼更加優雅,更加簡潔,在某些方面可以提升程式碼的效率

1.匿名自執行函式

變數如果不加var 會預設新增到全域性變數中,這些臨時變數如果誤用會導致全域性物件過於龐大,影響訪問速度(因為變數取值是需要從原型鏈上取的)

  var datamodel = {   

    table : [],   

    tree : {}   

};   

(function(dm){   

    for(var i = 0; i < dm.rows.length;i++){   

       var row = dm.rows[i];   

       for(var j = 0; j < row.cells.length;i++){   

           drawCell(i, j);   

       }   

    }   

    //build dm.tree     

})(datamodel); 

這個函式由於外部無法直接引用它的內部變數所以執行完後很快就會被釋放

2.快取

假設有一個很耗時間的函式物件,每次呼叫都會花費很多時間;

那麼我們就需要將計算的值儲存起來,當呼叫這個函式的時候,首先從快取中取出,如果找不到再進行計算

閉包正是可以做到這一點,因為它不會釋放外部引用,從而函式值得到保留

 var CachedSearchBox = (function(){

   var cache = {},

       count = [];

   return {

      attachSearchBox : function(dsid){

         if(dsid in cache) return cache[dsid];

        var fsb = newuikit.webctrl.SearchBox(dsid);//新建

         cache[dsid] = fsb;

         return fsb;

      },

     clearSearchBox : function(dsid){

         if(dsid in cache)

           cache[dsid].clearSelection();

     }

   }

 })

3.實現封裝

  var person = function(){

     var name = "default";

     return {

       getName : function (){

         return name;

       },

       setName : function (newName){

          name = newName;

       }

     }

  }();

print(person.name)//undefined

print(person.getName())//default

person.setName("hello");

print(person.getName())//hello

4.閉包的另一個重要用途是實現面向物件中的物件,這個物件擁有獨立的成員及狀態,互不干涉。

functionPerson(){   

    var name = "default";      

    return {   

       getName : function(){   

           return name;   

       },   

       setName : function(newName){   

           name = newName;   

       }   

    }   

};

varjohn = Person();   

print(john.getName());   

john.setName("john");   

print(john.getName());   

varjack = Person();   

print(jack.getName());   

jack.setName("jack");   

print(jack.getName());

由此可見,john和jack都可以成為Person的例項,因為兩個例項對name這個成員的訪問時獨立的。

Js caller,callee,call,apply 概念

Arguments :

該物件代表正在執行的函式和呼叫它的函式的引數

arguments 是一個類似陣列但不是陣列的物件

arguments.length 是實參的長度arguments.callee.length是形參的長度

1Array.prototype.selfvalue = 1;
2alert(new Array().selfvalue);
3function testAguments(){
4     alert(arguments.selfvalue);
5}

輸出為:undefined

caller :

返回一個函式的引用,該函式呼叫了當前函式

functionName.caller

 1// caller demo {
 2function callerDemo() {
 3     if (callerDemo.caller) {
 4         var a= callerDemo.caller.toString();
 5         alert(a);
 6     } else {
 7         alert("this is a top function");
 8     }
 9}
10function handleCaller() {
11     callerDemo();
12}

caller只有函式執行時才有定義。如果是函式頂層呼叫則為null

callee :

返回正在被執行的function物件,也就是function的正文

callee是arguments物件的一個成員,它表示函式物件的本身。

使用場景:有利於匿名函式的遞迴和保證函式的封裝性

1//用於驗證引數 2function calleeLengthDemo(arg1, arg2) {
 3     if (arguments.length==arguments.callee.length) {
 4         window.alert("驗證形參和實參長度正確!");
 5         return;
 6     } else {
 7         alert("實參長度:" +arguments.length);
 8         alert("形參長度: " +arguments.callee.length);
 9     }
10}
11//遞迴計算12var sum = function(n){
13   if (n <= 0)                        
14   return 1;
15   else
16     return n +arguments.callee(n - 1)
17}
18//比較一般的遞迴函式
19
20var sum = function(n){
21     if (1==n) return 1;
22else return n + sum (n-1);

apply call :

他們的作用就是將函式繫結到另一個物件上去執行,兩者僅定義引數的方式區別:

apply(thisArg,argArray); call(thisArg[,arg1,arg2]);

將所有函式內部的this指標都會被賦值為thisArg,這可實現將函式作為另外一個物件方法執行的目的

值得提的一點,在javascript框架prototype裡就是使用apply來建立一個定義類的模式

1   var Class = {
2        create: function() {
3            return function() {
4                 this.initialize.apply(this, arguments);
5            }
6       }
7   }

封裝之弊

封裝會影響作用域問題,使除錯困難,一般來說不算大問題,但有時會很難區分來自不同作用域的同名變數,這是實現私有方法和屬性所需的閉包會讓它變得複雜

摻和物件

  function mixin(dest,src) {

    for (var key in src){

        if (!dest[key]){

            dest[key]= src[key]

        }

    }

  }

  var person= {name: 'John', age: 29}

  var obj ={name: 'Jack'}

  mixin(obj, person)

  console.log(obj) //Object { name="Jack", age=29}

改變成更加靈活的Mixin,比如可以將任意多的引數摻和到物件中

function mixin(dest /*,Any number of objects */) {

    var sources= Array.prototype.slice.call(arguments, 1)

    for (var i=0;i<sources.length; i++) {

        var src= sources[i]

        for (var key in src){

            if (!dest[key]){

                dest[key]= src[key]

            }

        }  

    }

}

var person= {name: 'John', age: 29, toString: function(){return 'aa'}}

var permission= {open: 1}

var obj= {name: 'Jack'}

mixin(obj,person, permission)

console.log(obj) //Object { name="Jack", age=29, open=1}

摻和類

因為構造器只有一個原型物件,不過這可以通過多個摻元類的方式實現擴充,算是一種變相的多繼承。

function augment(destClass, /*,Any number of classes */) {

    var classes= Array.prototype.slice.call(arguments, 1)

    for (var i=0;i<classes.length; i++) {

        var srcClass= classes[i]

        var srcProto = srcClass.prototype

        var destProto= destClass.prototype    

        for (var method in srcProto){

            if (!destProto[method]){

                destProto[method]= srcProto[method]

            }

        }      

    }

}

指定拷貝方法

function augment(destClass,srcClass, methods) {

    var srcProto = srcClass.prototype

    var destProto= destClass.prototype    

    for (var i=0;i<methods.length; i++) {

        var method= methods[i]

        if (!destProto[method]){

            destProto[method]= srcProto[method]

        }

    }

}

function Person(){}

Person.prototype.getName= function() {}

Person.prototype.setName = function() {}

Person.prototype.getAge = function() {}

Person.prototype.setAge = function() {}

function Student(){}

augment(Student,Person, ['getName', 'setName'])

var s1= new Student()

console.log(s1) //Student { getName=function(), setName=function()}