在JavaScript中,对象是属性的无序集合,每一个属性存放一个原始值、对象或函数。javascript
在JavaScript中建立对象的两种方法:
① 字面上:html
var myObj = { key: value, // ... };
② 面向对象:java
var myObj = new Object(); myObj.key = value;
注意:在对象中,属性名永远都是字符串。若是你使用string
(字面量)之外的其余值做为属性名,那它首先会被转换为一个字符串。es6
JavaScript中的内置对象分为构造器对象和普通对象。
构造器对象:编程
Object
Boolean
String
Number
Function
Array
RegExp
Date
Error
其余对象:app
Math
JSON
内置对象,其实也叫内置构造器,它们能够经过new
的方式建立一个新的实例对象。函数
先上图。
this
__proto__
、prototype
和constructor
在JavaScript中,每一个对象都拥有一个原型对象prototype
,而指向该原型对象的内部指针则是__proto__
属性。
__proto__
属性先后各两个下划线,表示它本质是一个内部属性,而不是一个正式的对外的API。prototype
{}
建立的对象var myObj = {}; console.log(myObj.__proto__ === Object.prototype); // true
JavaScript中的函数也是对象,它的原型是Function.prototype
。指针
var Foo = function() {} Foo.__proto__ === Function.prototype // true
经过函数构造的实例对象,其原型指针__proto__
会指向该函数的prototype
属性。
function Foo(){} var a = new Foo(); console.log(a.__proto__ === Foo.prototype); // true console.log(Foo.prototype.__proto__ === Object.prototype); // true // constructor console.log(Foo.prototype.constructor === Foo); // true
原型对象都包含一个指向构造函数的指针constructor
。
注意:constructor
属性并非实例对象的属性,而是构造函数的原型对象的constructor
属性。
console.log(a.constructor === Foo); // true console.log(a.constructor === Foo.prototype.constructor); // true console.log(a.hasOwnProperty("constructor")); //false console.log(Foo.prototype.hasOwnProperty("constructor")); //true
a.constructor
经过原型链往上找到其原型对象Foo.prototype
的constructor
属性。
var a = new Array(); a.__proto__ === Array.prototype // true
Array.prototype
自己也是一个对象,也有继承的原型:
a.__proto__.__proto__ === Object.prototype // true // 等同于 Array.prototype.__proto__ === Object.prototype
那么Object
的原型指向的是谁呢?
a.__proto__.__proto__.__proto__ === null // true // 等同于 Object.prototype.__proto__ === null
总结:
原型链的意义在于,当读取对象的某个属性时,JavaScript引擎先寻找对象自己的属性,若是找不到,就到它的原型去找,若是仍是找不到,就到原型的原型去找。以此类推,若是直到最顶层的Object.prototype
仍是找不到,则返回undefined
。(全部对象的原型链的顶端是Object.prototype
)
JavaScript语言的传统方法是经过构造函数,定义并生成新对象。下面是一个例子。
function Point(x, y) { this.x = x; this.y = y; } // 下面覆盖了原型链上Object.prototype.toString方法 Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2); console.log(p.toString()); // (1, 2)
ES6引入了Class这个概念,经过class
关键字能够定义类。
ES6的class
能够看做是一种语法糖,它的绝大部分功能,ES5均可以作到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } console.log(typeof Point); // "function" console.log(Point === Point.prototype.constructor); // true var p = new Point(1, 2); console.log(p.toString()); // (1, 2)
上面代码代表,类的数据类型就是函数,构造函数的prototype
属性,在ES6的"类"上面继续存在。
事实上,类的全部方法都定义在类的prototype
属性上面。
class Point { constructor() {} toString() {} toValue(){} } // 等同于 Point.prototype = { toString(){}, toValue(){} };
constructor
方法constructor
方法是类的默认方法,经过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,若是没有显式定义,一个空的constructor
方法会被默认添加。
constructor() {}
constructor
方法默认返回实例对象(即this
),彻底能够指定返回另一个对象。
class Foo { // constructor() {} } console.log(new Foo() instanceof Foo); // true
class Foo { constructor() { return Object.create(null); } } console.log(new Foo() instanceof Foo); // false
类必须使用new
来调用,从而生成类的实例对象。
实例的属性除非显式定义在其自己(即定义在this
对象上),不然都是定义在原型上(即定义在class
上)。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3); console.log(point.hasOwnProperty('x')); // true console.log(point.hasOwnProperty('y')); // true console.log(point.hasOwnProperty('toString')); // false console.log(point.__proto__.hasOwnProperty('toString')); // true
类的全部实例共享一个原型对象。这也意味着,经过实例的__proto__
属性能够为原型对象添加方法。
var p1 = new Point(2,3); var p2 = new Point(3,2); console.log(p1.__proto__ === p2.__proto__); // true p1.__proto__.printName = function () { return 'Oops' }; p1.printName() // "Oops" p2.printName() // "Oops"
上面代码使用实例的__proto__
属性改写原型,必须至关谨慎,不推荐使用,由于这会改变Class的原始定义,影响到全部实例。
Class之间能够经过extends
关键字实现继承,这比ES5的经过修改原型链实现继承,要清晰和方便不少。
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
上面代码中,constructor
方法和toString
方法之中,都出现了super
关键字,它在这里表示父类的构造函数,用来新建父类的this
对象。
子类必须在constructor
方法中调用super
方法,不然新建实例时会报错。这是由于子类没有本身的this
对象,而是继承父类的this
对象,而后对其进行加工。若是不调用super
方法,子类就得不到this
对象。
ES5的继承,实质是先创造子类的实例对象this
,而后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6的继承机制彻底不一样,实质是先创造父类的实例对象this
(因此必须先调用super
方法),而后再用子类的构造函数修改this
。
注意:在子类的构造函数中,只有调用super
以后,才可使用this
关键字,不然会报错。这是由于子类实例的构建,是基于对父类实例加工,只有super
方法才能返回父类实例。
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; // ReferenceError super(x, y); this.color = color; // 正确 } }
__proto__
和prototype
类同时有__proto__
属性和prototype
属性,同时存在两条继承链。
__proto__
属性,表示构造函数的继承,老是指向父类。prototype
属性的__proto__
属性,表示方法的继承,老是指向父类的prototype
属性。class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
这样的结果是由于,类的继承是按照下面的模式实现的。
class A { } class B { } // B的实例继承A的实例 Object.setPrototypeOf(B.prototype, A.prototype); // B继承A的静态属性 Object.setPrototypeOf(B, A); Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; }
这里只介绍JavaScript的原型链相关的内容,若是想了解关于ES6 Class的更多内容请阅读ES6文档。
参考: