JavaScript學習總結(三)——閉包、IIFE、原型、函數與對象

1、閉包(Closure)

1.一、閉包相關的問題

請在頁面中放10個div,每一個div中放入字母a-j,當點擊每個div時顯示索引號,如第1個div顯示0,第10個顯示9;方法:找到全部的div,for循環綁定事件。javascript

示例代碼:css

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
        <style type="text/css">
            div {
                width: 100px;
                height: 100px;
                background: lightgreen;
                float: left;
                margin: 20px;
                font: 30px/100px "microsoft yahei";
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div>a</div>
        <div>b</div>
        <div>c</div>
        <div>d</div>
        <div>e</div>
        <div>f</div>
        <div>g</div>
        <div>h</div>
        <div>i</div>
        <div>j</div>
        <script type="text/javascript">
            var divs=document.getElementsByTagName("div");
            for (var i=0;i<divs.length;i++) {
                divs[i].onclick=function(){
                    alert(i);
                }
            }
        </script>
    </body>
</html>

運行結果:html

由於點擊事件的函數內部使用外部的變量i一直在變化,當咱們指定click事件時並無保存i的副本,這樣作也是爲了提升性能,但達不到咱們的目的,咱們要讓他執行的上下文保存i的副本,這種機制就是閉包。java

修改後的代碼:git

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
        <style type="text/css">
            div {
                width: 100px;
                height: 100px;
                background: lightgreen;
                float: left;
                margin: 20px;
                font: 30px/100px "microsoft yahei";
                text-align: center;
            }
        </style>
    </head>
    <body>
        <div>a</div>
        <div>b</div>
        <div>c</div>
        <div>d</div>
        <div>e</div>
        <div>f</div>
        <div>g</div>
        <div>h</div>
        <div>i</div>
        <div>j</div>
        <script type="text/javascript">
            var divs=document.getElementsByTagName("div");
            for (var i=0;i<divs.length;i++) {
                divs[i].onclick=(function(n){
                    return function(){
                        alert(n);    
                    }
                })(i);
            }
        </script>
    </body>
</html>

運行結果:github

n是外部函數的值,可是內部函數(點擊事件)須要使用,返回函數前的n被臨時駐留在內存中給點擊事件使用,簡單說就是函數的執行上下文被保存起來,i生成了多個副本。面試

1.二、理解閉包

閉包概念:當一個內部函數被調用,就會造成閉包,閉包就是可以讀取其餘函數內部變量的函數,定義在一個函數內部的函,建立一個閉包環境,讓返回的這個子程序抓住i,以便在後續執行時能夠保持對這個i的引用。內部函數比外部函數有更長的生命週期;函數能夠訪問它被建立時所處的上下文環境。chrome

示例1:編程

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
    </head>

    <body>
        <script type="text/javascript">
            //容許函數中嵌套函數
            //內部函數容許調用外部函數的變量
            //閉包就是可以讀取其餘函數內部變量的函數,內部函數和執行的上下文
            
            var foo=function(){
                var n=1;
                return function(){
                    n=n+1;
                    console.log(n);
                }
            }
            
            var bar=foo();
            bar();  //2
            bar();  //3
            
            var foobar=foo();
            foobar();  //2
            foobar();  //3
            
        </script>
    </body>

</html>

運行結果:bootstrap

Javascript語言特有的"鏈式做用域"結構(chain scope),子對象會一級一級地向上尋找全部父對象的變量

定義:閉包是指能夠訪問另外一個函數做用域變量的函數,通常是定義在外層函數中的內層函數。閉包就是可以讀取其餘函數內部變量的函數定義在一個函數內部的函數。

做用:局部變量沒法共享和長久的保存,而全局變量可能形成變量污染,因此咱們但願有一種機制既能夠長久的保存變量又不會形成全局污染。

特色:佔用更多內存;不容易被釋放

用法:變量既想反覆使用,又想避免全局污染如何使用?

  • 1.定義外層函數,封裝被保護的局部變量。
  • 2.定義內層函數,執行對外部函數變量的操做。
  • 3.外層函數返回內層函數的對象,而且外層函數被調用,結果保存在一個全局的變量中。

示例2:

var getNum;
function getCounter() {
var n = 1; 
var inner = function () { return n++; }
return inner;
}

getNum = getCounter();
console.log(getNum());
console.log(getNum());

結果:1 2

示例3:

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

結果:999 1000

在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證實了,函數f1中的局部變量n一直保存在內存中,並無在f1調用後被自動清除。

爲何會這樣呢?緣由就在於f1是f2的父函數,而f2被賦給了一個全局變量,這致使f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。

1.三、閉包測試

若是你能理解下面三段代碼的運行結果,應該就算理解閉包的運行機制了。

代碼片斷一:

  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());

代碼片斷二:

  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());

示例三:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>閉包</title>
    </head>
    <body>
        <div id="div1">
            <h2>h2點擊我看看</h2>
            <h2>h2點擊我看看</h2>
            <h2>h2點擊我看看</h2>
            <h2>h2點擊我看看</h2>
            <h2>h2點擊我看看</h2>
        </div>
        <script type="text/javascript">
            //閉包:使用外部函數內部變量的函數。
            var items = document.getElementsByTagName("h2");
            for(var i = 0; i < items.length; i++) {
                items[i].onclick =(function(n){
                    return function() {
                        alert(n + 1);
                    }
                })(i);
            }
        </script>
    </body>
</html>

結果:

面試題:

function fun(n, o) {
            console.log(o);
            return {
                fun: function (m) {
                    return fun(m, n);
                }
            };
        }

        var a = fun(0); // ?
        a.fun(1); // ?
        a.fun(2); // ?
        a.fun(3); // ?

        var b = fun(0).fun(1).fun(2).fun(3); // ?

        var c = fun(0).fun(1); // ?
        c.fun(2); // ?
        c.fun(3); // ?

