JavaScript學習總結(四)——this、原型鏈、javascript面向對象


一、this

在JavaScript中this表示:誰調用當前函數this就指向誰,不知道調用者時this指向window

JavaScript是由對象組成的,一切皆為對象,萬物皆為對象。this是一個動態的對象,根據調用的對象不同而發生變化,當然也可以使用call、apply修改this指向的對象。它代表函數運行時,自動生成的一個內部對象,只能在函數內部使用

1.0、猜猜答案

代碼如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JavaScript this</title>
    </head>
    <body>
        <h2>JavaScript this</h2>
        <input type="button" value="按鈕A" id="btnA"/>
        <input type="button" value="按鈕B" id="btnB" onclick="sayHello()"/>
        <script type="text/javascript">
            function sayHello(){
                alert("Hello Button!");
                console.log(this);
            };
            
            document.getElementById("btnA").onclick=sayHello;
            
        </script>
    </body>
</html>

請問輸出到控制台的結果是什么?相同嗎?輸出兩個什么對象?

答案:

<input type="button" value="按鈕A" id="btnA"/>
Window
View Code

1.1、JavaScript中函數與方法的區分

在面向過程的語言中我們習慣把完成某個特定功能的代碼塊稱為“函數”或“過程”,當然過程一般沒有返回值。在面向對象語言中我們把對象的功能稱為“方法”。但JavaScript是種介於面向對象與面向過程中間的語言,同樣的方法有時是函數,有時是方法,如下所示:

<script type="text/javascript">
            //1
            function show(){
                console.log("這是一個函數");
            }
            
            //2
            (function(){
                console.log("這是一個函數表達式");
            })();
            
            //3
            var obj1={
                show:function(){
                    console.log("這是一個方法");
                }
            };
            
            //4
            function obj2(){  //obj2是函數,構造函數
                this.show=function(){
                    console.log("這是一個方法,是一個表達式");
                }
            }
            var obj3=new obj2();
            
        </script>

可以簡單的認為如果調用時沒有通過對象沒有指定上下文則為函數,否則為方法。

1.2、指向全局對象

當在全部范圍內使用 this,它將會指向全局對象。一般是window對象,但全局對象不一定只有window,特別是在node.js環境中。作為函數調用時一般指向全局對象。

        <script type="text/javascript">
            var i=5;
            i=6;
            console.log(i);   //6
            console.log(window.i);  //6
            console.log(this.i);  //6
            console.log(this);  //window
        </script>

結果:

        <script type="text/javascript">
            var name="tom";
            console.log(this.name);      //頂層對象,一般為window    
            
            function show()
            {
                console.log(this.name);  //頂層對象,一般為window
                return function(){
                    console.log(this.name);  //頂層對象,一般為window,因為返回的是函數
                }
            }
            
            var f1=show();
            f1();
            
        </script>

運行結果:

1.3、作為方法調用

當函數作為方法調用時this指向調用該方法的對象。

            function show()
            {
                //當obj1.view()時this就是obj1,obj2.view()時this就是obj2
                console.log(this.name);
            }
            
            var obj1={name:"jack",view:show};
            obj1.view();
            
            var obj2={name:"mark",view:show};
            obj2.view();

運行結果:

示例代碼:

        <script type="text/javascript">
            var name="lucy";
            this.name="Mali";
            function show()
            {
                //當obj1.view()時this就是obj1,obj2.view()時this就是obj2
                console.log(this.name);
                
                return function(){
                    console.log(this.name);  //這里的this是調用時動態決定
                }
            }
            
            var obj1={name:"jack",view:show};
            obj1.view();
            
            var obj2={name:"mark",view:show};
            var f1=obj2.view();
            f1();  //因為f1屬於window(瀏覽器環境),調用f1時的this也就是window
            
        </script>

運行結果:

示例代碼:

        <script type="text/javascript">
            var name="lucy";
            this.name="Mali";
            function show()
            {
                //當obj1.view()時this就是obj1,obj2.view()時this就是obj2
                console.log(this.name);
                that=this;  //閉包
                return function(){
                    console.log(that.name);  //這里的that指向的是外層函數調用時的對象
                }
            }
            
            var obj1={name:"jack",view:show};
            obj1.view();
            
            var obj2={name:"mark",view:show};
            var f1=obj2.view();
            f1();            
        </script>

運行結果:

1.4、在構造函數中的this

構造函數中的this指向新創建的對象,new出誰this就是誰。

示例代碼:

<script type="text/javascript">
            this.name="吉娃娃";
            function Dog(name)
            {
                this.name=name;
                this.bark=function(){
                    console.log(this.name+"在叫,汪汪...");
                }
            }
            
            var dog1=new Dog("哈士奇");
            dog1.bark();
</script>

運行結果:

