理解 JavaScript 面向对象编程

创建对象

创建对象有两种方法。

原始模式

一种是原始模式,最简单的就是直接用 {...}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方式一
var Person = {
name : 'htf',
  age : '24'
}
// 方式二
function Person(name, age){
return {
   name: name,
   age: age
  }
}
var person1 = Person('zs', 25);
var person2 = Person('ls', 18);

方式一创建一个对象,但这样只完成了对象的封装,没办法复用原型对象生成实例。方式二能解决代码重复的问题,但是不能反映出它们是同一个原型对象的实例。

构造函数模式

另一种是用构造函数的方法来创建对象:

1
2
3
4
5
6
7
function Student(name) {
this.name = name;
// 这种方式定义的函数不能被多个对象共享
this.hello = function () {
alert('Hello, ' + this.name);
}
}

对构造函数使用 new 运算符,就能生成实例,并且 this 变量会绑定在实例对象上。

可以编写一个 createObj 函数,封装 new 操作,通用的模式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Student(obj) {
this.name = obj.name || '匿名'; // 默认值为'匿名'
this.grade = obj.grade || 1; // 默认值为1
}

// 可以被多个对象共享
Student.prototype.hello = function () {
alert('Hello, ' + this.name);
};

function createStudent(obj) {
return new Student(obj || {})
}

使用 Prototype 改进对象的创建

使用构造函数模式创建对象很方便,但也会遇到前面例子的注释中提到的问题,即:

某些相同的属性和方法不能被多个对象共享,每一次生成一个实例,都必须为重复的内容,多占用一些内存。

prototype 模式

改进办法就是,把那些不变的属性和方法,直接定义在 prototype 对象上。

原因在于,Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

看栗子:

1
2
3
4
5
6
7
8
9
10
function Student(name) {
this.name = name;
}

Student.prototype.desc = '学生';
Student.prototype.hello = function(){
alert('Hello, ' + this.name);
}

var stu1 = new Student('htf');

与 prototype 有关的方法

isPrototypeOf():判断某个 proptotype 对象和某个实例之间的关系,比如:

1
console.log(Student.prototype.isPrototypeOf(stu1));     // true

hasOwnProperty():用来判断某一个属性是本地属性,还是继承自 prototype 对象的属性,比如:

1
2
console.log(stu1.hasOwnProperty("name"));   // true
console.log(stu1.hasOwnProperty("desc")); // false

in 运算符:判断某个实例是否含有某个属性,不管是不是本地属性。

1
2
console.log("name" in stu1);   // true
console.log("desc" in stu1); // true

另外 for...in... 语句可以用来遍历一个对象的所有属性。

1
2
3
for(var prop in stu1) { 
alert("stu1[" + prop + "]=" + stu1[prop]);
}

两种定义 prototype 的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 方式一
function Person(name) {
this.name = name;
};
Person.prototype.getName = function() {
return this.name;
};

// 方式二
function Person(name) {
this.name = name;
};
Person.prototype = {
getName: function() {
return this.name;
}
};

// 测试
var p = new Person("ZhangSan");
console.log(p.constructor === Person);
console.log(Person.prototype.constructor === Person);

使用方式一,后两行输出true,使用方式二,后两行输出false。

原因在于,constructor 始终指向创建自身的构造函数,使用方式二时,Person.prototype.constructor = Object

修正方法:在方式二的末尾,加上一句 Person.prototype.constructor = Person;

继承

这里介绍三种方式。

将父类的新实例赋值给构造函数的原型(类式继承)

这种使用 原型链+构造函数 的继承是比较常用的一种继承方法,看个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Parent(age){
this.name = ['mike','jack','smith'];
this.age = age;
}
Parent.prototype.run = function () {
return this.name + ' are both' + this.age;
};
function Child(age){
Parent.call(this,age); // 对象冒充,给超类型传参
}
Child.prototype = new Parent(); // 原型链继承
var test = new Child(21); // 写new Parent(21)也行
alert(test.run()); // mike,jack,smith are both21

使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。

call() 的用法:调用一个对象的一个方法,以另一个对象替换当前对象。

直接继承 prototype(原型式继承)

与上一种方法相比,这种方式的优点是效率比较高(不用执行和建立 父类 的实例),比较省内存。

但是如果直接让 子类.prototype = 父类.prototype ,修改子类的 prototype 会对 父类的 prototype 造成影响,因此使用一个空函数作为中介。

看个栗子:

可复用的 extend 函数

1
2
3
4
5
6
7
function extends(Child, Parent){
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype; // 实现直接调用父对象的方法
}

继承的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

function Student(obj){
this.name = obj.name || 'aaa';
}

Student.prototype.hello = function(){
alert('hello' + this.name);
}

function PrimaryStudent(obj){
Student.call(this, obj);
this.grade = obj.grade || 1;
}

// 实现原型继承链:
extend(PrimaryStudent, Student);
// 绑定其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function(){
alert(this.grade);
}

拷贝继承

顾名思义,也就是把父对象的所有属性和方法,拷贝进子对象,从而实现继承。

1
2
3
4
5
6
7
8
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
   c[i] = p[i];
}
c.uber = p;
}

有关对象深拷贝可参考:学习笔记 2015.8.17

参考

  1. Javascript 面向对象编程
  2. JavaScript 基础 - 面向对象编程
  3. JavaScript继承方式详解
坚持原创技术分享,您的支持将鼓励我继续创作!