答案:

1、
//undefind
//0
//0
//0

2、
//undefind
//0
//1
//2

3、
//undefind
//0
//1
//1
查看答案

1.四、小結

閉包就是使用外部函數內部變量的函數

注意事項:
1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。

2)閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便改變父函數內部變量的值。

2、對象

對象就是「鍵/值」對的集合並擁有一個鏈接到原型(prototype)對隱藏鏈接。

2.一、對象常量(字面量)

一個對象字面量就是包含在一對花括號中的零個或多個「鍵/值」對。對象字面量能夠出如今任何容許表達式出現的地方。

對象的定義:

        //空對象
        var obj1={};
        
        //對象中的屬性
        var obj2={name:"foo",age:19};
        var obj3={"nick name":"dog"};
        
        //對象中的方法
        var obj4={
            price:99,
            inc:function(){
                this.price+=1;
            }
        }

對象中可包含的內容:

對象常量能夠出如今任何容許表達式出現的地方,對象、數組、函數能夠相互間嵌套,形式能夠多種多樣。對象的值能夠是:數組,函數,對象,基本數據類型等。

            //對象中可包含的內容
            var obj5 = [{
                name: "jack"
            }, {
                name: "lucy",  //常量
                hobby:["讀書","上網","代碼"],  //數組
                friend:{name:"mark",height:198,friend:{}},  //對象
                show:function(){  //函數
                    console.log("你們好,我是"+this.name);
                }
            }];
            //對象中的this是動態的,指向的是:調用者
            obj5[1].show();

輸出:你們好,我是lucy

2.二、取值

方法一:直接使用點號運算

            //3取值
            var obj6={"nick name":"pig",realname:"Rose"};
            console.log(obj6.realname);
            //console.log(obj6.nick name);  錯誤

方法二:使用索引器,當對象中的key有空格是

            //3取值
            var obj6={"nick name":"pig",realname:"Rose"};
            
            console.log(obj6["realname"]);
            console.log(obj6["nick name"]);

2.三、枚舉(遍歷)

示例一:

            var obj7={weight:"55Kg","nick name":"pig",realname:"Rose"};
            
            for (var key in obj7) {
                console.log(key+":"+obj7[key]);
            }

運行結果:

輸出順序是不能保證的。

示例二:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>對象</title>
    </head>
    <body>
        <h2>對象</h2>
        
        <script>
            var phone={"name":"Mi Plus5","price":999,"color":"white"};
            //直接訪問
            console.log(phone.name);
            console.log(phone["price"]);
            //迭代
            for(var key in phone){
                console.log(key+"->"+phone[key]);
            }
            //迭代window
            for(var key in this){
                console.log(key);
                console.log(this[key]);
            }
        </script>
    </body>
</html>
View Code

結果:

2.四、更新與添加

若是對象中存在屬性就修改對應值,若是不存在就添加。對象經過引用傳遞,它們永遠不會被複制

            var obj8={realname:"King"};
            obj8.realname="Queen";  //修改
            obj8.weight=1000;  //添加屬性
            obj8.show=function()  //添加方法
            {
                console.log(this.realname+","+this.weight);
            }
            obj8.show();

輸出:

Queen,1000

            var obj8={realname:"King"};
            obj8.realname="Queen";  //修改
            obj8.weight=1000;  //添加屬性
            obj8.show=function()  //添加方法
            {
                console.log(this.realname+","+this.weight);
            }
            obj8.show();
            
            //引用
            var obj9=obj8;   //obj9指向obj8的引用
            obj9.realname="Jack";
            obj8.show();

輸出:

2.五、對象的原型

javascript是一種動態語言,與C#和Java這樣的靜態語言是不同的;javascript並無嚴格的類型,能夠簡單認爲javascript是由對象組成的,對象間鏈接到原型(prototype)實現功能的擴展與繼承。每一個對象都連接到一個原型對象,而且能夠從中繼承屬性,全部經過常量(字面量)建立的對象都鏈接到Object.prototype,它是JavaScript中的頂級(標配)對象,相似高級語言中的根類。

建立對象的三種方法:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            //一、對象字面量建立對象
            var o={name:"goodcat"};
            
            //二、經過Object建立對象
            var obj=new Object();
            obj.name="badcat";
            
            //三、構造函數,類,Cat就是一個類型
            function Cat(name){
                this.name=name;
            }
            
            //對象(經過構造方法建立的對象)
            var mycat=new Cat("tom");
            console.log(mycat.name);
            
            //類型判斷
            console.log(mycat instanceof Cat);
            console.log(mycat instanceof Object);
        
            
        </script>
    </body>
</html>
View Code

理解Object,Function,prototype,__proto__,constractor(構造方法)之間的關係很是重要。

經過修改原型實現擴展方法,對象的prototype是不容許直接訪問的,可使用__proto__訪問:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>對象</title>
    </head>
    <body>
        <h2>原型</h2>
        <script>
            Object.prototype.o=function(){
                alert(this);
                console.log(this);
            }

            var str="Hello JavaScript!";
            str.o();
            
            "Hello Prototype!".o();
            
            var phone={"name":"Mi Plus5","price":999,"color":"white"};
            phone.o();
            console.log(phone.__proto__);  //Object
            console.log(phone.prototype);
            //對象的prototype是不容許直接訪問的,可使用__proto__訪問
        </script>
    </body>
</html>

結果:

