面向对象编程
1 原型对象(prototype)
JavaScript的面向对象编程与大多数编程语言不一样,类与实例是大多数编程语言的基本概念,JavaScript不区分类和实例的概念,而是通过原型对象(prototype)来实现面向对象编程(原型对象就是有点类的意思)。
JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。
当我们用obj.xxx
访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype
对象,最后,如果还没有找到,就只能返回undefined。
2 创建对象
创建原型的方法除了简单地使用{...}
创建之外,还可以使用其他别的方法。
2.1 继承原型对象
Object.create()
方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有
var Student = {
name:"匿名",
age:"21"
};
/* 以上面的对象作为原型对象创建一个新对象 */
function createstudent (name,age) {
var s=Object.create(Student);//基于Student原型创建一个新对象
s.name=name;//自定义属性
return s;//返回对象
}
var xiaoming=createstudent("小明");
xiaoming.name;//小明
xiaoming.age;//21
2.2 关键字new调用构造函数
JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数,再使用new
关键字调用这个函数,并返回一个对象。一般的,如果构造函数没有return
语句或者是return
了基本类型,则会自动忽视return
并返回this
,而当return
的是一个对象时,则就返回这个对象。
var Student = function (name) {
this.name=name
};
var xiaoming=new Student("小明");
xiaoming.name;//小明
如果不写关键字new
,这就是一个普通函数,在strict
模式下,this
指向undefined
,会报错。
但是,如果写了关键字new
,它就变成了一个构造函数,它绑定的this
指向新创建的对象,并默认返回this
,也就是说,不需要在最后写return this;
。
为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写
1 那么此时的对象xiaoming
的原型对象是谁呢?
构造函数有一个prototype
属性(注意只有构造函数才拥有),这个prototype
属性指向的对象就是通过其创建的对象的原型对象了,也就是说上述的对象xiaoming
其原型对象就是Student.prototype
,而这些子对象其实可以通过__proto__
这个非标准用法来查看。
Student.prototype===xiaoming.__proto__;//true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
// 验证继承关系:
xiaoming instanceof Student; // true
还有使用构建函数创建的对象都会继承原型对象的constructor
属性,它指向函数本身。
xiaoming.constructor===Student.prototype.constructor;//true
Student.prototype.constructor === Student; // true,等同于构建函数
此外,使用new关键字创建多个对象时,若这些对象的有信息是固定的(比如说共享同一个函数),那么就无需填写在构建函数里头,可以直接绑定在原型对象,这样运行起来可以省很多内存,如:
Student.prototype.hello=function () {
alert('Hello, ' + this.name + '!');
};
xiaoming.hello();
3 原型继承
假如现在有一个Person
对象,想要在Person
对象下创建一个Student
对象,使其有继承关系,实现继承的方法以下方式:
- 第一种,使用
new
,创建Student
:
Student.prototype = new Person();
Student.prototype.constructor = Student;//修复构造函数
使用new
有个弊端,就是假如Person
的构造函数带有参数的话,就会变得局限了。
- 第二种,使用
Object.create()
,创建Student
:
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;//修复构造函数
要制作原型继承,第一步当然就是定义新的构造函数用来继承原型对象了。
- 定义新的构造函数,并在内部用
call()
调用要“继承”的构造函数,并绑定this;
第二步,创建一个原型对象的空函数(应该空实例也行),并被新的构造的原型对象继承
- 借助中间函数F实现原型链继承,最好通过封装的
inherits
函数完成,inherits
函数的主要功能是:被新的构造的原型对象继承,以及修复其的构造函数
Obeject.create()
是ES5从引进的功能,那么在低版本如何创建继承呢?其实也很简单,只需创建一个空函数继承父原型对象,在调用new
:
/* 旧版本创建Object.create() */
//假如Object对象没有create属性
if (!Object.create){
Obeject.create = function(proto) {
function F () {}
F.prototype = prtoto;//把参数(父原型对象)赋值
return new F;//返回以及调用new的对象
};
}
4 class继承
上面的太难太麻烦了?救星来了。
新的关键字
class
从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单,注意是定义类,不仅仅用于继承
class的定义包含了构造函数constructor和定义在原型对象上的属性或函数(注意没有function关键字),如下使用class
关键字创建构造函数:
/*
class的定义包含了构造函数constructor和定义在原型对象上的函数hello()(注意没有function关键字)
*/
class Student {
constructor(name) {
this.name=name;
}
hello () {
alert('Hello, ' + this.name + '!');
}//没有function关键字定义
}
var xiaoming=new Student("小明");
4.1 class继承
用class定义对象的另一个巨大的好处是继承更方便了,直接通过extends
来实现,还需要使用super
调用父类的构造方法:
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}