你們好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新......前端
- github:https://github.com/Daotin/Web
- 微信公衆號:Web前端之巔
- 博客園:http://www.cnblogs.com/lvonve/
- CSDN:https://blog.csdn.net/lvonve/
在這裏我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的項目。如今就讓咱們一塊兒進入 Web 前端學習的冒險之旅吧!git
原型鏈表示的是實例對象與原型對象之間的一種關係,這種關係是經過__proto__
原型來聯繫的。github
實例對象的原型 __proto__
指向的是該對象的構造函數中的原型對象 prototype,若是該對象的構造函數的 prototype 指向改變了,那麼實例對象中的原型 __proto__
的指向也會跟着改變。chrome
例如:微信
Person.prototype = new Cat();
由於 Person.prototype = {}; 能夠是一個對象,因此傳入另外一個對象的實例函數是能夠的,這時候 Person 的實例對象能夠訪問 Cat 原型 prototype 中的屬性和方法,而不能再訪問本身 Person 中原型的屬性和方法了。函數
實例對象的__proto__
指向的是構造函數的原型對象 prototype,因爲prototype也是個對象,因此也有 __proto__
,這個 __proto__
指向的是 Object 的 prototype,而 Object 的 prototype 裏面的 __proto__
指向的是 null。學習
示例:this
function Person() {} var per = new Person(); console.log(per.__proto__ === Person.prototype); // true console.log(Person.prototype.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // null
原型鏈圖示:.net
先看個案例:問下面程序有問題嗎?firefox
function Person() {} Person.prototype.eat = function () {}; function Student() {} Student.prototype.say = function () {}; Student.prototype = new Person(); var stu = new Student(); stu.say();
解答:stu.say(); 會報錯。由於 Student 的原型指向變成了 Person 的一個實例對象,Person 的實例對象鍾並無 say 方法,因此報錯。
解決辦法:在原型指向改變以後再添加原型方法。
function Person() {} Person.prototype.eat = function () {}; function Student() {} Student.prototype = new Person(); Student.prototype.say = function () {}; var stu = new Student(); stu.say();
PS:這個時候就不會報錯, Student 添加的原型方法的位置是一個匿名 Person 的實例對象中,這裏是一個 Person 的實例對象,不是全部的,因此當你再 new 一個 Person 的實例對象的時候,不會有 say 方法。
當實例對象訪問一個屬性的時候,會先從實例對象中找,找到了直接使用,找不到再到指向的原型對象中找,找到了使用,仍是找不到,則爲 undefined。
如何改變原型對象中的屬性的值呢?怎麼賦值的怎麼修改。
若是你使用 對象.屬性 = 值
的方式來賦值的話,若是這個屬性在實例對象中有的話,改變的是實例對象中屬性的值;若是實例對象中沒有這個屬性的話,則此次修改至關於給該實例對象添加了一個屬性,其指向的原型對象中相應的屬性的值並無被改變。
原型的第二個做用:繼承。目的也是節省內存空間。
經過改變子類原型的指向到父類的實例對象,能夠實現繼承。
案例:
// 父類:人 function Person(name, age) { this.name= name; this.age=age; } Person.prototype.eat = function () { console.log("eat()"); }; // 子類:學生 function Student(sex) { this.sex=sex; } // 子類繼承父類只須要改變子類原型的指向到父類的實例對象。 Student.prototype = new Person("Daotin", 18); Student.prototype.study = function () { console.log("study()"); }; var stu = new Student("male"); console.log(stu.name); // Daotin console.log(stu.age); // 18 stu.eat(); // eat()
缺陷1:在改變子類原型對象的指向的時候,屬性在初始化的時候就固定了,那麼每一個子類實例對象的值都固定了。
解決辦法:不須要子類原型的指向到父類的實例對象,只須要借用父類的構造函數。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log("eat()"); }; function Student(name, age, sex) { Person.call(this, name, age); this.sex = sex; } //Student.prototype = new Person("Daotin", 18); Student.prototype.study = function () { console.log("study()"); }; var stu = new Student("Daotin", 18, "male"); console.log(stu.name); console.log(stu.age); console.log(stu.sex); stu.eat(); // 不能訪問
Person.call(this, name, age);
第一個參數 this,表示當前對象,意思是當前對象呼叫 Person,將 name 和 age 傳過來,具體傳多少,我本身指定。這樣不一樣的子類,經過本身能夠設置不一樣的屬性。
缺陷2:stu.eat();
不能訪問了,就是父類原型方法不能繼承了。
解決辦法:組合繼承(原型方式繼承 + 借用構造函數繼承)
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log("eat()"); }; function Student(name, age, sex) { Person.call(this, name, age); // 借用父類構造函數,實現父類屬性的繼承 this.sex = sex; } Student.prototype = new Person(); // 不傳參數了,實現原型方法的繼承 Student.prototype.study = function () { console.log("study()"); }; var stu = new Student("Daotin", 18, "male"); console.log(stu.name); console.log(stu.age); console.log(stu.sex); stu.eat(); stu.study();
Student.prototype = new Person();
// 不傳參數了,實現原型方法的繼承。
Person.call(this, name, age);
// 借用父類構造函數,實現父類屬性的繼承。
就是把對象中須要共享的屬性和方法直接以遍歷的方式複製到了另外一個對象中。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.eat = function () { console.log("eat()"); }; var per = {}; // 循環拷貝 for(var key in Person.prototype) { per[key] = Person.prototype[key]; } console.log(per);
// 函數的聲明 if(true) { function f1() { console.log("f1()--1"); } } else { function f1() { console.log("f1()--2"); } } f1();
函數聲明若是放在 if-else- 裏面,chrome 和 firefox 輸出 f1()--1,IE8 下輸出 f1()--2,由於函數聲明會提早,第二個將第一個覆蓋了。
// 函數表達式 var func; if(true) { func = function () { console.log("f1()--1"); }; } else { func = function () { console.log("f1()--2"); }; } func();
函數表達式的方式,輸出都是 f1()--1。因此儘可能使用函數表達式。
function func() { console.log(this) // window } func();
正常狀況下是證正確的。
"use strict"; function func() { console.log(this) // window } window.func(); // 嚴格模式下必須加 window,由於他認爲函數是一個方法,方法必須經過對象來調用的。
2.一、函數也是對象,對象不必定是函數(好比:Math)。
只要有 __proto__
的就是對象;
只有要 prototype
的就是函數,由於函數纔會調用 prototype 屬性。
對象不必定是函數:好比 Math,中有 __proto__
,可是沒有 prototype
。
2.二、全部的函數都是由 Function 構造函數建立的實例對象。
既然函數是對象,那麼是什麼構造函數建立的呢?
var f1 = new Function("a", "b", "return a+b"); f1(1,2); // 上面至關於:函數的聲明 function f1(a, b) { return a+b; } f1(1,2); // 至關於:函數表達式 var f1 = function (a, b) { return a+b; } f1(1,2);
那麼 Function 是個什麼東西呢?
經查看是對象也是函數?而後查看它的 __proto__
指向的是 Object的原型對象。全部的對象指向的都是Object的原型對象。