示例2:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>

    <body>
        <script type="text/javascript">
            //一、對象字面量建立對象
            var o = {
                name: "goodcat"
            };

            //二、經過Object建立對象
            var obj = new Object();
            obj.name = "badcat";

            //三、構造函數,類,Cat就是一個類型
            function Cat(name) {
                this.name = name;
            }

            //對象(經過構造方法建立的對象)
            var mycat = new Cat("tom");
            var okcat = new Cat("ok");
            console.log(mycat.name);

            //對象的prototype是不容許直接訪問的(類型的是能夠訪問的),可使用__proto__訪問
            console.log(mycat.prototype);
            console.log(mycat.__proto__); //非標準,chrome

            //經過修改原型實現擴展方法,類型的prototype是能夠訪問的
            Cat.prototype.show = function() {
                console.info("這是一隻叫" + this.name + "的貓");
            }
            mycat.show();
            okcat.show();
            
            //在Object的原型中添加了一個out方法
            Object.prototype.out=function(){
                alert(JSON.stringify(this));
            }
            
            mycat.out();
            okcat.out();
            (new Date()).out();
            "Hello".out();
            Window.out();
            
        </script>
    </body>

</html>
View Code

結果:

javascript對象藏寶圖:

在JavaScript中,原型也是一個對象,經過原型能夠實現對象的屬性繼承,JavaScript的對象中都包含了一個"Prototype"內部屬性,這個屬性所對應的就是該對象的原型。

"Prototype"做爲對象的內部屬性,是不能被直接訪問的。因此爲了方便查看一個對象的原型,Firefox和Chrome中提供了"__proto__"這個非標準(不是全部瀏覽器都支持)的訪問器(ECMA引入了標準對象原型訪問器"Object.getPrototype(object)")。

(1)、全部構造器/函數的__proto__都指向Function.prototype,它是一個空函數(Empty function)

(2)、全部對象的__proto__都指向其構造器的prototype

(3)、對於全部的對象,都有__proto__屬性,這個屬性對應該對象的原型

(4)、對於函數對象,除了__proto__屬性以外,還有prototype屬性,當一個函數被用做構造函數來建立實例時,該函數的prototype屬性值將被做爲原型賦值給全部對象實例(也就是設置實例的__proto__屬性)

使用原型實現繼承:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>對象</title>
    </head>
    <body>
        <h2>原型</h2>
        <script>
            //機器 父類
            var  Machine={
                weight:1355
            };
            
            //構造方法,類
            function Car(_name){
                this.name=_name;
            }
            
            //Car的原型是機器
            Car.prototype=Machine;
            
            //建立對象
            var bmw=new Car("寶馬");
            var benz=new Car("奔馳");
            console.log(bmw.name);
            console.log(bmw.weight);
        </script>
    </body>
</html>

結果:

如今咱們修改系統中的Object對象,添加一個建立方法,指定要建立對象的原型,實現相似繼承功能:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>對象</title>
    </head>
    <body>
        <h2>原型</h2>
        
        <script>
            alert(typeof Object.beget);
            if(typeof Object.beget==="undefined"){
                Object.beget=function(proto){
                    //構造方法,F是一個類型
                    var F=function(){};
                    //指定類型F的原型
                    F.prototype=proto;
                    //建立一個F類型的對象
                    return new F();
                }
            }
            
            //機器
            var  Machine={
                name:"機器",
                show:function(){
                    console.log("機器的名稱是:"+this.name);
                }
            };
            
            //建立對象,指定原型
            var bmw=Object.beget(Machine);
            bmw.name="寶馬";
            bmw.show();
            
            console.log(bmw);
        </script>
    </body>
</html>

運行結果:

原型關係是一種動態關係,若是修改原型,該原型建立的對象會受到影響。

            var lucy=Object.create(rose);  //簡單認爲是:建立一個對象且繼承rose
            lucy.name="lucy";  //重寫
            
            var jack=Object.create(rose);
            jack.name="jack";
            
            //修改原型中的方法
            rose.show=function(){
                console.log("姓名->"+this.name);
            }
            
            lucy.show();
            jack.show();

結果:

關於原型在函數中會再講到。

2.六、刪除

            //刪除屬性
            delete mark.name;   
            //調用方法,輸出:姓名:undefined
            mark.show(); 
            
            //刪除函數
            delete mark.show;  
            //錯誤,mark.show is not a function
            mark.show();

刪除不用的屬性是一個好習慣,在某些狀況下可能引起內存泄漏。

2.七、封裝

使用對象封裝的好處是能夠減小全局變量的污染機會,將屬性,函數都隸屬一個對象。

封裝前:

var name="foo";   //name是全局的,被暴露
            i=1;  //全局的,沒有var關鍵字聲明的變量是全局的,與位置關係不大
            function show(){  //show 是全局的,被暴露
                console.log("name->"+name);
                console.log(++i);
            }
            
            //i是全局的 2
            show();  
            //3
            show();

封裝後:

//對外只暴露bar,使用閉包封裝
            var bar=function(){
                var i=1;
                return{
                    name:"bar",
                    show:function(){
                        console.log("name->"+this.name);
                        console.log(++i);
                    }
                };
            };
            
            var bar1=bar();
            //2
            bar1.show();
            //3
            bar1.show();
            
            var bar2=bar();
            //2,由於被封裝,且閉包,i是局部私有的
            bar2.show();

運行結果:

2.八、對象的淺拷貝與深拷貝

淺拷貝和深拷貝都是對於JS中的引用類型而言的,淺拷貝就只是複製對象的引用(堆和棧的關係,原始(基本)類型Undefined,Null,Boolean,Number和String是存入堆,直接引用,object array 則是存入桟中,只用一個指針來引用值),若是拷貝後的對象發生變化,原對象也會發生變化。只有深拷貝纔是真正地對對象的拷貝。

2.8.一、淺拷貝