按照嚴格的語法,構造函數不應該返回值,但是JavaScript是允許構造方法返回值的,默認返回this,修改后的示例如下:

            this.name="吉娃娃";
            function Dog(name)
            {
                this.name=name;
                this.bark=function(){
                    console.log(this.name+"在叫,汪汪...");
                }
                return this.bark;
            }
            
            var dog1=new Dog("哈士奇");
            dog1();

運行結果:

 

1.5、指定this指向的對象

當調用方法是通過Function.call,或Function.apply時this為調用時指定的對象,如果沒有指定則為頂層對象,瀏覽器中是window。

示例代碼:

            this.name="伽啡";
            function Cat(name)
            {
                this.name=name;
            }
            var bark=function(n){
                console.log(this.name+"叫了"+(n||1)+"聲喵喵...");
            }
            
            var cat1=new Cat("波斯");
            var cat2=new Cat("龍貓");
            
            bark.call(cat1,5);  //調用時this是cat1
            bark.apply(cat2,[8]);  //調用時this是cat2
            bark.apply(window,[9]);  //調用時this是window
            bark.apply();  //調用時this是頂層對象

 

運行結果:

1.6、作為事件時的this

如果頁面中的元素與事件進行綁定時事件中的this一般指向網頁元素,如按鈕,文本框等。但是在元素中直接指定要執行的函數則this指向window頂層對象。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>this</title>
    </head>
    <body>
        <input type="button" value="按鈕A" class="btn"/>
        <input type="button" value="按鈕B" class="btn"/>
        <input type="button" value="按鈕C" class="btn"/>
        <input type="button" value="按鈕D" onclick="handler()"/>
        <!--handler中的this指向window或頂層對象-->
        <input type="button" value="按鈕E" id="btnE"/>
        <script type="text/javascript">
            var buttons = document.querySelectorAll(".btn");
            
            for (var i=0;i<buttons.length;i++) {
                //handler中的this指向按鈕對象
                buttons[i].onclick=handler;
            }
            
            function handler(){
                    alert(this.value);
            }
            
            //handler中的this指向btnE
            document.getElementById("btnE").addEventListener("click",handler,false);
        </script>
    </body>
</html>

當點擊A-C時的效果:

當點擊D時的效果:

 

當點擊按鈕E時的效果:

<!DOCTYPE html>
<html>

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

    <body>
        <input type="button" value="按鈕F的值" id="btnF" />
        <input type="button" value="按鈕G的值" id="btnG" onclick="app.show()"/>
        <script type="text/javascript">
            value="window的值"
            var app = {
                value: "app的值",
                show: function() {
                    alert(this.value); //如果show為事件,則this指向觸發事件的對象
                },
                init: function() {
                    //handler中的this指向btnE
                    document.getElementById("btnF").addEventListener("click", this.show, false);
                }
            };
            
            app.show();   //"app的值",show方法中的this指向app這個對象
            app.init();     //init中的this指向誰app對象
            
            
        </script>
    </body>
</html>

 加載時運行結果:

點擊按鈕F時的效果:

點擊按鈕G時的效果:

 

在HTML元素上直接指定事件嚴格來說都不能說是事件綁定,只能描述是當按鈕點擊完成后執行的函數。如果想將執行時的對象帶回,可以增加參數this。

1.7、小結

函數調用可以如下幾種基本形式:

1)、fun(x,y);

2)、obj.fun(x,y);

3)、fun.apply(obj,[x,y]);

4)、fun.call(obj,x,y);

第1,2種調用的方式最終將轉換成3,4方法,也就是說1,2只是一種簡化的語法糖,那么this就是apply與obj的第1個參數,指向調用時的上下文。

二、原型(prototype)與原型鏈

在javascript面向對象中關於原型(prototype)、原型鏈、__proto__、Function、Object等內容是較難理解的,先上幾張便於大家理解的圖:

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>
        <script type="text/javascript">
            //構造器,函數,對象
            function Cat(nickname, color) {
                //各個對象獨有的
                this.nickname = nickname;
                this.color = color;
            }
            
            //所有對象公有的
            Cat.prototype.life = 9;
            Cat.prototype.bark = function() {
                console.log(this.nickname, this.color, this.life, "喵喵...");
            }

            var c1 = new Cat("波斯", "綠色");
            c1.bark();
            var c2 = new Cat("加菲", "紅色");
            c2.bark();
        </script>
    </body>

</html>

控制台:

關系圖:

(1)、所有的函數/構造器都擁有prototype屬性,用於指向原型對象。

(2)、所有的函數即是構造器又是對象。

(3)、所有的對象都擁有__proto__非標准屬性。

(4)、所有函數(構造器)的__proto__都指向Function.prototype對象。

(5)、所有對象的__proto__指向它的構造器的prototype對象。

(6)、所有函數都是由Function構造出來的,Function自己構造了自己,Object是由Function構造出來的,Function是構造器。

(7)、所有對象的最終原型對象都指向了Object的prototype屬性,Object的prototype對象的__proto__屬性指向NULL。

(8)、所有prototype原型對象中的constructor屬性都指向其構造器。

