前言:写一下原型链吧,感觉挺多东西的。继承和构造函数也写一下。之前看过很多遍。再来整理一下~
主要的参考文献就是阮一峰老师的es6和js的红宝书。
js的面向对象设计与其他的面向对象语言有所区别。
1、关于对象
1.1 数据属性
数据属性包含:
- configurable
- enumerable
- writable:
- value
直接在对象上定义的属性,这些特性都被设置为true,而value被设置为指定的值
var person = {};
Object.defineProperty(person, "name", {
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
var person = {};
person.name = "Nicholas";
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Greg"
可以多次调用Object.defineProperty()方法修改同一个属性,但在把configurable特性设置为false 之后就会有限制了。
在调用Object.defineProperty()方法时,如果不指定,configurable、enumerable 和writable 特性的默认值都是false。
1.2 访问器属性
访问器属性不包含数据值;它们包含一对儿getter 和setter 函数
- configurable:可配置属性,表示能否通过delete删除属性而重新定义属性
- enumerable: 表示能否通过for-in循环返回属性
- get:读取属性时调用的函数
- set:写入属性时调用的函数
访问器不能直接直接定义,需要通过Object.defineProperty()
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
ECMAScript 5 又定义了一个Object.defineProperties()。利用这个方法可以通过描述符一次定义多个属性。这个方法接收两个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应
var book={};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},
year:{
get:function(){
return this._year;
},
set: function(newVal){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
}
2、原型和原型链
2.1 原型对象
无论什么时候,只要创建一个新函数,就会有一个根据一组特定规则为该函数创建的一个prototype属性,指向这个函数的原型对象。而原型对象上又有一个constructor属性,这个属性指向其构造函数。
比如上文的Person.prototype. constructor 指向Person。
创建了构造函数之后,其原型对象默认只会取得constructor 属性;至于其他方法,则都是从Object 继承而来的。
当用构造函数创建实例之后,实例内部包含一个指针__proto__指向构造函数的原型对象。
看如下一段代码:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true

2.1.1 原型和实例的关系
(1)判断原型isPrototypeOf()
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
Object.isPrototypeOf(person1); //false
这里我们用原型对象的isPrototypeOf()方法来测试实例的原型对象。
(2)Object.getPrototypeOf()
Object.getPrototypeOf(person1);
// {name: "Nicholas", age: 29, job: "Software Engineer", sayName: ƒ, constructor: ƒ}
Object.getPrototypeOf(person1)==Person.prototype; //true;
(3)instanceof
person1 instanceof Person; //true
person1 instanceof Object; //true
(4)constructor
person1.constructor == Person; //true
person1.constructor==Object; //false
2.1.2 原型属性和实例属性
判断一个对象的属性是实例属性还是原型属性。
比如一个栗子:
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype={
friends:['xh','xh'],
sayName:function(){
console.log(this.name);
}
}
var person1=new Person("Shelly",20);
(1)hasOwnProperty
判断属性是否存在在实例中。如果存在在实例中则返回true
person1.hasOwnProperty("name"); //true
person1.hasOwnProperty("friends"); // false
(2)in
in判断是否存在在实例或者原型之中
'name' in person1; //true
‘friend’ in person1; //true
2.1.3 遍历属性
(1)Object.keys()
返回的只是实例属性
Object.keys(person1); //["name", "age"]
Object.keys(Person.prototype); // ["friends", "sayName"]
(2)for-in循环
for(item in person1){
console.log(item);
}
//name age friends sayName
(3)Object.getOwnPropertyNames()
可以获取该对象的属性
Object.getOwnPropertyNames(person1); //["name", "age"]
Object.getOwnPropertyNames(Person.prototype); // ["friends", "sayName"]
2.1.4 原型链的复写问题
使用如下方法构造对象的时候会导致原型链的重新指向,可以通过设置其constructor属性重新指回去,但是Person已经和旧的原型链断开了。
function Person(){}
Person.prototype={
constructor:Person,
name:'xx',
age:14
}

2.2 原型链
首先记住这张图。

ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?
显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
3、类的创建方式
3.1 ES5中类的创建方式
3.1.1 工厂模式
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
console.log(this.name);
}
return o;
}
var person1=createPerson("nike", 29, "Software Engineer");
var person2=createPerson("greg",27,"Doctor");
person1.sayName(); //nike
person2.sayName(); //greg
函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person 对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型).
3.1.2 构造函数模式
改进工厂模式,产生了构造函数模式。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
}
}
var person1=new Person("nike", 29, "Software Engineer");
var person2=new Person("greg",27,"Doctor");
person1.sayName(); //nike
person2.sayName(); //greg
与工厂模式的区别
①没有显式的创建对象
②直接将属性和方法赋予给了this 对象
③没有return语句
Person使用的是大写字母P,按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。 构造函数本身也是函数,只不过可以用来创建对象而已。
要创建Person的新实例,必须使用new操作符。
new操作符做了以下事情:
- 创建一个新对象
- 将构造函数的作用域赋予给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
在前面例子的最后,person1 和person2 分别保存着Person 的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person.
console.log(person1.constructor == Person);
console.log(person2.constructor == Person);
实际上constructor是保存在person1的原型链之中。person1的_proto_指向其构造函数的prototype,也就是Person.prototype。Person.prototype中的constructor指向Person构造函数。

