js
是一門基於對象的語言。js
中的一切皆對象;es6
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
對象定義的屬性和方法的一個複製。app
構造函數(constructor)
,其實就是一個普通函數,可是內部使用了this
變量,對構造函數使用new
運算符,就能生成實例,而且this
變量會綁定在實例對象上。函數
function Cat(name,color){ this.name=name; this.color=color; } Cat.prototype.type=function(){}; var cat1=new Cat()
這時cat1
會自動含有一個constructor
屬性,指向它們的構造函數。this
alert(cat1.constructor==Cat);//true alert(cat instanceof Cat);//true
js
提供了一個instanceof
運算符,用來檢驗cat1
是不是Cat
的實例對象。es5
構造函數(constructor):每new
生成一個實例,就至關於在內存上又複製了一次
原型對象(prototype):而portotype
,全部的實例都只指向一個內存地址,用於不變的屬性和方法spa
不論是構造函數內部仍是原型對象,裏面的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