(9)、原型對象prototype中的成員是所有對象共有的。

(10)、對象在查找成員時先找本對象自己的所有成員,再查找構造器的原型中的成員(向上再查找父類的成員,多步),最終查詢Object的成員。

 

JavaScript Object Layout:

Object Relationship Diagram with Inheritance

JavaScript是一種通過原型實現繼承的語言與別的高級語言是有區別的,像java,C#是通過類型決定繼承關系的,JavaScript是的動態的弱類型語言,總之可以認為JavaScript中所有都是對象,在JavaScript中,原型也是一個對象,通過原型可以實現對象的屬性繼承JavaScript的對象中都包含了一個" prototype"內部屬性,這個屬性所對應的就是該對象的原型

"prototype"作為對象的內部屬性,是不能被直接訪問的。所以為了方便查看一個對象的原型,Firefox和Chrome內核的JavaScript引擎中提供了"__proto__"這個非標准的訪問器(ECMA新標准中引入了標准對象原型訪問器"Object.getPrototype(object)")。

1.1、為什么需要prototype

現在有一個Student構造函數,通過new調用該構造函數可以創建一個新的對象,示例如下:

            //構造方法,用於創建學生對象
            function Student(name) {
                this.name = name;
            }
            
            var tom=new Student("tom");
            tom.show=function(){
                console.log(this.name);
            }
            
            var rose=new Student("rose");
            rose.show=function(){
                console.log(this.name);
            }
                        
            tom.show();
            rose.show();

運行結果:

上面的示例中tom與rose都需要show方法,創建對象后我們直接為兩個對象分別都增加了show方法,但是這樣做的弊端是如果再增加更多對象也需要添加show方法,最好的辦法是修改構造方法Student,如下所示:

            //構造方法,用於創建學生對象
            function Student(name) {
                this.name = name;
                this.show = function() {
                    console.log(this.name);
                }
            }

            var tom = new Student("tom");
            var rose = new Student("rose");

            tom.show();
            rose.show();

但是如果Student構造函數是一個內置的函數或是其它框架定義的類型,則修改就比較麻煩了,可以不修改源碼的情況擴展該對象的原型也可以達到目的,如下所示:

            //構造方法,用於創建學生對象
            function Student(name) {
                this.name = name;
            }
            
            //修改Student對象的原型,增加show方法
            Student.prototype.show = function() {
                console.log(this.name);
            }
            
            var tom = new Student("tom");
            var rose = new Student("rose");

            tom.show();
            rose.show();

在示例中我們並沒有修改Student這個構造函數而是修改了了Student的原型對象,類似它的父類,如下圖所示:

此時你也許會認為所有的Object都增加了show方法,這樣想是錯誤的,因為Student的原型是一個對象而不是像其它高級語中的類型,我們修改的只是Student的原型對象,不會影響其它的對象。

            var mark=new Object();
            mark.name="mark";
            
            var lucy={name:"lucy"};
            //mark.show();
            //lucy.show();

此處的兩個show方法是錯誤的。總之原型的主要作用就是為了實現繼承與擴展對象。

在JavaScript中,原型也是一個對象,通過原型可以實現對象的屬性繼承,JavaScript的對象中都包含了一個”[[Prototype]]”內部屬性,這個屬性所對應的就是該對象的原型。

“[[Prototype]]”作為對象的內部屬性,是不能被直接訪問的。所以為了方便查看一個對象的原型,Firefox和Chrome中提供了__proto__這個非標准(不是所有瀏覽器都支持)的訪問器(ECMA引入了標准對象原型訪問器”Object.getPrototype(object)”)。在JavaScript的原型對象中,還包含一個”constructor”屬性,這個屬性對應創建所有指向該原型的實例的構造函數

 

1.2、typeof與instanceof

1.2.1、typeof

在 JavaScript 中,判斷一個變量的類型可以用typeof,如:

             var str1="Hello";
             var str2=new String("Hello");
             
            console.log(typeof 1);
            console.log(typeof(true));
            console.log(typeof str1);
            console.log(typeof str2);
            console.log(typeof([1,2,3]));
            console.log(typeof Date);
            console.log(typeof undefined);
            console.log(typeof null);
            console.log(typeof zhangguo);

運行結果:

1)、數字類型, typeof 返回的值是 number。比如說:typeof(1),返回值是number
2)、字符串類型, typeof 返回的值是 string。比如typeof("123")返回值是string。
3)、布爾類型, typeof 返回的值是 boolean 。比如typeof(true)返回值是boolean。
4)、對象、數組、null 返回的值是 object 。比如typeof(window),typeof(document),typeof(null)返回的值都是object。
5)、函數類型,返回的值是 function。比如:typeof(eval),typeof(Date)返回的值都是function。
6)、不存在的變量、函數或者undefined,將返回undefined。比如:typeof(abc)、typeof(undefined)都返回undefined。

1.2.2、instanceof

