JS面向对象程序设计
理解对象
对象中有四个属性描述符分别是:
[[Configurable]]:表示是否能通过delete删除该属性或者能否修改属性的特性。默认是true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const person = {
name: 'barney',
age: 25
};
Object.defineProperty(person, 'name', {
value: 'barney',
writable: true,
configurable: false,
enumerable: true
});
delete person.name;
delete person.age;
console.log(person.name, person.age); // barney undefined
Object.defineProperty(person, 'name', {
value: 'billyy',
writable: true,
configurable: true,
enumerable: true
}); // 这句话会报错,因为一旦[[configurable]]的值为false之后,就不能再转换为true了,是一个不可逆过程可以看到,当属性描述符[[configurable]]为false之后,无法通过delete删除该属性了。
[[Enumberable]]:表示能否通过
for...in
循环返回属性。默认是true1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function logProprties (obj) {
for (const i in obj) {
console.log(i);
}
}
const person = {
name: 'barney',
age: 25
};
logProprties(person); // name age
Object.defineProperty(person, 'name', {
value: 'barney',
writable: true,
configurable: true,
enumerable: false
});
logProprties(person); // age可以看到,当name的[[Enumberable]]为true的时候,通过
for...in
是可以访问到name
属性的,但是改为false之后,就无法通过for...in
访问到了。[[Writable]]:是否可以修改数据的值。默认是true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const person = {
name: 'barney',
age: 25
};
Object.defineProperty(person, 'name', {
writable: false
});
person.name = 'billy';
console.log(person.name); // barney
Object.defineProperty(person, 'name', {
value: 'billy'
});
console.log(person.name); // billy可以看到,当[[Writable]]为false的时候,
name
的值已经不能直接通过赋值语句来修改了。只能通过Object.defineProperty来修改了。[[Value]]:当前属性的值,默认是undefiend
访问器属性:
- [[get]]:在读取的时候调用的函数,默认值是undefined
- [[set]]:在写入属性值 的时候调用的函数,默认值是undefined
1 | const person = { |
定义多个属性的特性可以用Object.defineProperties()
,而获取一个对象的属性特性可以用Object.getOwnPropertyDescriptor()
。
创建自定义对象
一个对象其实就是数据和操作数据方法的集合。下面将介绍很多中创建对象的方法,是一个循序渐进的优化路径,最终只有一两个是常用的,但是这个优化思路需要逐步递进。
工厂模式
1
2
3
4
5
6
7
8
9
10
11
12function createPerson (name, age, job) {
const obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function () { console.log(this.name); }
return obj;
}
const person = createPerson('barney', 25, 'engineer');工厂模式解决了创建多个相似对象的问题,但是每个对象与创建他们函数无法有太多联系,即无法知道
person
与createPerson
之间的关系。构造函数模式:明确对象与构造函数之间的关系
1
2
3
4
5
6
7
8
9
10function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () { console.log(this.name); };
}
const person = new Person('barney', 25, 'engineer');
person.sayName(); // barney
console.log(person instanceof Person); // true可以通过
instanceof
来判断对象person
是构造函数Person
的实例。但是可以看到每个实例都自己实现了一次sayName
方法,但是他们的行为是一样,所以我们应该只实现一次sayName
方法。1
2
3
4
5
6
7
8
9
10
11function sayName () { console.log(this.name); }
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
const person = new Person('barney', 25, 'engineer');
person.sayName(); // barney可以看到,我们在
Person
外部定义一个sayName
方法,这样就可以只实现一次sayName
方法了。但是这样做的话,就将sayName
与Person
解耦了。但其实sayName
是Person
特有的方法,其他的对象是不能调用的。构造函数+原型链模式:解决共享函数的问题。(重要)
1
2
3
4
5
6
7
8
9
10
11
12// 构造函数
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype.sayName = function () { console.log(this.name); };
const person = new Person('barney', 25, 'engineer');
person.sayName(); // barney
console.log(person instanceof Person); // true可以看到,数据等每个实例特有的属性,都放在构造函数
Person
中,而对这些数据的共同操作则放在Person.prototype
上,这样相同的行为逻辑就只实现了一次,而且还是在Person的原型链上。这样,其他的类型的对象就没有办法直接调用sayName
这个方法了。而这种模式也是比较常用的模式。
继承
继承是面向对象的一个非常重要且常用的手段,JS中的继承方法主要有两个。
组合继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29function User (name) {
this.name = name;
}
User.prototype.sayName = function () { console.log(this.name); };
function Vip (name, level) {
User.call(this, name);
this.level = level;
}
// 将prototype指向User的实例,User的实例的[[prototype]]指向User.prototype,这样Vip.prototype就可以访问User.prototype上的方法了。
Vip.prototype = new User();
// 上一句修改了Vip.prototype.constructor的指向,指向了Object.prototype,所以现在要重新指向默认的Vip
Vip.prototype.construtor = Vip;
// 定义新的方法只能通过Vip.prototype.xxx的格式,不能用字面量表示法
Vip.prototype.sayLevel = function () { console.log(this.level); };
const user = new User('Billy');
user.sayName();
console.log(user instanceof User);
const vip = new Vip('Barney', 2);
vip.sayName();
vip.sayLevel();
console.log(vip instanceof Vip);
console.log(vip instanceof User);组合继承中可以看到,
User
构造函数被调用了两次,其中一次是为了让Vip.prototype
与User.prototype
之间产生链接,我们将这一步优化一下,创建一个指向User.prototype
的对象就可以了。这样User
就只需要调用一次,而且可读性也更强。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27function User (name) {
this.name = name;
}
User.prototype.sayName = function () { console.log(this.name); };
function Vip (name, level) {
User.call(this, name);
this.level = level;
}
const proto = User.prototype;
proto.constructor = Vip;
Vip.prototype = proto;
Vip.prototype.sayLevel = function () { console.log(this.level); };
const user = new User('Billy');
user.sayName(); // Billy
console.log(user instanceof User); // true
const vip = new Vip('Barney', 2);
vip.sayName(); // Barney
vip.sayLevel(); // 2
console.log(vip instanceof Vip); // true
console.log(vip instanceof User); // true
Class
JS中的Class是更接近面向对象的表达方式,但是Class的实质仍然是Function,所以,可以把Class看作是Function的一种语法糖。具体类如何实现上面的User
和Vip
,看示例
1 | class User { |
这样写,可读性更好。需要注意的是,子类中的constructor调用super
方法。而关于更多class的细节问题参考: https://es6.ruanyifeng.com/#docs/class和https://es6.ruanyifeng.com/#docs/class-extends
原文作者: Billy & Barney
原文链接: https://liangbilin.github.io/2020/02/24/Barney--JS面向对象程序设计/
版权声明: 转载请注明出处(必须保留作者署名及链接)