默認是淺拷貝,只是將地址進行了複製,示例以下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            //淺拷貝
            var obj1={name:"cat"};
            var obj2=obj1;
        
            obj2.name="pig";
            
            console.log(obj1.name);
            console.log(obj2.name);
        </script>
    </body>
</html>

結果:pig

2.8.二、深拷貝

深拷貝就是對目標的徹底拷貝,不像淺拷貝那樣只是複製了一層引用,就連值也都複製了。

只要進行了深拷貝,不會相互影響。

實現深拷貝的方法主要是三種:

一、利用 JSON 對象中的 parse 和 stringify

就是說若是對象中含有一個函數時(很常見),就不能用這個方法進行深拷貝

            var obj1 = {
                name: "cat",
                show:function(){
                    console.log(this.name);
                }
            };
            var obj2 = JSON.parse(JSON.stringify(obj1));

            obj2.name = "pig";

            console.log(obj1.name);
            console.log(obj2.name);
            
            obj1.show();
            obj2.show();  //函數被丟失

結果:

二、利用遞歸來實現每一層都從新建立對象並賦值

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>

    <body>
        <script type="text/javascript">
            var obj1 = {
                name: "cat",
                show: function() {
                    console.log("名稱:"+this.name);
                }
            };
            var obj2 = deepClone(obj1);

            obj2.name = "pig";

            console.log(obj1.name);
            console.log(obj2.name);

            obj1.show();
            obj2.show();

            function deepClone(obj) {
                let objClone = Array.isArray(obj) ? [] : {};
                if(obj && typeof obj === "object") {
                    for(key in obj) {
                        if(obj.hasOwnProperty(key)) {
                            //判斷ojb子元素是否爲對象,若是是,遞歸複製
                            if(obj[key] && typeof obj[key] === "object") {
                                objClone[key] = deepClone(obj[key]);
                            } else {
                                //若是不是,簡單複製
                                objClone[key] = obj[key];
                            }
                        }
                    }
                }
                return objClone;
            }
        </script>
    </body>

</html>

運行結果:

三、ES6 中 引入了Object.assgn 方法展開運算符也能實現對對象的拷貝。

賦值運算符 = 實現的是淺拷貝,只拷貝對象的引用值;

JavaScript 中數組和對象自帶的拷貝方法都是「首層淺拷貝」;

JSON.stringify 實現的是深拷貝,可是對目標對象有要求(非 undefined,function);

若想真正意義上的深拷貝,請遞歸。

2.九、對象建立(new)的原理

在 JavaScript 中,使用 new 關鍵字後,意味着作了以下四件事情:

一、建立一個新的對象,這個對象的類型是 object;

二、設置這個新的對象的內部、可訪問性和[[prototype]]屬性爲構造函數(指prototype.construtor所指向的構造函數)中設置的;

三、執行構造函數,當this關鍵字被說起的時候,使用新建立的對象的屬性; 返回新建立的對象(除非構造方法中返回的是‘無原型’)。

四、在建立新對象成功以後,若是調用一個新對象沒有的屬性的時候,JavaScript 會延原型鏈向止逐層查找對應的內容。這相似於傳統的‘類繼承’。

注意:在第二點中所說的有關[[prototype]]屬性,只有在一個對象被建立的時候起做用,好比使用 new 關鍵字、使用 Object.create 、基於字面意義的(函數默認爲 Function.prototype ,數字默認爲 Number.prototype 等)。它只能被Object.getPrototypeOf(someObject) 所讀取。沒有其餘任何方式來設置或讀取這個值。

示例:

1 function Foo(){}
2 var foo = new Foo();

每個函數中都會有一個叫prototype的屬性,類型是object,即一個引用對象。

每個對象中都會有一個叫__proto__的屬性,類型是object,是一個引用對象。

一、當JavaScript引擎執行new操做時,會立刻開闢一個塊內存,建立一個空對象(並將this指向這個對象)。

二、執行構造函數Foo(),對這個空對象進行構造(構造函數裏有什麼屬性和方法都一一給這個空白對象裝配上去,這就是爲什麼它叫構造函數了)。

三、給這個空對象添加了一個叫__proto__的屬性,並且這個__proto__指向Foo()的prototype對象。換句話說,就是__proto__ = prototype;

2.十、Object與Object.property

2.10.一、Object

JavaScript中的全部對象都來自 Object;全部對象從Object.prototype繼承方法和屬性,儘管它們可能被覆蓋。例如,其餘構造函數的原型將覆蓋 constructor 屬性並提供本身的 toString() 方法。Object 原型對象的更改將傳播到全部對象,除非受到這些更改的屬性和方法將沿原型鏈進一步覆蓋。

Object 構造函數爲給定值建立一個對象包裝器。若是給定值是 null 或 undefined,將會建立並返回一個空對象,不然,將返回一個與給定值對應類型的對象。

當以非構造函數形式被調用時,Object 等同於 new Object()。

Object.assign()
經過複製一個或多個對象來建立一個新的對象。

Object.create()
使用指定的原型對象和屬性建立一個新對象。

Object.defineProperty()
給對象添加一個屬性並指定該屬性的配置。

Object.defineProperties()
給對象添加多個屬性並分別指定它們的配置。

Object.entries()
返回給定對象自身可枚舉屬性的 [key, value] 數組。

Object.freeze()
凍結對象:其餘代碼不能刪除或更改任何屬性。

Object.getOwnPropertyDescriptor()
返回對象指定的屬性配置。

Object.getOwnPropertyNames()
返回一個數組,它包含了指定對象全部的可枚舉或不可枚舉的屬性名。

Object.getOwnPropertySymbols()
返回一個數組,它包含了指定對象自身全部的符號屬性。