使用 typeof 運算符時采用引用類型存儲值會出現一個問題,無論引用的是什么類型的對象,它都返回 "object"。ECMAScript 引入了另一個 Java 運算符 instanceof 來解決這個問題。instanceof 運算符與 typeof 運算符相似,用於識別正在處理的對象的類型。與 typeof 方法不同的是,instanceof 方法要求開發者明確地確認對象為某特定類型。

instanceof用於判斷某個對象是否被另一個函數構造。

例如:
var oStringObject = new String("hello world");
console.log(oStringObject instanceof String); // 輸出 "true"

             var str1="Hello";
             var str2=new String("Hello");
             
            console.log(str1 instanceof String);   //string false
            console.log(str2 instanceof String);  //object true
            
            console.log(1 instanceof Number); //false
            
            function Foo(){};
            
            var f1=new Foo();
            console.log(f1 instanceof Foo);

運行結果:

1.3、Function與Object

1.3.1、Function

函數就是對象,代表函數的對象就是函數對象。所有的函數對象是被Function這個函數對象構造出來的。Function是最頂層的構造器。它構造了系統中所有的對象,包括用戶自定義對象,系統內置對象,甚至包括它自已。這也表明Function具有自舉性(自已構造自己的能力)。這也間接決定了Function的call和constructor邏輯相同。每個對象都有一個 constructor 屬性,用於指向創建其的函數對象。

先來看一個示例:

            function Foo(a, b, c) {  //Foo這個構造函數由Function構造,函數也是對象
                return a * b * c;
            }
            
            var f1=new Foo();   //f1由Foo構造,Foo是一個構造函數,可以理解為類
            
            console.log(Foo.length);   //參數個數 3
            console.log(typeof Foo.constructor);  //function
            console.log(typeof Foo.call);  //function
            console.log(typeof Foo.apply);  //function
            console.log(typeof Foo.prototype);  //object object

運行結果:

 

函數與對象具有相同的語言地位
沒有類,只有對象
函數也是一種對象,所謂的函數對象
對象是按引用來傳遞的

            function Foo() {};
            var foo = new Foo();
            //Foo為foo的構造函數,簡單理解為類型
            console.log(foo instanceof Foo); // true
            
            //但是Function並不是foo的構造函數
            console.log(foo instanceof Function); // false
            
            //Function為Foo的構造函數
            alert(Foo instanceof Function); //true

上面的代碼解釋了foo和其構造函數Foo和Foo的構造函數Function的關系,Foo是由Function構造得到,可以簡單理解為,系統中有一個這樣的構造函數:

            function Function(name,body)
            {
            }
            
            var Foo=new Function("Foo","");

如上面例子中的構造函數,JavaScript中函數也是對象,所以就可以通過_proto_查找到構造函數對象的原型。

Function對象作為一個函數,就會有prototype屬性,該屬性將對應”function () {}”對象。

Function對象作為一個對象,就有__proto__屬性,該屬性對應”Function.prototype”,也就是說,”Function._proto_ === Function.prototype”。

在這里對“prototype”和“proto”進行簡單的介紹:

對於所有的對象,都有__proto__屬性,這個屬性對應該對象的原型。

對於函數對象,除了__proto__屬性之外,還有prototype屬性,當一個函數被用作構造函數來創建實例時,該函數的prototype屬性值將被作為原型賦值給所有對象實例(也就是設置實例的__proto__屬性)

1.3.2、Object

對於Object它是最頂層的對象,所有的對象都將繼承Object的原型,但是你也要明確的知道Object也是一個函數對象,所以說Object是被Function構造出來的。

alert(Function instanceof Function);//true 
alert(Function instanceof Object);//true 
alert(Object instanceof Function);//true 

function Foo() {};
var foo = new Foo();
alert(foo instanceof Foo); // true
alert(foo instanceof Function); // false
alert(foo instanceof Object); // true
alert(Foo instanceof Function); // true
alert(Foo instanceof Object); // true

JavaScript 原型鏈

            function A() {this.x="x";};
            var a=new A();
            
            function B() {this.y="y"};
            B.prototype=a;
            var b=new B();
            
            function C() {this.z="z"};
            C.prototype=b;
            var c=new C();
            
            console.log(c instanceof C);
            console.log(c instanceof B);
            console.log(c instanceof A);
            console.log(c instanceof Object);
            
            console.log(c.x+","+c.y+","+c.z);

運行結果:

當查找一個對象的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達原型鏈的頂部(也就是 Object.prototype),如果仍然沒有找到指定的屬性,就會返回 undefined。

Object對象本身是一個函數對象。既然是Object函數,就肯定會有prototype屬性,所以可以看到”Object.prototype”的值就是”Object {}”這個原型對象。反過來,當訪問”Object.prototype”對象的”constructor”這個屬性的時候,就得到了Obejct函數。
另外,當通過”Object.prototype._proto_”獲取Object原型的原型的時候,將會得到”null”,也就是說”Object {}”原型對象就是原型鏈的終點了。

