摘要:也就是說,不必在構(gòu)造函數(shù)中添加定義對(duì)象信息,而是可以直接將這些信息添加到原型中。子類的屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。以前,這些原生構(gòu)造函數(shù)是無法繼承的。
面向?qū)ο?/b>
js是一門基于對(duì)象的語言。js中的一切皆對(duì)象;
console.log(Object.prototype.toString.call(123)) //[object Number] console.log(Object.prototype.toString.call("123")) //[object String] console.log(Object.prototype.toString.call(undefined)) //[object Undefined] console.log(Object.prototype.toString.call(true)) //[object Boolean] console.log(Object.prototype.toString.call({})) //[object Object] console.log(Object.prototype.toString.call([])) //[object Array] console.log(Object.prototype.toString.call(function(){})) //[object Function]
new function
var a = function () {}; console.log(typeof a);//function var b = new function () {}; console.log(typeof b);//object var c = new Function (); console.log(typeof c);//function new function 是一個(gè)JavaScript中用戶自定義的對(duì)象 var obj = function (name) { this.name = name; }; var b = new obj("aaa")=o == Object.create(obj.prototype);; console.log(b.name); // 創(chuàng)建一個(gè)以另一個(gè)空對(duì)象為原型,且擁有一個(gè)屬性p的對(duì)象 o = Object.create({}, { p: { value: 42 } }) // 省略了的屬性特性默認(rèn)為false,所以屬性p是不可寫,不可枚舉,不可配置的: o.p = 24 o.p //42
私有變量和函數(shù)
在函數(shù)內(nèi)部定義的變量和函數(shù),叫局部(內(nèi)部)變量和函數(shù),如果不對(duì)外提供接口,外部是無法訪問到的。
function Box(){ var color = "blue"; //私有變量 var fn = function(){} //私有函數(shù) } var obj = new Box(); alert(obj.color); //彈出 undefined,訪問不到私有變量 alert(obj.fn); //同上
靜態(tài)變量和函數(shù)
定義一個(gè)函數(shù)后加"."來添加的屬性和函數(shù),該函數(shù)可以訪問到,但實(shí)例訪問不到。
function Obj(){}; Obj.num = 72; //靜態(tài)變量 Obj.fn = function() { } //靜態(tài)函數(shù) alert(Obj.num); //72 alert(typeof Obj.fn) //function var t = new Obj(); alert(t.name); //undefined alert(typeof t.fn); //undefined
實(shí)例變量和函數(shù)
function Box(){ this.a=[]; //實(shí)例變量 this.fn=function(){} //實(shí)例方法 } var box1=new Box(); box1.a.push(1); box1.fn={}; console.log(box1.a); //[1] console.log(typeof box1.fn); //object var box2=new Box(); console.log(box2.a); //[] console.log(typeof box2.fn); //function
在box1中修改了a和fn,而在box2中沒有改變,由于數(shù)組和函數(shù)都是對(duì)象,是引用類型,這就說明box1中的屬性和方法與box2中的屬性與方法雖然同名但卻不是一個(gè)引用,而是對(duì)Box對(duì)象定義的屬性和方法的一個(gè)復(fù)制。
ES5 構(gòu)造函數(shù)構(gòu)造函數(shù)(constructor),其實(shí)就是一個(gè)普通函數(shù),但是內(nèi)部使用了this變量,對(duì)構(gòu)造函數(shù)使用new運(yùn)算符,就能生成實(shí)例,并且this變量會(huì)綁定在實(shí)例對(duì)象上。
function Cat(name,color){ this.name=name; this.color=color; } Cat.prototype.type=function(){}; var cat1=new Cat()
這時(shí)cat1會(huì)自動(dòng)含有一個(gè)constructor屬性,指向它們的構(gòu)造函數(shù)。
alert(cat1.constructor==Cat);//true alert(cat instanceof Cat);//true
js提供了一個(gè)instanceof運(yùn)算符,用來檢驗(yàn)cat1是否是Cat的實(shí)例對(duì)象。
原型對(duì)象與實(shí)例對(duì)象之間的關(guān)系。構(gòu)造函數(shù)(constructor):每new生成一個(gè)實(shí)例,就相當(dāng)于在內(nèi)存上又復(fù)制了一次
原型對(duì)象(prototype):而portotype,所有的實(shí)例都只指向一個(gè)內(nèi)存地址,用于不變的屬性和方法
不管是構(gòu)造函數(shù)內(nèi)部還是原型對(duì)象,里面的this在沒有new之前都指向該構(gòu)造函數(shù)Cat
Cat.prototype.constructor===Cat //true alert(Cat.prototype.isPrototypeof(cat1)) //true
isPrototypeOf()用來判斷某個(gè)prototype對(duì)象和某個(gè)實(shí)例之間的關(guān)系
alert(cat1.hasOwnProperty("name")); // true alert(cat1.hasOwnProperty("type")); // false
hasOwnProperty()用來判斷某個(gè)屬性到底是本地屬性,還是繼承自prototype對(duì)象的屬性。本地為true
in運(yùn)算符用來判斷,某個(gè)實(shí)例是否含有某個(gè)屬性,不管是不是本地屬性。還可以用來遍歷某個(gè)對(duì)象的所有屬性。
alert("name" in cat1);//true for(var i in cat1){alert("cat1["+i+"]="+cat1[i])}繼承
構(gòu)造函數(shù)綁定
function Cat(name,color){ Animal.apply(this, arguments);//等于是把父類的實(shí)例屬性復(fù)制了一份給子類實(shí)例裝上了,占內(nèi)存 this.name = name; this.color = color; } var cat1 = new Cat("大毛","黃色"); alert(cat1.species); // 動(dòng)物
利用prototype
Cat.prototype = new Animal(); //由于prototype 引用類型指向同一個(gè)地址會(huì)影響其它實(shí)例,而且不能向父傳參 Cat.prototype.constructor = Cat; //把prototype指向原來的構(gòu)造函數(shù)
組合繼承
function Cat(){ Animal.call(this); } Cat.prototype = new Animal(); //比較常用,占內(nèi)存
通過空對(duì)象
function extend(Child, Parent) { var F = function(){}; //利用一個(gè)空對(duì)象去轉(zhuǎn)接prototype,而且空對(duì)象幾乎不占內(nèi)存,不會(huì)影響父對(duì)象 F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; //糾正回來它的構(gòu)造函數(shù)指向 Child.uber = Parent.prototype; //輔助屬性,可以直接調(diào)用父的方法 } function Animal(){ } Animal.prototype.species = "動(dòng)物"; function Cat(name,color){ this.name=name this.color=color } extend(Cat,Animal); var cat1 = new Cat("大毛","黃色"); alert(cat1.species); // 動(dòng)物
拷貝繼承
function extend2(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { //循環(huán)父的原型方法給到子 c[i] = p[i]; } c.uber = p; } extend2(Cat, Animal); var cat1 = new Cat("大毛","黃色"); alert(cat1.species); // 動(dòng)物原型和原型鏈
私有變量和函數(shù)
在函數(shù)內(nèi)部定義的變量和函數(shù),叫局部(內(nèi)部)變量和函數(shù),如果不對(duì)外提供接口,外部是無法訪問到的。
function Box(){ var color = "blue"; //私有變量 var fn = function(){} //私有函數(shù) } var obj = new Box(); alert(obj.color); //彈出 undefined,訪問不到私有變量 alert(obj.fn); //同上
靜態(tài)變量和函數(shù)
定義一個(gè)函數(shù)后加"."來添加的屬性和函數(shù),該函數(shù)可以訪問到,但實(shí)例訪問不到。
function Obj(){}; Obj.num = 72; //靜態(tài)變量 Obj.fn = function() { } //靜態(tài)函數(shù) alert(Obj.num); //72 alert(typeof Obj.fn) //function var t = new Obj(); alert(t.name); //undefined alert(typeof t.fn); //undefined
實(shí)例變量和函數(shù)
function Box(){ this.a=[]; //實(shí)例變量 this.fn=function(){} //實(shí)例方法 } var box1=new Box(); box1.a.push(1); box1.fn={}; console.log(box1.a); //[1] console.log(typeof box1.fn); //object var box2=new Box(); console.log(box2.a); //[] console.log(typeof box2.fn); //function
在box1中修改了a和fn,而在box2中沒有改變,由于數(shù)組和函數(shù)都是對(duì)象,是引用類型,這就說明box1中的屬性和方法與box2中的屬性與方法雖然同名但卻不是一個(gè)引用,而是對(duì)Box對(duì)象定義的屬性和方法的一個(gè)復(fù)制。
我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,這個(gè)對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法。那么,prototype就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象實(shí)例的原型對(duì)象。
使用原型的好處是可以讓對(duì)象實(shí)例共享它所包含的屬性和方法。也就是說,不必在構(gòu)造函數(shù)中添加定義對(duì)象信息,而是可以直接將這些信息添加到原型中。使用構(gòu)造函數(shù)的主要問題就是每個(gè)方法都要在每個(gè)實(shí)例中創(chuàng)建一遍。
在JavaScript中,一共有兩種類型的值,原始值和對(duì)象值。每個(gè)對(duì)象都有一個(gè)內(nèi)部屬性 prototype ,我們通常稱之為原型。原型的值可以是一個(gè)對(duì)象,也可以是null。如果它的值是一個(gè)對(duì)象,則這個(gè)對(duì)象也一定有自己的原型。這樣就形成了一條線性的鏈,我們稱之為原型鏈。
函數(shù)可以用來作為構(gòu)造函數(shù)來使用。另外只有函數(shù)才有prototype屬性并且可以訪問到,但是對(duì)象實(shí)例不具有該屬性,只有一個(gè)內(nèi)部的不可訪問的__proto__屬性。__proto__是對(duì)象中一個(gè)指向相關(guān)原型的神秘鏈接。按照標(biāo)準(zhǔn),__proto__是不對(duì)外公開的
當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例的時(shí)候,實(shí)例內(nèi)部將包含一個(gè)內(nèi)部指針(__proto__)指向構(gòu)造函數(shù)的prototype,這個(gè)連接存在于實(shí)例和構(gòu)造函數(shù)的prototype之間,而不是實(shí)例與構(gòu)造函數(shù)之間。
function Person(name){ //構(gòu)造函數(shù) this.name=name; } Person.prototype.printName=function() {//原型對(duì)象 alert(this.name); } var person1=new Person("Byron"); //實(shí)例化對(duì)象 console.log(person1.__proto__); //Person console.log(person1.constructor); //Person console.log(Person.prototype); //指向原型對(duì)象Person var person2=new Person("Frank");
Person的實(shí)例person1中包含了name屬性,同時(shí)自動(dòng)生成一個(gè)__proto__屬性,該屬性指向Person的prototype,可以訪問到prototype內(nèi)定義的printName方法
實(shí)例就是通過構(gòu)造函數(shù)創(chuàng)建的。實(shí)例一創(chuàng)造出來就具有constructor屬性(指向構(gòu)造函數(shù))和__proto__屬性(指向原型對(duì)象),
構(gòu)造函數(shù)中有一個(gè)prototype屬性,這個(gè)屬性是一個(gè)指針,指向它的原型對(duì)象。
原型對(duì)象內(nèi)部也有一個(gè)指針(constructor屬性)指向構(gòu)造函數(shù):Person.prototype.constructor = Person;
實(shí)例可以訪問原型對(duì)象上定義的屬性和方法。
在這里person1和person2就是實(shí)例,prototype是他們的原型對(duì)象。
原型鏈的示意圖可以用下圖來表示:
類
-
基本上,ES6的class可以看作只是一個(gè)語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對(duì)象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已。
class Point { //類名 constructor(x, y) { //構(gòu)造函數(shù),constructor方法默認(rèn)返回實(shí)例對(duì)象(即this) this.x = x; //this關(guān)鍵字代表實(shí)例對(duì)象 this.y = y; } toString() { //prototype原型對(duì)象 return "(" + this.x + ", " + this.y + ")"; } } typeof Point // "function" Point === Point.prototype.constructor // true
上面代碼表明,類的數(shù)據(jù)類型就是函數(shù),類本身就指向構(gòu)造函數(shù)。
let point = new Point(1,2); //也是直接使用new命令,傳給constructor的值 point.toString(); //(1,2) point.hasOwnProperty("x") //true,自身的屬性(因?yàn)槎x在this變量上), point.hasOwnProperty("toString") //false/此屬性是定義在原型上 point.__proto__.hasOwnProperty("toString") //true point.constructor === Point.prototype.constructor //true
在類的實(shí)例上面調(diào)用方法,其實(shí)就是調(diào)用原型上的方法。
由于類的方法都定義在prototype對(duì)象上面,所以類的新方法可以添加在prototype對(duì)象上面。Object.assign方法可以很方便地一次向類添加多個(gè)方法。
class Point { constructor(){ //可以忽略不寫,會(huì)自動(dòng)添加 // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
prototype對(duì)象的constructor屬性,直接指向“類”的本身,這與ES5的行為是一致的。
Point.prototype.constructor === Point //true,類內(nèi)部的方法不能枚舉,和es5不一樣繼承
Class之間可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,這比ES5的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。
class ColorPoint extends Point {}//相當(dāng)于var ColorPoint=new Point也就是ColorPoint繼承了Point
上面代碼定義了一個(gè)ColorPoint類,該類通過extends關(guān)鍵字,繼承了Point類的所有屬性和方法。但是由于沒有部署任何代碼,所以這兩個(gè)類完全一樣,等于復(fù)制了一個(gè)Point類。es6的繼承是先新建父類的實(shí)例,再在子類中繼承修改this的指向
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); //調(diào)用父類的constructor(x, y)給父類傳值,用來新建父類的this對(duì)象。 this.color = color; } toString() { return this.color + " " + super.toString(); // ES6 規(guī)定,通過super調(diào)用父類的方法時(shí),super會(huì)綁定子類的this。 } } let cp = new ColorPoint(25, 8, "green"); cp instanceof ColorPoint // true cp instanceof Point // true
ES5的繼承,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對(duì)象this,然后再將父類的方法添加到this上面Parent.apply(this)。
ES6的繼承機(jī)制完全不同,實(shí)質(zhì)是先創(chuàng)造父類的實(shí)例對(duì)象this(所以必須先調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this。
繼承鏈
大多數(shù)瀏覽器的ES5實(shí)現(xiàn)之中,每一個(gè)對(duì)象都有__proto__屬性,指向?qū)?yīng)的構(gòu)造函數(shù)的prototype屬性。ES6同時(shí)有prototype屬性和__proto__屬性,因此同時(shí)存在兩條繼承鏈。
(1)子類的__proto__屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。
(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。
class A {} class B extends A {} B.__proto__ === A //true,es5中,是constructor,實(shí)例 B.prototype.__proto__ === A.prototype //true,es5中,是B._proto_===A.prototype,方法
這樣的結(jié)果是因?yàn)?,類的繼承是按照下面的模式實(shí)現(xiàn)的。
// B的實(shí)例繼承A的實(shí)例 Object.setPrototypeOf(B.prototype, A.prototype) = B.prototype.__proto__ = A.prototype; const b = new B(); // B的實(shí)例繼承A的靜態(tài)屬性 Object.setPrototypeOf(B, A) = B.__proto__ = A; const b = new B(); 《對(duì)象的擴(kuò)展》一章給出過Object.setPrototypeOf方法的實(shí)現(xiàn)。 Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; }
這兩條繼承鏈,可以這樣理解:作為一個(gè)對(duì)象,子類(B)的原型(__proto__屬性)是父類(A);作為一個(gè)構(gòu)造函數(shù),子類(B)的原型(prototype屬性)是父類的實(shí)例。
Object.create(A.prototype); // 等同于 B.prototype.__proto__ = A.prototype; 實(shí)例的__proto__屬性
子類實(shí)例的__proto__屬性的__proto__屬性,指向父類實(shí)例的__proto__屬性。也就是說,子類的原型的原型,是父類的原型。
var p1 = new Point(2, 3); var p2 = new ColorPoint(2, 3, "red"); p2.__proto__ === p1.__proto__ // false p2.__proto__.__proto__ === p1.__proto__ // true
三種特殊情況。
第一種特殊情況,子類繼承Object類。
class A extends Object {} A.__proto__ === Object //true A.prototype.__proto__ === Object.prototype //true
這種情況下,A其實(shí)就是構(gòu)造函數(shù)Object的復(fù)制,A的實(shí)例就是Object的實(shí)例。
第二種特殊情況,不存在任何繼承。
class A {} //因?yàn)锳就是一個(gè)函數(shù),所以它繼承的自然就是函數(shù)。就相當(dāng)于new Function,但它的prototype是一個(gè)對(duì)象,所以繼承自對(duì)象 A.__proto__ === Function.prototype // true A.prototype.__proto__ === Object.prototype // true
這種情況下,A作為一個(gè)基類(即不存在任何繼承),就是一個(gè)普通函數(shù),所以直接繼承Funciton.prototype。但是,A調(diào)用后返回一個(gè)空對(duì)象(即Object實(shí)例),所以A.prototype.__proto__指向構(gòu)造函數(shù)(Object)的prototype屬性。
第三種特殊情況,子類繼承null。
class A extends null {} A.__proto__ === Function.prototype // true,表明new出來的都是函數(shù),A是函數(shù) A.prototype.__proto__ === undefined // true,因?yàn)槔^承自null,所以它的_proto_找不著,就是undefined
這種情況與第二種情況非常像。A也是一個(gè)普通函數(shù),所以直接繼承Funciton.prototype。但是,A調(diào)用后返回的對(duì)象不繼承任何方法,所以它的__proto__指向Function.prototype,即實(shí)質(zhì)上執(zhí)行了下面的代碼。
class C extends null { constructor() { return Object.create(null); }
原生構(gòu)造函數(shù)的繼承
原生構(gòu)造函數(shù)是指語言內(nèi)置的構(gòu)造函數(shù),通常用來生成數(shù)據(jù)結(jié)構(gòu)。ECMAScript的原生構(gòu)造函數(shù)大致有下面這些。
Boolean() Number() String() Array() Date() Function() RegExp() Error() Object()
以前,這些原生構(gòu)造函數(shù)是無法繼承的。
class MyDate extends Date{ getTest(){ console.log("我是MyDate的擴(kuò)展方法",this===date,new Date(),new MyDate(),) // this向的是它的實(shí)例對(duì)象,this===date } } let date=new MyDate(); console.log(date.getTime());//本地時(shí)間 date.getTest() //我是MyDate的擴(kuò)展方法 true
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92754.html
原型鏈及繼承的理解 定義函數(shù) function A(name) { // 構(gòu)造內(nèi)容(構(gòu)造函數(shù)) this.name = name; /* // 也支持定義方法。但為了性能,不建議在構(gòu)造里定義方法 this.fn = function(parmas){ // your code } */ } // 原型鏈 A.pro...
摘要:數(shù)組的構(gòu)造函數(shù)是原型鏈的指向與其他除以外的構(gòu)造函數(shù)相同,的也指向頂級(jí)原型對(duì)象,每一個(gè)數(shù)組都是的實(shí)例,都指向。實(shí)例對(duì)象查找構(gòu)造函數(shù)原型對(duì)象的方法一般會(huì)把對(duì)象共有的屬性和方法都放在構(gòu)造函數(shù)的原型對(duì)象上。 showImg(https://segmentfault.com/img/remote/1460000018998704?w=900&h=506); 閱讀原文 概述 在 JavaScr...
摘要:是完全的面向?qū)ο笳Z言,它們通過類的形式組織函數(shù)和變量,使之不能脫離對(duì)象存在。而在基于原型的面向?qū)ο蠓绞街校瑢?duì)象則是依靠構(gòu)造器利用原型構(gòu)造出來的。 JavaScript 函數(shù)式腳本語言特性以及其看似隨意的編寫風(fēng)格,導(dǎo)致長期以來人們對(duì)這一門語言的誤解,即認(rèn)為 JavaScript 不是一門面向?qū)ο蟮恼Z言,或者只是部分具備一些面向?qū)ο蟮奶卣鳌1疚膶⒒貧w面向?qū)ο蟊疽?,從?duì)語言感悟的角度闡述為什...
摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對(duì)象。對(duì)象就是數(shù)據(jù),對(duì)象本身不包含方法。類是相似對(duì)象的描述,稱為類的定義,是該類對(duì)象的藍(lán)圖或原型。在中,對(duì)象通過對(duì)類的實(shí)體化形成的對(duì)象。一類的對(duì)象抽取出來。注意中,對(duì)象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對(duì)象。對(duì)象就是數(shù)據(jù),對(duì)象本身不包含方法。類是相似對(duì)象的描述,稱為類的定義,是該類對(duì)象的藍(lán)圖或原型。在中,對(duì)象通過對(duì)類的實(shí)體化形成的對(duì)象。一類的對(duì)象抽取出來。注意中,對(duì)象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
閱讀 1310·2021-10-08 10:05
閱讀 4133·2021-09-22 15:54
閱讀 3114·2021-08-27 16:18
閱讀 3113·2019-08-30 15:55
閱讀 1448·2019-08-29 12:54
閱讀 2757·2019-08-26 11:42
閱讀 555·2019-08-26 11:39
閱讀 2139·2019-08-26 10:11