prototype
ES6
ES5
javascript 是 prototype-base 的語言
在
ES6
提供了 class ,
但它只是 prototype 的封裝
var Animal = function(){};
var dog = new Animal();
dog.__proto__.__proto__.__proto__; // null
每一個物件都有其 " 原型" 物件
原型物件又指向它自己的原型物件
一直到 null 為止
也就是原型鏈 (prototype chain) 的終點
var text = 'some text here';
text.split(' ');
// ['some', 'text', 'here']
舉例來說,為什麼字串 text
有 split 這個方法可以用?
String.prototype.split
// split() { [native code] }
因為它的 String 原型 prototype
有 split 這個屬性
所以 Array, String, Number
不只是原始值 (primitive value)
也是物件實例 (Instance)
var Animal = function(name){
this.name = name
};
Animal.prototype.color = 'yellow'
當我們在存取屬性時
會先從物件身上開始找起
找不到就會從 prototype 上找
最終指向 undefined
var dog = new Animal('dog');
dog.color;
// 1. 先找 dog.color 得到 undefined
// 2. 在找 dog.prototype.color 得到 "yellow"
dog.nonexistence;
// 1. 先找 dog.color 得到 undefined
// 2. 在找 dog.prototype.nonexistence
// 3. 得到 "undefined"
__proto__ 這個東西指向該物件的原型
它為非標準的瀏覽器實做
在 中 可以透過下面這兩個訪問器 (accessors) 來存取
ES6
Object.getPrototypeOf(dog) === dog.__proto__;
// true
Object.setPrototypeOf(obj, property);
dog.__proto__ === Animal.prototype;
// true
在 prototype 的屬性是所有的 instance 共用的
所以當它修改時會影像到所有的實例
var MyClass = function(){};
MyClass.prototype.foo = 10;
var myObj1= new MyClass();
myObj1.foo; // 10
MyClass.prototype.foo = 20;
myObj1.foo; // 20
function Animal(name){
this.name = name;
}
Animal.prototype.run = function(){
console.log(this.name + "is running!!");
}
var a = new Animal("a");
var b = new Animal("b");
Animal.prototype //Animal {}
Animal.prototype instanceof Object // true
Animal.prototype.constructor == Animal) // true
a.__proto__ == Animal.prototype //true
b.__proto__ == Animal.prototype //true
a.__proto__.__proto__ // Object {}
a.__proto__.run == a.run //true
a.__proto__.run == a.prototype.run // true
不像其它語言有類別的定義
但任何函式都能以屬性的方式加到物件中
所以實現繼承也就是把別的物件身上的屬性
複製到自己的 prototype 上
ES5
var Animal = function(name){
this.name = name;
};
Animal.prototype.run = function(){
console.log(this.name + ' is running');
}
這裡有一個動物類別
我們要另外做一個 "狗" 的類別繼承它
function Dog(name){
// 調用父類的構造函數,通過改變this指向將屬性賦值到新的實例對象
Animal.call(this, name);
}
Dog.prototype = new Animal();
var dog = new Dog("dog");
dog.run(); // dog is running!!
Line:3 呼叫了父類別的 constructor
Line:6 把 prototype 指向了Animal instance
當我們呼叫 dog.run 時,會照
dog -> dog.__proto__ (Animal) -> Animal.prototype
來搜尋該屬性
另一種繼承的方式,透過 Object.create()
var shape = {
width: 100,
height: 100,
getArea: function(){
return this.width * this.height
}
};
var rectangle = Object.create(shape);
// rectangle 是由 shape 繼承來的物件
rectangle.width = 50;
rectangle.getArea(); // 5000
// 呼叫 getArea 時裡面的 this 指向 rectangle
另一種繼承的方式,透過 Object.create()
rectangle.__proto__ === shape; // true
rectangle; // {width: 50}
shape;
// shape -> Object.prototype -> null
rectangle
// rectangle -> shape -> Object.prototype -> null
物件屬性的枚舉 (enumerate)
for(var i in shape) {
console.log(i);
}
// width
// height
// getArea
透過 for ... in 來枚舉物件身上的屬性
但是
var rectangle = Object.create(shape);
rectangle.width = 50; // rectangle 只有 width 這個屬性
for(var i in rectangle) {
console.log(i);
}
// width
// height
// getArea
for ... in 也會把原型鏈上的屬性印出來
解法 1: Object.hasOwnProperty()
var rectangle = Object.create(shape);
rectangle.width = 50; // rectangle 只有 width 這個屬性
for(var i in rectangle) {
if(rectangle.hasOwnProperty(i)) console.log(i)
}
// width
解法 2: Object.keys()
var keys = Object.keys(rectangle);
keys.forEach(function(key, idx){
console.log(key);
});
// width
解法 3: Object.definedProperties()
var shape = {};
Object.defineProperties(shape, {
'width': {
value: 100,
writable: true,
enumerable: false,
},
'height': {
value: 100,
writable: true,
enumerable: false,
}
// etc.
});
解法 3: Object.definedProperties()
var rectangle = Object.create(shape);
for(i in rectangle) {
console.log(i);
}
// nothing print out
不要擴充原生物件的原型
Array.prototype.sum = function(){
var sum = this.reduce(
(sum, current) => sum + current
, 0);
return sum;
};
var ary = [1, 2, 3];
ary.sum(); // 6
關於繼承,還有一些事
function Dog(name){
Animal.call(this, name);
}
Dog.prototype = new Animal();
var dog = new Dog("dog");
dog.__proto__;
// Animal {name: undefined}
// dog 的 prototype 多了不必要的屬性 "name"
function Dog(name){
Animal.call(this, name);
}
Dog.prototype = new Animal();
var dog = new Dog("dog");
dog.constructor === Animal; // true
dog.constructor === Dog; // false
var F = function(){};
F.prototype = Animal.prototype;
Dog.prototype = new F();
Dog.prototype.constructor = Dog;
function objCreate(prototype){
var F = function(){};
F.prototype = prototype;
return new F();
}
function inherit(subclass, parentclass){
subclass.prototype = objCreate(parentclass.prototype);
subclass.prototype.constructor = subclass;
}
改良
封裝
function Animal(name){
this.name = name;
}
Animal.prototype.run = function(){
console.log(this.name + "is running!!");
}
function Dog(name){
Animal.call(this,name);
}
inherit(Dog, Animal);
var dog = new Dog('dog');
dog.run();//dog is running!!
回頭修改一下前面的程式
以上是最早期的繼承方式
Class
ES6
class declaration
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
const p = new Polygon(100,100);
// Polygon {height: 100, width: 100}
No Hoisting
var p = new Polygon(); // ReferenceError
class polygon {}
Class expressions
// unnamed
var Polygon = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Polygon.name);
// "Polygon"
Class expressions
// named
var Polygon = class Polygon2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Polygon.name);
// "Polygon2"
Extend
class Rectangle extends Polygon {
// do something here...
}
Prototype methods
class Rectangle extends Polygon {
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea(){
return this.height * this.width;
}
}
const square = new Rectangle(100, 100);
console.log(square.area);
// 10000
Override
class Polygon{
constructor(w, h){
this.w = w;
this.h = h;
}
getShape(){
return "polygon"
}
}
Override
class Rectangle extends Polygon{
getShape(){
return "Rectangle"
}
}
const square = new Rectangle(100, 100);
square.getShape(); // "Rectangle"
Super
class Rectangle extends Polygon{
getShape(){
const rs = super.getShape();
return `quadrilateral of ${rs}`;
}
}
const square = new Rectangle(100, 100);
square.getShape();
// "quadrilateral of polygon"
"Super" in constructor
class Triangle extends Polygon{
constructor(sideA, sideB, sideC){
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
}
var t = new Triangle();
// ReferenceError: Must call super constructor in derived class
"Super" in constructor
- sub Class 的 constructor 一定要呼叫 super
- 如果 sub Class 沒有 constructor 會自己建立一個並呼叫 super
- ES5 的繼承是先創造子類,再把父類的東西添加上去
- ES6 的繼承是先創造父類,而後在被子類修改
"Super" in constructor
class Triangle extends Polygon{
constructor(sideA, sideB, sideC){
super(0 ,0);
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
}
var t = new Triangle(3, 4, 5);
// Triangle {w: 0, h: 0, sideA: 3, sideB: 4, sideC: 5}
Static mehods
class Point {
constructor(x, y){
this.x = x;
this.y = y;
}
static distance(p1, p2){
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(0, 0);
const p2 = new Point(3, 4);
Point.distance(p1, p2); // 5
Instance properties
class Polygon{
constructor(w, h){
this.w = w;
this.h = h;
}
static w = 10;
}
const rect = new Polygon(100, 100);
rect.w; // 100;
Polygon.w; // 10
Prototype 上的 primitive
class MyClass{};
MyClass.prototype.foo = 100;
var c1 = new MyClass();
c1.foo; // 100
var c2 = new MyClass();
c2.__proto__.foo = 200;
c1.foo; // 200
當你想要在 prototype 上建立共用的 primitive 屬性,目前只能這樣做
當 這份草案通過就可以改成這樣
class MyClass{
foo = 100;
};
var c1 = new MyClass();
c1.foo; // 100
END
Quiz
請修改下面的物件,當它被設定屬性 width 時
如果數值不是整數,會四捨五入
如果輸入的數值非法 (包含負數),會變成 -1
var shape = {
width: 0,
};
shape.width = 3.4;
console.log(shape.width); // 3
shape.width = -infinity;
console.log(shape.width); // -1
// hint
// Object.defineProperty()
承上題,請改用 class 方式達成
class Shape {
// ...
}
var shape = new Shape();
shape.width = 3.4;
console.log(shape.width); // 3
// hint: get set
javascript prototype
By mangogan
javascript prototype
- 539