1.4、通過prototype擴展對象

JavaScript內置了很多對象,如Array、Boolean、Date、Function、Number、Object、String 

假設我們想給String類型增加一個repeat方法,實現重復字符,如"a".rpt(),則將輸出aa,"a".rpt(5),輸出“aaaaa”,因為String是JavaScript中內置的對象,可以通過修改該對象的原型達到目的:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>prototype原型</title>
    </head>
    <body>
        <script type="text/javascript">
            var v="hi";
            
            String.prototype.rpt=function(n)
            {
                n=n||2;
                var temp="";
                for(var i=0;i<n;i++) temp+=this;
                return temp;
            }
            
            console.log(v.rpt());
            console.log(v.rpt(10));
        </script>
    </body>
</html>

運行結果:

示例中給String對象的原型增加了一個rpt方法,所有的String都是衍生自String.prototype,那所有的String對象都將獲得rpt方法。

            //擴展String類型,增加trim方法用於刪除字符串的首尾空格
            String.prototype.trim=function()
            {
                return this.replace(/^\s+|\s+$/igm,'');
            }
            
            console.log("[begin]"+"  Hello JavaScript  ".trim()+"[end]");

運行結果:

為了擴展更加方便,可以修改Function的原型,給每一個函數衍生的對象增加方法method用於擴展。

            //修改函數對象的原型,添加method方法,擴展所有的函數
            Function.prototype.method=function(name,fun){
                //如果當前擴展的函數的原型中不包含名稱為name的對象
                if(!this.prototype[name]){
                    //添加
                    this.prototype[name]=fun;
                }
            }
            
            String.method("show",function(){
                console.log("輸出:"+this);
            });
            
            "Hello".show();
            "JavaScript".show();

運行結果:

1.5、通過prototype調用函數

我們可以通過對象調用某個方法,因為這個對象的原型中已經定義好了該方法,其實我們通過原型也可以直接調用某個方法,有些方法只存在原型中,只有當前類型關聯了該原型才可以獲得該方法,但有時我們需要使用該方法去處理非該原型下的對象,如:

            function add(x,y,z)
            {
                var array1=[].slice.call(arguments,0,3);
                console.log(array1);
                var array2=Array.prototype.slice.apply(arguments,[0,3]);
                console.log(array2);
            }
            add(1,2,8,9,10);

運行結果:

示例代碼:

            var str1 = "Hello JavaScript";
            console.log(str1.toUpperCase()); //傳統的調用辦法

            var str2=String.prototype.toUpperCase.apply(str1); //通過原形直接調用
            console.log(str2);
            
            var str3=String.prototype.toUpperCase.call(str1); //通過原形直接調用
            console.log(str3);
            
            var array1=[2,3,5,7,8,9,10];
            var array2=array1.slice(1,3);
            console.log(array2);
            
            var slice=Array.prototype.slice;
            console.log(slice.apply(array1,[1,3]));//通過原形直接調用
            console.log(slice.call(array1,1,3));

運行結果:

練習:擴展Date,增加一個顯示中文日期的方法,如:new Date().trandition(),輸出2016年08月09日 23點15分18秒;使用prototype的方式間接調用該方法。

1.6、prototype和__proto__的區別

prototype是函數才有的屬性,prototype本身就是一個對象,prototype對象中擁有constractor構造器(該構造器反向指回函數)

__proto__是對象帶有屬性,用於訪問創建對象的類型所對應的原型

字面量創建的對象(o={})或Object創建的對象__proto__指向Object.prototype

示例:

var a = {};
console.log(a.prototype);  //undefined
console.log(a.__proto__);  //Object {}

var b = function(){}
console.log(b.prototype);  //b {}
console.log(b.__proto__);  //function() {}
<!DOCTYPE html>
<html>

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

    <body>
        <script>
            //構造方法,類,函數
            function Foo(name) {
                this.name = name;
            }

            var f1 = new Foo("zhangguo");

            //prototype是函數才有的屬性,prototype本身就是一個對象,prototype對象中擁有constractor構造器(該構造器反向指回函數)
            console.log(f1.prototype);  //undefined
            console.log(Foo.prototype);  //function Foo.prototype
            //__proto__是對象帶有屬性,用於訪問創建對象的類型所對應的原型
            console.log(f1.__proto__);   //function Foo.prototype
            console.log(Foo.__proto__);  //function(){}
            //Foo.__proto__ 就是Function.prototype
            
            console.log(Foo.__proto__===Function.prototype);  //true
            console.log(Foo.prototype===f1.__proto__);  //true
            
            
            //字面量創建的對象(o={})或Object創建的對象__proto__指向Object.prototype
            var o={};
            console.log(o.__proto__===Object.prototype);  //true
            var obj=new Object();
            console.log(obj.__proto__===Object.prototype);  //true
            var obj2=Object.create({});
            console.log(obj2.__proto__===Object.prototype);  //false
            
            //            console.log(obj1.prototype);
            //            console.log(obj1.__proto__);
            //            console.log(Foo.prototype);
        </script>
    </body>

