0%

封装

在总结弄清楚继承之前,我们先来总结一下封装相关的东西

封装

1. 私有属性和公有属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fuction Cat (name, color) {
var heart = '❤️';
var stomach = '胃';
var heartbeat = function () {
console.log(heart + '跳');
}
this.color = color;
this.name = name;
this.jump = function () {
this.heartbeat(); // 能跳起来,表明这只猫是活的,心也就是跳的
console.log('我跳起来了,来追我啊')
}
}
var guaiguai = new Cat('guaiguai', 'white');
console.log(guaiguai);
guaiguai.jump();

在函数内用var定义的就是私有的
在函数内用this承接的就是公有

2.静态属性方法和公有属性方法

继续上面的,添加以下代码

1
2
3
4
5
6
7
Cat.descript = '我这个构造函数是用来生产猫的';
Cat.actingCute = function () {
console.log('一听到猫,我就知道它在卖萌');
}
Cat.prototype.cleanTheBody = function () {
console.log('我会用唾液清理身体');
}

在构造函数上也就是使用Cat.xxx定义的是静态属性和方法
在构造函数内使用this设置,或者设置在构造函数原型对象上比如Cat.prototype.xxx,就是公有属性和方法(实例方法)

静态属性的方法的例子:Promise.all(), Promise.race(), Object.assign(), Array.from()

实例方法:push(), shift(), 等等他们实际上存在Array.prototype上面 Array.prototype.push()

3.实例自身的属性和构造函数的原型对象的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
function Cat (name) {
this.name = name;
}
Cat.prototype.prototypPro = '我是构造函数原型对象上面的属性';
Cat.prototype.cleanTheBody = function () {
console.log('我会用唾液清理身体');
}
var guaiguai = new Cat('guaiguai');

console.log(guaiguai);
console.log(guaiguai.name);
console.log(guaiguai.prototypePro);
guaiguai.cleanTheBody();

打印结果

1
2
3
4
Cat {name: "guaiguai"}
'guaiguai'
'我是构造函数原型对象上的属性'
'我会用唾液清洁身体'

定义在构造函数的原型对象的属性和方法,虽然不能直接表现在实例对象上面,但是实例对象可以访问或者调用它们

4. 实例自身属性和构造函数原型对象的属性区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Cat (name) {
this.name = name;
}
Cat.prototype.prototypePro = '我是构造函数原型对象上面的属性';
Cat.prototype.cleanTheBody = function () {
console.log('我会用唾液清理身体');
}
var guaiguai = new Cat('guaiguai');

for(key in guaiguai) {
if(guaiguai.hasOwnProperty(key)) {
console.log('我是自身属性' + key);
} else {
console.log('我不是自身属性' + key);
}
}

console.log('-分隔符-')
console.log(Object.key(guaiguai));
console.log(Object.getOwnPropertyNames(guaiguai));

打印结果

1
2
3
4
5
6
我是自身属性name
我不是自身属性PrototypePro
我不是自身属性cleanTheBody
-分隔符-
['name']
['name']

前提: 属性的enumerable为true
for in 可以遍历实例自身属性和构造函数原型对象的属性
Object.key 和 Object.getOwnPropertyNames只能获取实例自身属性
可以通过 .hasOwnProperty 判断一个属性是不是该实例的自身属性

5. 检查上面的是否掌握
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
function Person (name, sex) {  
this.name = name
this.sex = sex
var evil = '我很邪恶'
var pickNose = function () {
console.log('我会扣鼻子但不让你看见')
}
this.drawing = function (type) {
console.log('我要画一幅' + type)
}
}
Person.fight = function () {
console.log('打架')
}
Person.prototype.wc = function () {
console.log('我是个人我会wc')
}
var p1 = new Person('lindaidai', 'boy')
console.log(p1.name)
console.log(p1.evil)
p1.drawing('国画')
p1.pickNose()
p1.fight()
p1.wc()
Person.fight()
Person.wc()
console.log(Person.sex)

结果:

1
2
3
4
5
6
7
8
9
lindaidai
undefined
我要画一幅画画
Uncaught TypeError: p1.pickNose is not a function
Uncaught TypeError: p1.fight is not a function
我是个人我会wc
打架
Uncaught TypeError: Person.wc is not a function
undefined
6. 如果实例自身属性和构造函数原型对象的属性重名
1
2
3
4
5
6
7
8
9
function Cat () {  
this.color = 'white'
this.getColor = function () {
console.log(this.color)
}
}
Cat.prototype.color = 'black'
var cat = new Cat()
cat.getColor() // white

构造函数本身自己可以找到,直接就返回了。
原型链查找概念:当访问一个对象的属性和方法时,它不仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层层往上找,知道找到一个名字匹配的属性和方法或者到达原型了的末端(null)

