在JavaScript中,咱們建立一個函數A(就是聲明一個函數), 那麼 js引擎 就會用構造函數Function
來建立這個函數。因此,全部的函數的constructor
屬性都指向 構造函數Function
。A.constructor === Function; //true
(函數自己並無這個屬性,後面介紹。記住,這裏是函數,重點,要考,哈哈) 而後會在內存中建立一個原型對象B,原型對象這個東西,看不見,摸不着,只能經過函數的 prototype
來獲取這個對象。( 即:prototype的屬性的值是這個對象 )。瀏覽器
對象的建立,有三種方式 一、字面量方式(又稱直接量)bash
啥是字面量呢?能夠理解爲,沒用new
出來的都屬於字面量建立。app
舉個栗子:函數
var c = {}; //c 是變量, {} 就是字面量了
複製代碼
字面量對象的建立過程跟函數的過程類似,當你聲明一個對象字面量時,js引擎就會構造函數 Object 來建立這個對象,因此 效果等同 new Object()
ui
二、構造器方式this
這個呢,就是用 new
建立。spa
栗子在此:prototype
var c = new Object();
複製代碼
三、Object.create3d
這是個ES5中新增的方法,這個方法是建立一個新對象,把它的__proto__指向傳進來的函數或者對象。究竟是怎麼回事呢,下面,我們來簡單實現下它的功能code
function create(proto, options){ //proto表示傳入的函數或者對象,options表示給子類添加的屬性
var obj = {};
obj.__proto__ = proto;
if(options == null){
return obj;
}
return Object.defineProperties(obj,options); //Object.defineProperties方法直接在一個對象上定義新的屬性或修改現有屬性,並返回該對象。因此這裏就直接return了
}
複製代碼
檢驗一下:
var a = function(){};
//自制的create
var b = create(a);
console.log(b.__proto__ === a); //true;
var c = create(a.prototype);
console.log(c.__proto__ === a.prototype); //true
//Object.create
var d = create(a);
console.log(d.__proto__ === a); //true;
var e = create(a.prototype);
console.log(e.__proto__ === a.prototype); //true
複製代碼
這裏說明一下,你能夠在別處看到的create的實現是這樣的:
//這裏就簡化第二個參數了。就寫第一個參數的實現
function create(proto){
function f(){};
f.prototype = proto;
return new f();
}
複製代碼
這個是兼容的寫法,由於,__proto__
屬性是非標準的,部分如今瀏覽器實現了該屬性。其實,你要是明白,new
到底幹了啥, 你就明白,這兩個實際是一個東西了。
__proto__
指向 new
後面構造函數 的原型對象。this
指向 實例對象。下面,手寫一個方法 實現new 的功能:
function New(func){ //func 指傳進來的構造函數,關於構造函數,我我的理解來就是用來生成一個對象實例的函數。
var obj = {}; //這裏就直接新建一個空對象了,不用new Object了,效果同樣的,由於我感受這裏講實現new的功能 再用 new 就不太好。
if(func.prototype != null){
obj.__proto__ = func.prototype;
}
func.apply(obj,Array.prototype.slice.call(arguments, 1));
//把func構造函數裏的this 指向obj對象,把傳進來的第二個參數開始,放入func構造函數裏執行,好比:屬性賦值。
return obj;
}
複製代碼
不知各位看客是否看明白了呢,簡單指明一下把
function create(proto){
var f = function(){};
f.prototype = proto;
return new f();
//就等於
function create(proto){
var f = function(){};
var obj = {};
f.prototype = proto;
obj.__proto__ = f.prototype;
return obj;
}
//就等於
function create(proto){
var obj = {};
obj.__proto__ = proto;
return obj;
}
//看明白了嗎, 就是用 function f 作了一下過渡。
//驗證
function a(){};
var b = create(a);
console.log(b.__proto__ === a); //true;
複製代碼
我想,一說到JavaScript的原型是使人奔潰的,其中prototype容易和__proto__二者的聯繫就太頭疼了,反正看圖比看字舒服。
我看大多數的教程都是把prototype
和 __proto__
放一塊兒講,我以爲仍是分開將比較好,原本就不是一個東西
這是個函數纔有的屬性,它保存着對其原型對像的引用,即指向原型對象。(任何函數都有原型對象。)它的原型對象是看不見,摸不着的,只能經過函數的prototype
屬性來獲取它。
好比
function A(){};
A.prototype; //這樣就獲取到了A的原型對象
複製代碼
總結:你看到prototype
,你就想着它對應着,它的原型對象就行。
每當你建立一個函數,js 就會 對應的生成一個原型對象。它只能被函數的prototype
屬性獲取到。(A.prototype
總體變現爲A
的原型對象)
這個是非標準的屬性,現代部分瀏覽器支持,好比,火狐,谷歌。對應的標準屬性是[[prototype]],這是個隱藏屬性,獲取不到。 這個屬性就是把全部的函數啊,對象啊,連成一條鏈的東西,咱們稱爲原型鏈。這條鏈的終點是Object.prototype.__proto__ === null
。
那它到底指向誰呢,我給了兩種記憶方式吧:
一、函數的__proto__
指向Function.protoype
,原型對象的__proto__
指向Object.prototype
的。字面量
和 new
出來的實例對象 ,指向其構造函數(誰 new 出來的)的prototype
, Object.create
建立的對象呢,就是上面說的,你給它誰,它就指向誰。
二、除了Object.create
建立的對象的__proto__
指向你給定的,原型對象的__proto__
指向Object.prototype
,其餘的__proto__
都是指向其構造函數的原型對象。(你要是看懂了上面的new
的實現,就應該明白爲啥了。 )
第二點注意:全部函數都是 構造函數 Function
建立出來的,因此,函數的構造函數 就是 Function
。
選這兩個中你喜歡的一個,對着下面的圖找答案:
constructor
屬性是原型對象獨有的,它指向的就是它的構造函數。上面的1、prototype中說,函數的prototype
屬性指向它的原型對象。此時的函數是構造函數了。因此函數 和 它的原型對像 以這兩個 屬性 保持着 相互聯繫。
function A(){};
A.prototype.constructor === A //true
Object.prototype.constructor === Object //true
複製代碼
那函數的constructor 和 普通對象的 constructor 是怎麼回事呢?
開頭說的function A(){};A.constructor
不知道你們有沒有點疑惑?不是原型對象纔有的嗎?
其實, 他們就是在一條原型鏈上的,也就是說,A 上沒有 constructor
屬性,它就會沿着原型鏈向上查找,到了 Object
的原型對象上,就找到了constructor
屬性,因而就能夠用 A.constructor
了.
好比:
var Person = function(name) {
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
var p = new Person("666");
console.log(p.getName()); //666
複製代碼
看這裏Person 構造函數裏是否是沒有getName 方法,可是 p 實例怎麼能夠用呢? 由於p實例 的__proto__
指向 的原型對象上有 getName 函數,因此 p 向原型鏈上查找到了 Person 的原型對象上, 它有getName 方法, 因而, p 就可使用這個方法了。
注意:因此,在找構造函數時,須要注意是在它的原型鏈上找,而不是原型對象上:
Object.constructor === Object.__proto__.constructor
//true
複製代碼
啥是構造函數?其實每一個函數都是構造函數,只是咱們通常把 生成實例對象 的函數 稱爲構造函數(經過new ,new 後面的就是構造函數),本質是函數。
好比:
var Person = function(name) {
this.name = name;
this.getName = function(){
return this.name;
}
}
var p1 = new Person("zdx");
console.log(p1.getName()); //zdx
var p2 = new Person("666");
console.log(p2.getName()) //666
console.log(Person.prototype.constructor === Person) ///true
//這裏的Person 是個函數對吧,而後外面用 new Person();
//建立了一個Person 的實例,此時,Person 就是一個構造函數了,也稱它爲類,咱們把類的首字母都大寫。
//由於,這個函數,使用 new 能夠構造無數個實例來。
複製代碼
說一下,怕有人不知道,實例就是實例對象,由於一個實例,它自己就是對象。
開始放大招了,哈哈:
function Person(name) {
this.name = name;
}
var p = new Person("zdx");
console.log(p.__proto__ === Person.prototype); //true
//p是實例對象, 它的構造函數是 Person,按照上面的所說的,它是new 出來的,因此指向它構造函數的prototype
console.log(Person.prototype.__proto__ === Object.prototype); //true
//Person.prototype 是原型對象, 因此指向 Object.prototype.
console.log(Person.__proto__ === Function.prototype); //true
//Person 是構造函數, 它的構造函數是Function, 因此它就指向 Function.prototype
console.log(Function.prototype.__proto__ === Object.prototype); //true
//Function.prototype 是原型對象, 因此指向 Object.prototype.
console.log(Object.prototype.__proto__ === null); //true
//這裏就是全部原型鏈的終端,原型鏈到這裏就沒了。
複製代碼
因此說js 萬物皆對象呢? 全部的函數啊,對象啊,實例啊,它的原型鏈最終都到了Object.prototype原型對象。
畫成圖就是醬樣子滴:
其實吧,有時候看圖也不同好,哈哈,你能夠按照我說的規則,本身不看圖畫一下。簡單來驗證一下
var a = {}; //等同與 new Object()
console.log(a.prototype); //undefined 對象沒有原型對象
console.log(a.__proto__ === Object.prototype); //true
var b = function(){};
console.log(b.prototype); //b的原型對像
console.log(b.__proto__ === Function.prototype);
var c = []; //等同於 new Array(),構造函數是Array
console.log(c.__proto__ === Array.prototype); //true
var d = ""; //等同於 new String(),構造函數是 String
console.log(d.__proto__ === String.prototype); //true
複製代碼
其實,原型鏈的根本做用就是爲了 屬性 的讀取。
上面簡單說過,當在一個 函數 或者 對象 上 讀取屬性時,它會先查找自身屬性,若是有,就直接返回,若是沒有呢,就會沿着原型鏈向上查找。
舉個簡單的栗子,每一個函數啊,對象啊,都有toString 方法,它是哪來的呢? 球都麻袋!(等等),不是屬性的讀取嗎。toString這個是方法(函數)呀!同窗,你頗有眼光嘛。事實上,咱們把屬性值爲函數的,稱之爲 方法。其實呢,你要了解這些方法是怎麼回事。
function test(){};
test.toString(); //"function test(){}"
console.log(test.hasOwnProperty("toString")); //false
console.log(test.__proto__.hasOwnProperty("toString")); //true
//這就找到了,這裏的hasOwnProperty 方法 是檢查 該屬性是否 是自身的屬性
//而 (函數的__proto__)test.__proto__ 都指向(等於) Function.prototype
console.log(test.__proto__ === Function.prototype); //true
console.log(Function.prototype.hasOwnProperty("toString")); //true
//看到這裏,明白了嗎。函數的內置方法(函數) 一部分是 Function.prototype 上的屬性;
//一部分? 是的,由於,原型鏈的終端 在 Object.prototype ;
//因此,在Object.prototype 上添加的屬性,方法, 函數也是可使用的;
//好比:
Object.prototype.say = function() { console.log(5666) };
function test(){};
test.say(); //5666
複製代碼
那麼屬性的賦值是怎麼一回事呢?
首先函數 或 對象 會查找 自身屬性, 若是有,就會 覆蓋該屬性 的值, 若是沒有,它就會建立 一個 自身的屬性,總之,賦值是不會對原型鏈進行查找。
function Person(){};
var p = new Person();
//若是你用的同一個窗口運行這個,結果多是上面的 5666,由於剛剛更改了該函數,您從新打開個瀏覽器窗口便可。
p.toString(); //"[object Object]"
//有疑惑嗎,其實,toString 這個方法, Function.prototype, 和 Object.prototype 都有;
Function.prototype.toString = function(){
console.log("我是Function原型對象的");
}
Person.toString(); //我是Function原型對象的
p.toString(); //"[object Object]"
//Object上的toString 方法並無改變。
複製代碼
運用 最多見的就是 繼承了。
function Person(name){ //父類(構造函數)
this.name = name;
}
Person.prototype.getName = function(){ //在父類的原型對象上添加方法
return this.name;
}
function Son(name,age){ //子類(構造函數)
this.name = name; //爲了減輕讀者壓力,就不使用 call 了, Person.call(this, name);
this.age = age;
}
Son.prototype = new Person(); //把子類(構造函數) 的原型對象 掛到 原型鏈上去。
Son.prototype.getAge = function(){
return this.age;
}
var s = new Son("zdx",666);
s.getName(); //Son.prototype 上沒有 getName方法,如今能使用了,就完成了 繼承。
複製代碼
須要解讀一下嘛?
Son.prototype = new Person();
//就等於
var obj = {};
obj.__proto__ = Person.prototype;
Son.prototype = obj;
//這樣,就把Son.prototype 掛到原型鏈上去了。
Son.prototype.__proto__ === Person.prototype //ture
複製代碼
而後Son 的實例對象 上使用方法 時,就沿着鏈查找, Son.prototype 沒有, 上級Person.prototype 上有。ok,繼承了。
簡單寫一個:
var jQuery = function(name) {
return new jQuery.fn.init();
}
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function(name) {
this.name = name;
},
each: function() {
console.log('each');
return this;
}
}
jQuery.fn.init.prototype = jQuery.fn;
複製代碼
估計看起來有點困難,沒事,咱們簡化一下
function jQuery(name){
return new init(name);
}
function init(name){
this.name = name;
}
jQuery.prototype = {
constructor: jQuery,
each: function(){
console.log('each')
}
}
init.prototype = jQuery.prototype;
複製代碼
看懂了嗎,你使用jQuery()
, 就至關於與 new jQuery()
; 其實 你 new init() 和 new jQuery(); 是同樣的;由於 init.prototype = jQuery.prototype
,因此它們的實例對象是同樣的。它這樣寫,就是爲了你使用方便,不須要你使用 new 來建立jq對象。
使用起來就是這樣的:
$();
//是否是比
new $();
//方便多了。
複製代碼
最後要說的就是,別把原型鏈和做用域鏈搞混了!!!哈哈哈