</html>

結果:

1.7、__proto__屬性指向

/*1、字面量方式*/
var a = {};
console.log(a.__proto__);  //Object {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*2、構造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}

console.log(a.__proto__ === a.constructor.prototype); //true

/*3、Object.create()方式*/
var a1 = {a:1}
var a2 = Object.create(a1);
console.log(a2.__proto__); //Object {a: 1}

console.log(a.__proto__ === a.constructor.prototype); //false(此處即為圖1中的例外情況)

 示例:

<!DOCTYPE html>
<html>

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

    <body>
        <script>
            //構造方法,類,函數
            function Foo(name) {
                this.name = name;
            }

            var f1 = new Foo("zhangguo");

            console.log(f1.constructor === Foo); //true
            console.log(f1.__proto__ === Foo.prototype); //true

            /*1、字面量方式*/
            var a = {};
            //a的__proto__是指向了a的構造器所指向的原型
            //字面量創建的對象的__proto__指向了Object的原型
            console.log(a.__proto__ === a.constructor.prototype); //true

            /*2、構造器方式*/
            var A = function() {};

            var a = new A();

            console.log(a.__proto__); //A的原型

            console.log(a.__proto__ === a.constructor.prototype); //true
            console.log(a.__proto__ === A.prototype); //true

            /*3、Object.create()方式*/
            var a1 = {a: 1}
            var a2 = Object.create(a1);
            
            console.log(a2.__proto__===a1);   //true,a1就是a2的原型(父類)
        </script>
    </body>

</html>

結果:

1.8、原型鏈

示例:

var A = function(){};
var a = new A();
console.log(a.__proto__); //A {}(即構造器function A 的原型對象)
console.log(a.__proto__.__proto__); //Object {}(即構造器function Object 的原型對象)
console.log(a.__proto__.__proto__.__proto__); //null

示例:

<!DOCTYPE html>
<html>

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

    <body>
        <script>
            //構造方法,類,函數
            function Foo(name) {
                this.name = name;
            }

            var f1 = new Foo("zhangguo");
            //1、var f1={};
            //2、f1.__proto__=Foo.prototype;
            //3、Foo.call(f1,"zhangguo");

            //在Foo的原型中添加屬性age
            Foo.prototype.age = 80;
            Foo.prototype.show = function() {
                console.log("name:" + this.name + ",age:" + this.age);
            }

            var f2 = new Foo("zhangsan");

            console.log(f1.age == f2.age);
            f1.show();
            f2.show();

            var A = function() {};
            var a = new A();
            console.log(a.__proto__); //A {}(即構造器function A 的原型對象)
            console.log(a.__proto__.__proto__); //Object {}(即構造器function Object 的原型對象)
            console.log(a.__proto__.__proto__.__proto__); //null
        </script>
    </body>

</html>

結果:

當查找一個對象的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達原型鏈的頂部(也就是 Object.prototype),如果仍然沒有找到指定的屬性,就會返回 undefined。

function Person(name, age){ 
    this.name = name; 
    this.age = age; 
  } 
Person.prototype.MaxNumber = 9999;
Person.__proto__.MinNumber = -9999;
var will = new Person("Will", 28); 
console.log(will.MaxNumber); // 9999 
console.log(will.MinNumber); // undefined 

在這個例子中分別給”Person.prototype “和” Person.proto”這兩個原型對象添加了”MaxNumber “和”MinNumber”屬性,這里就需要弄清”prototype”和”proto”的區別了。

“Person.prototype “對應的就是Person構造出來所有實例的原型,也就是說”Person.prototype “屬於這些實例原型鏈的一部分,所以當這些實例進行屬性查找時候,就會引用到”Person.prototype “中的屬性。

對象創建方式影響原型鏈

var July = { 
   name: "張三", 
   age: 28, 
   getInfo: function(){ 
     console.log(this.name + " is " + this.age + " years old"); 
   }
 } 
console.log(July.getInfo()); 

當使用這種方式創建一個對象的時候,原型鏈就變成下圖了. July對象的原型是”Object.prototype”也就是說對象的構建方式會影響原型鏈的形式。

1.9、構造對象

在JavaScript中,每個函數 都有一個prototype屬性,當一個函數被用作構造函數來創建實例時,這個函數的prototype屬性值會被作為原型賦值給所有對象實例(也就是設置 實例的`__proto__`屬性),也就是說,所有實例的原型引用的是函數的prototype屬性。(只有函數對象才會有這個屬性!)

使用new關鍵字調用函數(new ClassA(…))的具體步驟:

1. 創建空對象;

var obj = {};

2. 設置新對象的constructor屬性為構造函數,設置新對象的__proto__屬性指向構造函數的prototype對象;

obj.__proto__ = ClassA.prototype;