7. 实例的原型对象的原型对象
1
2
3
4
5
6
7
8
9
10
11
12
13
function Cat () {  
this.color = 'white'
this.getColor = function () {
console.log(this.color)
}
}
Cat.prototype.color = 'black'
Object.prototype.color = 'yellow'
Object.prototype.feature = 'cute'
var cat = new Cat()
cat.getColor() // white
console.log(cat) // Cat {color: "white", getColor: ƒ}
console.log(cat.feature) // cute

实例的原型对象本质是一个对象,它的__proto__也就是Object.prototype

8. Object.prototype的方法
1
2
3
4
const obj = { name: 'obj' };
console.log(obj.toString());
console.log(obj.hasOwnProperty('name'));
console.log(Object.prototype);

obj本质是一个Object, const obj = { name: ‘obj’ } 实际上调用了new Object:

1
const obj = new Object({ name: 'obj' });

这样我们当然可以使用Object.prototype上面的方法

9. 总结 - 构造函数

定义:把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口

1. 私有属性、公有属性、静态属性概念:
  • 私有属性和方法:只能在构造函数内部访问不能在外部访问(在构造函数内使用var声明的属性)
  • 公有属性和方法(实例方法):对象外可以访问对象内的属性和方法(在构造函数内使用this设置,或者设置在构造函数原型对象上,比如Cat.prototype.xxx)
  • 静态属性和方法:定义在构造函数上的方法,不需要实例就可以调用,比如Object.assign()
2. 实例对象上的属性和构造函数原型对象的属性:
  • 定义在构造函数原型对象的属性和方法,虽然不能直接表现在实例对象上面,但是实例对象可以访问和调用它们
  • 当访问一个对象的属性和方法时,不仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层层往上,知道找到名字匹配的属性和方法,或者到达原型链的末端(null)
3. 遍历实例对象属性的方法:
  • 使用for…in…能获取实例对象的属性和原型链的属性
  • 使用Object.key()和Object.getOwnPropertyNames()只能获取实例对象的属性
  • 可以通过.hasOwnProperty()方法传入属性名来判断一个属性是不是实例自身属性

ES6之后的封装

1
2
3
4
5
6
7
8
9
10
11
12
class Cat {
constructor() {}
toString() {}
toValue() {}
}
// 等价于
function Cat () {}
Cat.prototype = {
constructor() {}
toString () {}
toValue () {}
}
1. 将上面例子1换成class版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class Cat (name, color) {
constructor () {
var heart = '❤️';
var stomach = '胃';
var heartbeat = function () {
console.log(heart + '跳');
}
this.color = color;
this.name = name;
this.jump = function () {
heartbeat(); // 能跳起来,表明这只猫是活的,心也就是跳的
console.log('我跳起来了,来追我啊')
}
}
}

var guaiguai = new Cat('guaiguai', 'white');
console.log(guaiguai);
guaiguai.jump();

当你使用class时候,它会默认调用constructor这个函数,来接收一些参数,并创建一个新的实例对象(this)并将它返回,因此被成为constructor构造方法

如果你的class没有定义constructor,它也会隐式生成一个constructor方法

2. 弄懂在类中定义属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Cat {  
constructor () {
var heart = '❤️'
this.name = 'guaiguai'
this.jump = function () {}
}
color = 'white'
cleanTheBody = function () {
console.log('我会用唾液清洁身体')
}
hideTheShit () {
console.log('我在臭臭完之后会把它藏起来')
}
}
var guaiguai = new Cat()
console.log(guaiguai) // Cat { color: "white", name: "guaiguai", cleanTheBody: ƒ, jump: ƒ}
console.log(Object.keys(guaiguai)) // ["color", "cleanTheBody", "name", "jump"]
guaiguai.cleanTheBody() // 我会用唾液清洁身体
guaiguai.hideTheShit() // 我在臭臭完之后会把它藏起来

用了4中方法定义属性:

  1. 在constructor中var一个变量,它只存在于constructor这个构造函数中
  2. 在constructor使用this定义的属性和变量会被定义到实例上面
  3. 在class使用 = 来定一个属性和方法,效果与第2点相同,会被定义到实例上面
  4. 在class直接定义一个方法,会被添加到原型对象prototype上面

在类的所有方法都定义在类的prototype属性上面

3. 在class定义静态属性和方法
  1. class本质是一个对象,所以也可以使用Cat.xx这种方法定义
  2. 使用static标识符表示它是一个静态的属性和方法