Object.getPrototypeOf()
返回指定對象的原型對象。

Object.is()
比較兩個值是否相同。全部 NaN 值都相等(這與==和===不一樣)。

Object.isExtensible()
判斷對象是否可擴展。

Object.isFrozen()
判斷對象是否已經凍結。

Object.isSealed()
判斷對象是否已經密封。

Object.keys()
返回一個包含全部給定對象自身可枚舉屬性名稱的數組。

Object.preventExtensions()
防止對象的任何擴展。

Object.seal()
防止其餘代碼刪除對象的屬性。

Object.setPrototypeOf()
設置對象的原型(即內部 [[Prototype]] 屬性)。

Object.values()
返回給定對象自身可枚舉值的數組。

2.10.二、Object.property

幾乎全部的 JavaScript 對象都是 Object 的實例;一個典型的對象繼承了Object.prototype的屬性(包括方法),儘管這些屬性可能被遮蔽(亦稱爲覆蓋)。可是有時候可能故意建立不具備典型原型鏈繼承的對象,好比經過Object.create(null)建立的對象,或者經過Object.setPrototypeOf方法改變原型鏈。

改變Object原型,會經過原型鏈改變全部對象;除非在原型鏈中進一步覆蓋受這些變化影響的屬性和方法。這提供了一個很是強大的、但有潛在危險的機制來覆蓋或擴展對象行爲。

Object.prototype.constructor
特定的函數,用於建立一個對象的原型。

Object.prototype.__proto__ 
指向當對象被實例化的時候,用做原型的對象。

Object.prototype.__noSuchMethod__ 
當未定義的對象成員被調用做方法的時候,容許定義並執行的函數。

Object.prototype.__defineGetter__() 
關聯一個函數到一個屬性。訪問該函數時,執行該函數並返回其返回值。

Object.prototype.__defineSetter__() 
關聯一個函數到一個屬性。設置該函數時,執行該修改屬性的函數。

Object.prototype.__lookupGetter__() 
返回使用 __defineGetter__ 定義的方法函數 。

Object.prototype.__lookupSetter__() 
返回使用 __defineSetter__ 定義的方法函數。

Object.prototype.hasOwnProperty()
返回一個布爾值 ,表示某個對象是否含有指定的屬性,並且此屬性非原型鏈繼承的。

Object.prototype.isPrototypeOf()
返回一個布爾值,表示指定的對象是否在本對象的原型鏈中。

Object.prototype.propertyIsEnumerable()
判斷指定屬性是否可枚舉,內部屬性設置參見 ECMAScript [[Enumerable]] attribute 。

Object.prototype.toSource() 
返回字符串表示此對象的源代碼形式,可使用此字符串生成一個新的相同的對象。

Object.prototype.toLocaleString()
直接調用 toString()方法。

Object.prototype.toString()
返回對象的字符串表示。

Object.prototype.unwatch() 
移除對象某個屬性的監聽。

Object.prototype.valueOf()
返回指定對象的原始值。

Object.prototype.watch() 
給對象的某個屬性增長監聽。

參考

3、函數 Function

Function 構造函數 建立一個新的Function對象。 在 JavaScript 中, 每一個函數實際上都是一個Function對象。

var add = new Function('m', 'n', 'return m + n');
​
console.log(add(1, 2));

 

使用Function構造器生成的Function對象是在函數建立時解析的。這比你使用函數聲明或者函數表達式(function)並在你的代碼中調用更爲低效,由於使用後者建立的函數是跟其餘代碼一塊兒解析的。

全部被傳遞到構造函數中的參數,都將被視爲將被建立的函數的參數,而且是相同的標示符名稱和傳遞順序。

javascript中的函數就是對象,對象就是「鍵/值」對的集合並擁有一個鏈接到原型對隱藏鏈接。

屬性
arguments[]
一個參數數組,元素是傳遞給函數的參數。反對使用該屬性。
caller
對調用當前函數的Function對象的引用,若是當前函數由頂層代碼調用,這個屬性的值爲null。反對使用該屬性。
length
在聲明函數時指定的命名參數的個數。
prototype
一個對象,用於構造函數,這個對象定義的屬性和方法由構造函數建立的全部對象共享。

方法
apply( )
將函數做爲指定對象的方法來調用,傳遞給它的是指定的參數數組。
call( )
將函數做爲指定對象的方法來調用,傳遞給它的是指定的參數。
toString( )
返回函數的字符串表示。

描述
函數是JavaScript的一種基本數據類型。注意,雖然能夠用這裏介紹的Function()構造函數建立函數對象, 但這樣作效率不高,在大多數狀況下,建議使用函數定義語句或函數直接量來定義函數。

在JavaScriptl.1及之後版本中,函數主體會被自動地給予一個局部變量arguments,它引用一個Arguments對象。該對象是一個數組,元素是傳遞給函數的參數值。

3.0、Function定義

Function其實是對象,與其餘引用類型同樣具備屬性和方法。Function能夠經過三種方法進行定義,分別是函數聲明語法定義,函數表達式定義和Function構造函數定義。

1.函數聲明語法定義

function functionName(value1...){
    //函數體
}

 

2.函數表達式定義

var functionName = function(value1...){
    //函數體
}

3.Function構造函數定義

經過Function構造函數建立函數,可向構造函數中傳入任意數量的參數,但值得注意的是傳入的最後一個參數會做爲函數體,而其餘參數則做爲參數傳入函數中。用該方法去定義函數是不推薦使用的,由於該語法會致使解析兩次代碼,第一次解析常規ECMAScript代碼,第二次解析傳入構造函數的字符串,影響性能。

var functionName = new Function("value",...,"函數體");

示例:

            var f2=new Function("n1","n2","return n1+n2;");
            console.log(f2(1,2));