3. 使用新對象調用構造函數,函數中的this被指向新實例對象:

ClassA.call(obj);  //{}.構造函數();          

4. 將初始化完畢的新對象地址,保存到等號左邊的變量中

示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            
            function Car(name){
                this.name=name;
            }
            
            //1、直接創建
            var c1=new Car("BYD");
            
            //2、實際創建對象的步驟
            //2.1、創建空的對象字面量
            var c2={};
            //2.2、將對象的__proto__指向構造器的原型對象(prototype)
            c2.__proto__=Car.prototype;
            //2.3、調用構造器,初始化創建的對象
            Car.call(c2,"BYD");
            //2.4、將創建的對象地址賦值給變量(返回給左邊的變量)
        </script>
    </body>
</html>

結果:

 

注意:若構造函數中返回this或返回值是基本類型(number、string、boolean、null、undefined)的值,則返回新實例對象;若返回值是引用類型的值,則實際返回值為這個引用類型。

new 的過程分為三步  

var p = new Person('張三',20);

1. var p={}; 初始化一個對象p。

2. p._proto_=Person.prototype;,將對象p的 __proto__ 屬性設置為 Person.prototype

3. Person.call(p,”張三”,20);調用構造函數Person來初始化p。

1、prototype是函數才有的屬性

2、__proto__是每個對象都有的屬性,它不是W3C的標准屬性,它指向prototype

3、__proto__可以理解為“構造器原型”,__proto__===constructor.prototype(Object.create()創建的對象不適用)

4、當js引擎查找對象的屬性時,先查找對象本身是否存在該屬性,如果不存在會在原型鏈上查找,但不會查找自己的prototype

5、函數的原型對象constructor默認指向函數本身,原型對象除了有原型屬性外,為了實現繼承,還有一個原型鏈指針__proto__,該指針指向上一層的原型對象,而上一層的原型對象的結構依然類似,這樣利用__proto__一直指向Object的原型對象上,而Object的原型對象用Object.prototype.__proto__ = null表示原型鏈的最頂端,如此變形成了javascript的原型鏈繼承,同時也解釋了為什么所有的javascript對象都具有Object的基本方法。

引用

function Foo() {}
var foo = new Foo();
console.log(foo.prototype);// undefined
console.log(foo.__proto__ === Foo.prototype);// true
console.log(Foo.prototype);// [object Object]
console.log(Foo.prototype.prototype);// undefined

解釋

只有函數對象有 prototype 屬性(一般對象自己加的不算)

1、 foo 是 Foo 的一個實例,但是不是一個函數,所以沒有prototype;Foo是Function的一個實例,而Function是一個函數,他的實例Foo也是一個函數,所以他們都有prototype。此外Object Array RegExp等也是函數。Math就僅僅是一個new Object() ,不是函數。

2、構造函數的prototype,默認情況下就是一個new Object()還額外添加了一個constructor屬性。所以說默認是沒有prototype只有__proto__的。
除了Object.prototype這個對象,其他所有的對象都會有__proto__屬性,之后函數才會有prototype屬性。

在創建對象的時候會自動創建一個__proto__屬性,指向它構造函數的prototype,當訪問這個對象的屬性的時候會順帶訪問__proto__中對應的屬性,也就是構造函數prototype這樣實現了繼承。
只有創建函數的時候才會創建一個prototype屬性,目的就是為了完成上面的繼承方式。

圖(橙色箭頭是初始的關系,綠色是執行var Fish = new Fu...創建,藍色是執行f1= new Fish()創建。)

這樣f1 就可以通過__proto__ 訪問 Fish.prototype中的屬性(當然這是程序執行的時候自動查找的)。Fish就可以訪問 Function.prototype定義的屬性。所有對象都可以訪問Object.prototype 中的屬性。

1.10、小結

1、所有的函數/構造器都擁有prototype屬性,原型一定是對象

2、所有原型對象擁有constructor屬性指回其構造函數,原型中存放所有構造器創建的對象共有成員

3、JavaScript中通過原型鏈實現繼承,將對象與對象串聯起來,查詢成員時由近到遠

4、所有的對象都將指向其構造器的原型對象,默認不允許直接訪問,但chrome等瀏覽器中可以通過__proto__訪問到(所有對象的__proto_指向其構造器的原型對象)

5、JavaScript中一切都是對象,函數創建了對象其本身又是對象,對象都指定原型

6、所有的函數都是Function創建出來的,Function自己創建了自己

7、Object函數是Function創建出來的,Object函數的__proto__指向Function的原型對象

8、Function的原型對象的__proto__指向Object的原型對象,Object的原型對象指向了null,這是原型鏈的盡頭

三、JavaScript面向對象(OOP)

3.1、封裝

因為在JavaScript中沒有訪問修飾符,也沒有塊級作用域,this所定義的屬性默認對外就是公開訪問的,如下示例:

            function Animal(){
                this.name="動物";
                this.weight=0;  //重量
            }
            
            var cat=new Animal();
            cat.name="貓";
            cat.weight=-90;
            console.log(cat);