构造函数的主要问题在于每个方法都要在每个实例上重新创建一遍。
虽然可以在全局作用域中定义但是又不符合对象方法的设计思想,使得对象设计毫无封装性可言,于是采用构造函数模型来解决这个问题。
3.1.3 原型模式
每一个构造函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象就是包含由特定类型的所有实例共享的属性和方法。使用原型对象的好处就是可以让所有对象实例共享它包含的属性和方法。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
原型模式带来的问题就是实例之间都共享方法和属性。
3.1.4 结合构造函数和原型模式
由于构造函数和原型模式存在的问题。所以采用结合构造函数和原型模式的方式。这是自定义对象的最常见的方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,同时支持向构造函数传参。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
3.2 ES6中类的创建方式
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。ES6 的class可以看作只是一个语法糖。
es5:原型链+构造函数
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
es6 :class+constructor
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var p1=new Point(1,3);
p1.toString() //"(1, 3)"
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性法的前面,加上static关键字。
4、类的继承
4.1 es5中类的继承
es5中对象的继承仍然分为原型链继承、构造函数继承和结合原型链和构造函数继承。
4.1.1 原型链继承
原型链作为实现继承的主要方法。基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
回顾一下构造函数、原型和实例的关系:
每个构造函数都有一个原型对象prototype,原型对象都包含一个指向构造函数的指针constructor,而实例包含一个指向原型对象的指针__proto__
原型链实现继承就是让一个子类型的原型对象(SubType.prototype)等于父类型的实例(new SuperType)
那么子类型就会继承父类型实例所拥有的全部属性和方法。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
alert('property' in instance); //true
打印一下就是这样的

画成示意图:

需要有几个注意的问题:
(1)要在原型重写之后定义方法
(2)原型链的缺点在于实例的属性共享
4.1.2 构造函数法
借用构造函数的思想就是在子类型的构造函数内部调用父类型的构造函数。
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的
4.1.3 组合继承
组合继承也叫伪经典继承,指的是将原型链和构造函数结合起来。
其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName=function(){
alert(this.name);
}
function SubType(name,age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
alert(this.age);
};
4.2 es6中类的继承
extends继承
Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多
class Point {
constructor(x,y){this.x=x;this.y=y;}
toString(){
return '(' + this.x + ', ' + this.y + ')';
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
var ss=new ColorPoint(1,2,'red'); //"red (1, 2)"
6万+

被折叠的 条评论
为什么被折叠?