結果:3

注:函數是引入值類型,因此函數名僅僅是指向函數的指針,當使用函數名去賦值給另外一個變量名時,僅僅複製的是一個指針。即在下列a設置爲null時,僅將a存的指針消除而已,不會影響b調用函數。

var a = b = function(value1){
    return value1;
}
a = null;
b(1);

3.一、參數對象 (arguments)

每個函數中有一個默認對象叫arguments,相似數組,但不是數組,該對象是傳遞給函數的參數。

        <script type="text/javascript">
            function counter(){
                var sum=0;
                for(var i=0;i<arguments.length;i++){
                    sum+=arguments[i];
                }
                return sum;
            }
            
            console.log(counter(199,991,1,2,3,4,5));
            console.log(counter());
        </script>

運行結果:

1205

這裏的arguments是一個隱式對象,不聲明也在函數中,內部函數能夠訪問外部函數的任意內容,可是不能直接訪問外部函數的arguments與this對象。

            function f1()
            {
                console.log(arguments.length);
                f2=function()
                {
                    console.log(arguments.length);
                }
                return f2;
            }
            
            var f=f1(1,2,3);
            f();

運行結果:

3

0

3.二、構造函數

在javascript中對象構造函數能夠建立一個對象。

           <script type="text/javascript">
           /*構造函數*/
          //能夠簡單的認爲是一個類型的定義
           function Student(name,age){
                 this.name=name;
                 this.age=age;
                 this.show=function(){
                     console.log(this.name+","+this.age);
                 }
           }
           
           //經過new關鍵字調用構造函數,建立一個對象tom
           var rose=new Student("rose",18);
           var jack=new Student("jack",20);
           
           rose.show();
           jack.show();
        </script>

3.三、函數調用

3.3.一、call

調用一個對象的一個方法,以另外一個對象替換當前對象

call([thisObj[,args])

hisObj 可選項。將被用做當前對象的對象。args 將被傳遞方法參數序列。
call 方法能夠用來代替另外一個對象調用一個方法。call 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象。

示例:

           /*構造函數*/
           function Student(name,age){
                 this.name=name;
                 this.age=age;
           }
           
            show=function(add){
                     console.log(add+":"+this.name+","+this.age);
               }
           
           //經過new關鍵字調用構造函數,建立一個對象tom
           var rose=new Student("rose",18);
           var jack=new Student("jack",20);
          
          //調用show方法,指定上下文,指定調用對象,this指向rose,「你們好是參數」
          show.call(rose,"你們好");
          show.call(jack,"Hello");

運行結果:

call方法中的參數均可以省去,第1個參數表示在哪一個對象上調用該方法,或this指向誰,若是不指定則會指向window對象。

示例:

          var name="無名";
          var age=18;
          show.call();

結果:

undefined:無名,18

3.3.二、apply

apply([thisObj[,argArray]])
應用某一對象的一個方法,用另外一個對象替換當前對象,與call相似。
若是 argArray 不是一個有效的數組或者不是arguments對象,那麼將致使一個 TypeError。
若是沒有提供 argArray 和 thisObj 任何一個參數,那麼 Global 對象將被用做 thisObj, 而且沒法被傳遞任何參數。
對於第一個參數意義都同樣,但對第二個參數:
apply傳入的是一個參數數組,也就是將多個參數組合成爲一個數組傳入,而call則做爲call的參數傳入(從第二個參數開始)。
如 func.call(func1,var1,var2,var3)對應的apply寫法爲:func.apply(func1,[var1,var2,var3])
同時使用apply的好處是能夠直接將當前函數的arguments對象做爲apply的第二個參數傳入

示例代碼:

           /*構造函數*/
           function Student(name,age){
                 this.name=name;
                 this.age=age;
           }
           
            show=function(greeting,height){
                     console.log(greeting+":"+this.name+","+this.age+","+height);
               }
           
           //經過new關鍵字調用構造函數,建立一個對象tom
           var rose=new Student("rose",18);
           var jack=new Student("jack",20);
          
          //調用show方法,指定上下文,指定調用對象,this指向rose,「你們好是參數」
          show.apply(rose,["你們好","178cm"]);
          show.apply(jack,["Hello","188cm"]);

運行結果:

 

從上面的示例中能夠發現apply的第2個參數是一個數組,數組中的內容將映射到被調用方法的參數中,若是單這樣看發現不如call方便,其實若是直接取方法的參數arguments則apply要方便一些。經過簡單的變化就能夠替代call。

          function display(){
             show.apply(jack,arguments);
          }
          display("hi","224cm");

結果:

hi:jack,20,224cm

javascript裏call和apply操做符能夠隨意改變this指向
若是在javascript語言裏沒有經過new(包括對象字面量定義)、call和apply改變函數的this指針,函數的this指針都是指向window的。
關於this指針,個人總結是:是誰調用的函數,那麼這個函數中的this指針就是它;若是沒有明確看出是誰調用的,那麼應該就是window調用的,那麼this指針就是window。

3.3.三、caller

在一個函數調用另外一個函數時,被調用函數會自動生成一個caller屬性,指向調用它的函數對象。若是該函數當前未被調用,或並不是被其餘函數調用,則caller爲null。
在JavaScript的早期版本中,Function對象的caller屬性是對調用當前函數的函數的引用

        function add()
        {
            console.log("add被調用");
            //add方法的調用函數,若是調用add方法的不是函數則爲null
            console.log(add.caller);
        }
        
        function calc(){
            add();
        }
        
        //直接調用add方法
        add(); 
        //間接經過calc方法調用
        calc();

運行結果:

caller與this仍是有區別的,this是指調用方法的對象,而caller是指調用函數的函數。

        <script type="text/javascript">
        function add(n)
        {
            console.log("add被調用");
            if(n<=2){
                return 1;
            }
            return add.caller(n-1)+add.caller(n-2);
        }
        
        function calc(n){
            console.log("calc被調用");
            return add(n);
        }
        
        //1 1 2
        console.log(calc(3));
        </script>

結果:

3.3.四、Callee

當函數被調用時,它的arguments.callee對象就會指向自身,也就是一個對本身的引用

           function add(n1,n2){
                  console.log(n1+n2);
                  //arguments.callee(n1,n2);  //指向add方法
                  return arguments.callee;
           }
           
           add(1,2)(3,4)(5,6)(7,8)(8,9);

運行結果:

當第1次調用add方法時輸入3,當即將函數返回再次調用,每次調用後又返回本身,這樣能夠實現鏈式編程。

3.四、length

在聲明函數時指定的命名參數的個數。

示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Function</title>
    </head>
    <body>
        <h2>Function - length</h2>
        <script>
            function f1(n1,n2)
            {
                console.log("實際帶入的參數個數:"+arguments.length);
            }
            console.log("定義的命名參數個數:"+f1.length);
            f1(1);
            f1(1,2,3);
        </script>
    </body>
</html>

結果:

3.五、當即執行函數表達式 (IIFE)

IIFE即Immediately-Invoked Function Expression,當即執行函數表達式,在 JavaScript 中每一個函數被調用時,都會建立一個新的執行上下文。由於在函數裏定義的變量和函數是惟一在內部被訪問的變量,而不是在外部被訪問的變量,當調用函數時,函數提供的上下文提供了一個很是簡單的方法建立私有變量。

3.5.0、塊級做用域與函數做用域

javascript沒有塊級做用域但有函數級做用域,可使用IIFE模擬塊級做用域。

任何一對花括號中的語句集都屬於一個塊,在這之中定義的全部變量在代碼塊外都是不可見的,咱們稱之爲塊級做用域。

做用域永遠都是任何一門編程語言中的重中之重,由於它控制着變量與參數的可見性與生命週期。

塊級做用域:任何一對花括號({和})中的語句集都屬於一個塊,在這之中定義的全部變量在代碼塊外都是不可見的,咱們稱之爲塊級做用域。

與函數做用域:函數做用域就好理解了,定義在函數中的參數和變量在函數外部是不可見的。

示例:

        <script type="text/javascript">
            function calc(){
                for(var i=0;i<5;i++){
                    console.log(i);
                }
                console.log(i);
            }
       calc();
</script>

結果:

你想的結果:

        <script type="text/javascript">
            function calc(){
                for(var i=0;i<5;i++){
                    console.log(i);  //0,1,2,3,4
                }
                console.log(i);  //報錯
            }
            calc();
        </script>

實際結果:

解決方法,模擬一個塊級做用域:

        <script type="text/javascript">
            function calc() {
                
                //IIFE
                (function() {
                    for(var i = 0; i < 5; i++) {
                        console.log(i); //0,1,2,3,4
                    }
                })();
                
                console.log(i); //報錯
            }

            calc();
        </script>

結果:

 

3.5.一、匿名函數與匿名對象

匿名函數就是沒有名稱的函數,javascript中常常會使用匿名函數實現事件綁定,回調,實現函數級的私有做用域,以下所示:

        function(){
            console.log("這是一個匿名函數");
        };

匿名對象:

        {
            name:"foo",
            show:function(){
                console.log(this.name);
            }
        }

沒有名稱的匿名函數也叫函數表達式,它們間是有區別的。

3.5.二、函數與函數表達式

下面是關於函數與函數表達式定義時的區別

a)、函數定義(Function Declaration)

function Identifier ( Parameters ){ FunctionBody }

function 函數名稱(參數){函數主體}

在函數定義中,函數名稱是必不可少的,若是遺漏,會報提示錯誤:

代碼:

        function(){
            console.log("這是一個匿名函數");
        };

結果:

b)、函數表達式(Function Expression)

function Identifier(Parameters){ FunctionBody }
函數表達式中,參數和標識符都是可選的,與函數定義的區別是標識符可省去。

其實,"function Identifier(Parameters){ FunctionBody }"並非一個完整的函數表達式,完整的函數的表達式,須要一個賦值操做。
好比: var name=function Identifier(Parameters){ FunctionBody }

3.5.三、當即執行函數表達式與匿名對象

            //1 正常定義函數
            function f1(){
                console.log("正常定義f1函數");
            };
            
            //2 被誤解的函數表達式
            function(){
                console.log("報錯Unexpected token (");
            }();
            
            //3 IIFE,括號中的內容被解釋成函數表達式
            (function(){
                console.log("IIFE,正常執行");
            })();
            
            //4 函數表達式
            var f2=function(){
                console.log("這也被視爲函數表達式");
            };

第3種寫法爲何這樣就能當即執行而且不報錯呢?由於在javascript裏,括號內部不能包含語句,當解析器對代碼進行解釋的時候,先碰到了(),而後碰到function關鍵字就會自動將()裏面的代碼識別爲函數表達式而不是函數聲明。

若是須要將函數表達式或匿名對象當即執行,可使用以下方法:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>IIFE</title>
    </head>

    <body>
        <script type="text/javascript">
            //調用匿名函數
            (function() {
                console.log("這是一個函數表達式");
            })();

            //調用匿名對象
            ({
                name: "foo",
                show: function() {
                    console.log(this.name);
                }
            }).show();

            console.log({
                a: 1
            }.a);

            console.log({
                a: function() {}
            }.a());
        </script>
    </body>

</html>

 

運行結果:

3.5.四、各類IIFE的寫法