輸出:

weight是-90,不正確,封裝:

<!DOCTYPE html>
<html>

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

    <body>
        <script type="text/javascript">
            function Animal() {
                this.name = "動物";
                var weight = 0; //重量
                this.getWeight = function() { //
                    return weight;
                };
                this.setWeight = function(_weight) { //
                    if(_weight > 0) {
                        weight = _weight;
                    } else {
                        throw "動物的重量必須為正數";
                    }
                }
            }

            var cat = new Animal();
            cat.name = "";
            try{
                cat.setWeight(-90);
            }catch(e){
                console.log(e);
            }
            console.log(cat);
        </script>
    </body>

</html>

結果:

            function Animal()
            {
                this.name="動物";
                this.getName=function(){
                    return this.name;
                }
                this.setName=function(name){
                    this.name=name;
                }
                this.bark=function(){
                    console.log(this.name+"在叫...");
                }
            }
            var animal=new Animal();
            animal.setName("小狗");
            animal.bark();

這里定義的一個構造函數,將name封裝成屬性,Animal函數本身用於封裝動物的屬性與行為,運行結果:

3.2、繼承

JavaScript的繼承的實現主要依靠prototype(原型)來實現,再增加兩個動物,如狗與貓:

            function Animal()
            {
                this.name="動物";
                this.getName=function(){
                    return this.name;
                }
                this.setName=function(name){
                    this.name=name;
                }
                this.bark=function(){
                    console.log(this.name+"在叫...");
                }
            }
            
            function Dog(){
                this.hunt=function(){console.log("狗在捕獵");};
            }
            //指定Dog構造函數的原型對象,簡單理解為父類
            Dog.prototype=new Animal();
            
            function Cat(){
                this.hunt=function(){console.log("貓在抓老鼠");};
            }
            Cat.prototype=new Animal();
            
            var dog=new Dog();
            dog.bark();
            dog.hunt();
            
            var cat=new Cat();
            cat.bark();
            cat.hunt();

運行結果:

3.3、多態

java與C#中的多態主要體現在重載與重寫上,因為JavaScript是弱類型的,類方法參數是動態指定的所以並沒有真正意義上的重載,只能在方法中判斷參數達到目的。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JavaScript面向對象</title>
    </head>
    <body>
        <script type="text/javascript">
            function Animal()
            {
                this.name="動物";
                this.getName=function(){
                    return this.name;
                }
                this.setName=function(name){
                    this.name=name;
                }
                this.bark=function(){
                    console.log(this.name+"在叫...");
                }
            }
            
            function Dog(){
                this.hunt=function(){console.log("狗在捕獵");};
            }
            //指定Dog構造函數的原型對象,簡單理解為父類
            Dog.prototype=new Animal();
            //重寫原型對象中的bark方法
            Dog.prototype.bark=function(){
                console.log("汪汪...");
            }
            
            function Cat(){
                this.hunt=function(){console.log("貓在抓老鼠");};
            }
            Cat.prototype=new Animal();
            //重寫原型對象中的bark方法
            Cat.prototype.bark=function(){
                console.log("喵喵...");
            }
            
            function play(animal)
            {
                animal.bark();
                animal.hunt();
            }
            
            var dog=new Dog();
            var cat=new Cat();
            play(dog);
            play(cat);
        </script>
    </body>
</html>

運行結果:

 

示例:

<!DOCTYPE html>
<html>

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

    <body>
        <script type="text/javascript">
            //二種重寫的辦法
            function Animal(name) {
                this.name = name || "動物";
                this.bark = function() {
                    console.log(this.name + "叫了:嗷嗷...");
                }
            };

            function Cat() {
                this.hunt = function() {
                    console.log(this.name + "正在捕老鼠!");
                }
            }
            Cat.prototype = new Animal("");
            Cat.prototype.bark = function() {
                console.log("喵喵");
            }
            var cat = new Cat();
            cat.hunt();
            cat.bark();

            function Dog() {
                this.hunt = function() {
                    console.log(this.name + "正在捕獵!");
                }
                this.bark = function() {
                    console.log("汪汪");
                }
            }
            //讓Dog繼承Animal,修改原型的指向
            Dog.prototype = new Animal("");
            var dog = new Dog();
            dog.hunt();
            dog.bark();

            //重載的辦法
            function add(n) {
                if(typeof n === "number") {
                    return ++n;
                }
                if(n instanceof Array){
                    var sum=0;
                    for(var i=0;i<n.length;i++){
                        sum+=n[i];
                    }
                    return sum;
                }
            }
            console.log(add(100));
            console.log(add([1, 2, 3, 4, 5]));
        </script>
    </body>

</html>

結果:

 

練習:請使用javascript完成一個簡單工廠設計模式。

四、示例下載

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

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

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

五、視頻

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM