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