//最經常使用的兩種寫法
(function(){ /* code */ }()); // 老師推薦寫法
(function(){ /* code */ })(); // 固然這種也能夠

// 括號和JS的一些操做符(如 = && || ,等)能夠在函數表達式和函數聲明上消除歧義
// 以下代碼中,解析器已經知道一個是表達式了,因而也會把另外一個默認爲表達式
// 可是二者交換則會報錯
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

// 若是你不怕代碼晦澀難讀,也能夠選擇一元運算符
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();

// 你也能夠這樣
new function(){ /* code */ }
new function(){ /* code */ }() // 帶參

若是是函數表達式,可直接在其後加"()"當即執行。

若是是函數聲明,能夠經過"()"、"+"、"-"、"void"、"new"等運算符將其轉換爲函數表達式,而後再加"()"當即執行。

3.5.五、參數

函數表達式也是函數的一種表達形式,一樣能夠像函數同樣使用參數,以下所示:

            (function (n){
                console.log(n);
            })(100);

輸出:100 

其實經過IIFE還能造成一個相似的塊級做用域,當塊內的程序在使用外部對象時將優先查找塊內的對象,再查找塊外的對象,依次向上。

            (function(win,undfd){
                win.console.log("Hello"==undfd);
            })(window,undefined);

3.5.六、添加分號

爲了不與其它的javascript代碼產生影響後報錯,經常會在IIFE前增長一個分號,表示前面全部的語句都結束了,開始新的一語句。

            var k=100
            (function (n){
                console.log(n);
            })(k);

上面的腳本會報錯,由於javascript解釋器會認爲100是函數名。

            var k=100
            ;(function (n){
                console.log(n);
            })(k);

 

這樣就正確了,在javascript中一行語句的結束可使用分號,也能夠不使用分號,由於通常的自定義插件會使用IIFE,這是一段獨立的代碼,在應用過程當中不能保證用戶會加上分號,因此建議在IIFE前加上分號。

3.5.七、IIFE的做用

1)、提升性能

減小做用域查找時間。使用IIFE的一個微小的性能優點是經過匿名函數的參數傳遞經常使用全局對象window、document、jQuery,在做用域內引用這些全局對象。JavaScript解釋器首先在做用域內查找屬性,而後一直沿着鏈向上查找,直到全局範圍。將全局對象放在IIFE做用域內提高js解釋器的查找速度和性能。

function(window, document, $) {

}(window, document, window.jQuery); 

2)、壓縮空間

經過參數傳遞全局對象,壓縮時能夠將這些全局對象匿名爲一個更加精簡的變量名

function(w, d, $) {  
  
}(window, document, window.jQuery);

3)、避免衝突

 匿名函數內部能夠造成一個塊級的私有做用域。

4)、依賴加載

能夠靈活的加載第三方插件,固然使用模塊化加載更好(AMD,CMD),示例以下。

A.html與B.html文件同時引用公用的common.js文件,可是隻有A.html須要使用到StuObj對象,B.html不須要,但使用其它方法。

Student.js

var StuObj = {
    getStu: function(name) {
        return new Student(name);
    }
}

/*構造函數*/
function Student(name) {
    this.name = name;
    this.show = function() {
        console.log("Hello," + this.name);
    }
}

Common.js

function other1() {}

function other2() {}

(function($) {
    if($) {
        $.getStu("Tom").show();
    }
})(typeof StuObj=="undefined"?false:StuObj);

A.HTML

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>A</title>
    </head>
    <body>
        <script src="js/Student.js" type="text/javascript" charset="utf-8"></script>
        <script src="js/common.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

B.HTML

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script src="js/common.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            other1();
        </script>
    </body>
</html>

3.5.八、IIFE的變形

也許有人會說IIFE將參數放在最後,須要移動到文檔的末尾才能看到參數,比較麻煩,那麼能夠將IIFE變形爲以下形式:

        (function(n){
            console.log(n);
            
            
            
            
            
            
            
            //認爲這裏有30000代碼
            
            
            
            
            
            
            
        }(100));

若是中間有很長的代碼,參數100只有到文檔的末尾才能夠看獲得,變形後的結果:

        (function(exp){
            exp(100);
        }(function(n){
            console.log(n);
            //認爲這裏有30000代碼
        }));

修改後的代碼中有兩個函數表達式,一個做爲參數,就是咱們主要要完成的功能向控制檯輸出數字,另外一個做來IIFE當即執行的函數,主要的功能函數變成的IIFE的參數了。

            (function(win, doc, $) {

            }(window, document, jQuery));

            (
                function(library) {
                    library(window, document, window.jQuery);
                }
                (function(window, document, $) {

                })
            );

bootstrap的寫法:

            +function(yourcode) {

                yourcode(window.jQuery, window, document);

            }(function($, window, document) {
                    $(function() {});  //jQueryDOM加載完成事件
              });

結合call或apply的寫法:

              (function(x){console.log(x)}).call(window,888);
              (function(x){console.log(x)}).apply(window,[999]);

輸出:888 999

4、示例下載

https://github.com/zhangguo5/javascript003.git

https://git.coding.net/zhangguo5/javascript_01.git

https://git.dev.tencent.com/zhangguo5/javascriptpro.git

5、視頻

http://www.bilibili.com/video/av17173253/

6、做業

6.一、請擴展String類型增長trim方法,實現去掉字符串首尾空格,如:

「   abc   」.trim();  //abc

6.二、請定義一個動物(Animal)類型,並定義屬性(name名稱,food食物),定義方法eat吃,在方法中輸出「小狗喜歡吃骨頭!」

定義貓與狗類型,繼承自Animal,增長show方法顯示名稱與喜歡的食物,完成測試。

相關文章
相關標籤/搜索