1
2
3
4
5
6
class Cat {  
static descript = '我这个类是用来生产出一只猫的'
static actingCute () {
console.log('一听到猫我就想到了它会卖萌')
} // static actingCute = function () {} // 这种写法也是设置静态的方法}
}
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
29
30
31
32
33
34
class Cat {  
constructor (name, color) {
var heart = '❤️'
var stomach = '胃'
var heartbeat = function () {
console.log(heart + '跳')
}
this.name = name
this.color = color
heartbeat()
this.jump = function () {
console.log(this)
console.log('我跳起来了~来追我啊')
}
}
cleanTheBody = function () {
console.log('我会用唾液清洁身体')
}
static descript = '我这个类是用来生产出一只猫的'
static actingCute () {
console.log(this)
console.log('一听到猫我就想到了它会卖萌')
}
}
Cat.staticName = 'staticName'
var guaiguai = new Cat('guaiguai', 'white') // ❤️ 跳
console.log(guaiguai) // Cat { name: 'guaiguai', color: 'white', jump: function() {}, cleanTheBody: function () {} }
guaiguai.jump() // Cat { name: 'guaiguai', color: 'white', jump: function() {}, cleanTheBody: function () {} } 我跳起来了~来追我啊
guaiguai.cleanTheBody() // 我会用唾液清洁身体
console.log(guaiguai.descript) // undefined
guaiguai.actingCute() // Uncaught TypeError: guaiguai.actingCute is not a function
Cat.actingCute() // class Cat{...} 一听到猫我就想到了它会卖萌
console.log(Cat.descript) // 我这个类是用来生产出一只猫的
console.log(Cat.staticName) // staticName
4. 类不存在提升机制
1
2
3
4
5
6
7
8
9
var a = new A()
function A () {}
console.log(a) // A {}

var b = new B()
class B {}
console.log(b) // Uncaught ReferenceError: Cannot access 'B' before initialization

typeof B // function
5. class里面的this指向问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Cat {  
constructor () {
this.name = 'guaiguai'
var type = 'constructor'
}
type = 'class'
getType = function () {
console.log(this.type)
console.log(type)
}
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.getType() // class window

调用getType函数的guaiguai, 所以里面的的this指向了guaiguai,而guaiguai的type为class
打印type时,因为没有这个变量,所以往外找,找到了window。
var type = ‘constructor’是函数constructor中的变量, 你也可以理解为是constructor函数的私有变量

6. class里的箭头函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Cat {  
constructor () {
this.name = 'guaiguai'
var type = 'constructor'
}
type = 'class'
getType = () => {
console.log(this.type)
console.log(type)
}
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.getType() // class window
console.log(guaiguai) // Cat { name: 'guaiguai', type: 'class', getType: function() {}}

如果在构造函数使用了简头函数,this指向的这个实例对象

7. 属性名和方法名相同
1
2
3
4
5
6
7
8
9
10
11
class Cat {  
constructor () {
this.name = 'cat1'
}
name = 'cat2'
getName = function () {
console.log(this.name) // cat1
}
}
var cat = new Cat()
cat.getName()

constructor中定义的相同名称的属性和方法会覆盖class里面定义的

8. 再加上原型对象的属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
class Cat {  
constructor () {
this.name = 'cat1'
}
name = 'cat2'
getName = function () {
console.log(this.name)
}
}
Cat.prototype.name = 'cat3'
var cat = new Cat()
cat.getName()

还是以constructor中的为准

9. 箭头函数和方法名相同综合例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Cat {  
constructor () {
this.name = 'guaiguai'
var type = 'constructor'
this.getType = () => {
console.log(this.type)
console.log(type)
}
}
type = 'class'
getType = () => {
console.log(this.type)
console.log(type)
}
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.getType() // class constructor
console.log(guaiguai) // Cat { name: 'guaiguai', getType: function() {}, type: 'class'}
10. 总结class
class的基本概念:
  • 当你使用class的时候,它会默认调用constructor这个函数,来接受一些参数,并构造一个新的实例对象(this)并将它返回
  • 当你的class没有定义constructor,也会隐式生产一个constructor方法
class几种定义属性的区别:
  • 在constructor中var一个变量,这个变量只存在于constructor这个构造函数中
  • 在constructor中使用this定义的属性和方法,会被定义到实例上面
  • 在class使用=来定义一个属性和方法,和上面相同,会被定义到实例上面
  • 在class直接定一个方法,会被添加到原型对象prototype上
  • 在class中使用static修饰符定义的属性和方法被认为是静态的,被添加到类本身,不会添加到实例上
other
  • class虽然是函数,但并不会向函数一样提升至作用域最顶端
  • 如遇class中尖头函数题目请参考构造函数来处理
  • 使用class生成的实例对象,也有会沿着原型链查找的功能