比继承家业还要简单的JS继承题_封装篇|博客系列
# JS继承系列介绍
看过文章的小伙伴应该都感觉的出,我比较喜欢针对每个知识点出一些比较细节的题目,然后将这些细节连串起来最后组合成大家最爱的综合题😄。
该系列主要为了让我们彻底理解JavaScript面向对象的三大特性:封装、继承、多态。
"咦~这三大特性我知道啊,大清都完了你还在这谈"
啊~ 看到这里你先别着溜,开始的我也是和你一样觉得背背概念,写点小例子就懂了,直到自己给自己出了几道魔鬼题,我才发现之前对它们的理解还是不太全面…因此才有了本系列。
系列总目录:
-
封装
ES6之前的封装-构造函数ES6之后的封装-class
-
继承
- 原型链继承
- 构造继承
- 组合继承
- 寄生组合继承
- 原型式继承
class中的extends继承
-
多态
(在开始写之前本想要一篇文章全部搞定的,但是发现字真太多了,所以才分开来写,而且我终于又可以用我最爱的绯红主题了 😄)
这一章节主要是想向大家介绍一下JS面向对象的第一大特性-封装,也是为了给后面最重要的继承打好基础。
题目也不太多,总共17道,算是牛刀小试吧。
通过阅读本章节你可以学习到:
ES6之前的封装-构造函数ES6之后的封装-class
# 前期准备
先来理解一些最最最基本的概念:
(一)
// 1. 构造函数
function Cat (name) {
this.name
}
// 2. 构造函数原型对象
Cat.prototype
// 3. 使用Cat构造函数创建的实例'乖乖'
var guaiguai = new Cat('guaiguai')
// 4. 构造函数的静态方法,名为fn
Cat.fn = function () {}
// 5. 原型对象上的方法,名为fn
Cat.prototype.fn = function () {}

(二)
语法糖的意思是现有技术本可以实现,但是采用某种写法会更加简洁优雅。
比如class就是语法糖。
(三)
原型链继承的思维导图
(这个暂时看不懂没关系,在继承那一章节中会讲到)

# 封装
把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口。
# 1. ES6之前的封装
(虽然以下内容是概念部分,但是对你解题很有帮助哦,请务必仔细阅读它 😁)
都知道ES6的class实际就是一个语法糖,那么在ES6之前,是没有类这个概念的,因此是借助于原型对象和构造函数来实现。
- 私有属性和方法:只能在构造函数内访问不能被外部所访问(在构造函数内使用
var声明的属性) - 公有属性和方法(或实例方法):对象外可以访问到对象内的属性和方法(在构造函数内使用
this设置,或者设置在构造函数原型对象上比如Cat.prototype.xxx) - 静态属性和方法:定义在构造函数上的方法(比如
Cat.xxx),不需要实例就可以调用(例如Object.assign())
# 1.1 题目一
(理解私有属性方法和公有属性方法)
比如我现在想要封装一个生产出猫,名为Cat的构造函数。
- 由于猫的
心和胃都是我们肉眼看不见的,所以我把它们设置为私有属性(隐藏起来) - 并且猫的
心跳我们也是看不到的,所以我把它设置为私有方法(隐藏起来) - 然后猫的
毛色是可以看见的,所以我把它设置为公有属性 - 并且猫
跳起来这个动作我们是看的到的,所以我把它设置为公有方法
function Cat (name, color) {
var heart = '❤️'
var stomach = '胃'
var heartbeat = function () {
console.log(heart + '跳')
}
this.name = name
this.color = color
this.jump = function () {
heartbeat() // 能跳起来表明这只猫是活的,心也就能跳
console.log('我跳起来了~来追我啊')
}
}
var guaiguai = new Cat('guaiguai', 'white')
console.log(guaiguai)
guaiguai.jump()
上述代码打印出来的应该是:
Cat{ name: 'guaiguai', color: 'white', jump: function(){} }
❤️跳
我跳起来了~来追我啊
可以看到,我们生产出名字叫做乖乖的小猫咪只有这几个属性能访问到(也就是能被肉眼看到),为公有属性:
namecolorjump
而私有属性,是我们看不到的:
heartsomachheartbeat
所以如果你想要直接使用它是不能够的:
// 私有
console.log(guaiguai.heart) // undefined
console.log(guaiguai.stomach) // undefined
guaiguai.heartbeat() // 报错
小结:
很好区分:
- 在函数内用
var定义的就是私有的 - 在函数内用
this承接的就是公有
# 1.2 题目二
(理解静态属性方法和公有属性方法)
我们现在往刚刚的Cat构造函数中加些东西。
-
我们需要对
Cat这个构造函数加一个描述,表明它是用来生产猫的,所以我把descript设置为它的静态属性 -
由于一听到猫这种动物就觉得它会卖萌,所以我把
卖萌这个动作设置为它的静态方法 -
由于猫都会用唾液清洁身体,所以我把
清洁身体这个动作设置为它的公有方法
// 这段是旧代码
function Cat (name, color) {
var heart = '❤️'
var stomach = '胃'
var heartbeat = function () {
console.log(heart + '跳')
}
this.name = name
this.color = color
this.jump = function () {
heartbeat() // 能跳起来表明这只猫是活的,心也就能跳
console.log('我跳起来了~来追我啊')
}
}
// 这段是新增的代码
Cat.descript = '我这个构造函数是用来生产出一只猫的'
Cat.actingCute = function () {
console.log('一听到猫我就想到了它会卖萌')
}
Cat.prototype.cleanTheBody = function () {
console.log('我会用唾液清洁身体')
}
var guaiguai = new Cat('guaiguai', 'white')
console.log(Cat.descript)
Cat.actingCute()
console.log(guaiguai.descript)
guaiguai.cleanTheBody()
上述代码打印出来的应该是:
'我这个构造函数是用来生产出一只猫的'
'一听到猫我就想到了它会卖萌'
undefined
'我会用唾液清洁身体'
可以看到,我们定义的descript和actingCute是定义在构造函数Cat上的,所以可以直接被Cat调用,为静态属性和方法。
但是descript和actingCute并不能存在于乖乖这个实例上,descript只是对构造函数Cat的描述,并不是对乖乖的描述,所以打印出undefined。
不过清洁身体是定义在原型对象prototype中的,属于公有方法(实例方法),也就是乖乖这个实例可以用它来调用。
静态属性和方法:
descriptactingCute
实例(公有)属性和方法:
namecolorjumpcleanTheBody
小结:
也很好区分:
- 在构造函数上也就是使用
Cat.xxx定义的是静态属性和方法 - 在构造函数内使用
this设置,或者设置在构造函数原型对象上比如Cat.prototype.xxx,就是公有属性和方法(实例方法)
(也有小伙伴可能会有疑问,这个静态属性和方法是有什么用的啊,感觉我们编码的时候并没有用到过啊。Really? 哈哈 😄,Promise.all()、Promise.race()、Object.assign()、Array.form()这些不就是吗?)
(至于实例方法,想想push、shift,它们实际上不是存在于原型对象上的吗?Array.prototype.push)
# 1.3 题目三
(理解实例自身的属性和定义在构造函数原型对象中的属性的区别)
OK👌你刚刚既然已经说了使用this.xxx = xxx的方式和使用Cat.prototype.xxx = xxx都是属于实例对象guaiguai上的公有属性,那它们是有什么区别吗?
来看这道题我们就能理解它们的区别了:
function Cat (name) {
this.name = name
}
Cat.prototype.prototypeProp = '我是构造函数原型对象上的属性'
Cat.prototype.cleanTheBody = function () {
console.log('我会用唾液清洁身体')
}
var guaiguai = new Cat('guaiguai')
console.log(guaiguai)
console.log(guaiguai.name)
console.log(guaiguai.prototypeProp)
guaiguai.cleanTheBody()
这里输出的结果 🤔️?
Cat {name: "guaiguai"}
'guaiguai'
'我是构造函数原型对象上的属性'
'我会用唾液清洁身体'
看到没,name是使用this.xxx = xxx的形式定义的,它能直接让实例guaiguai就拥有这个属性。
而prototypeProp 、cleanTheBody毕竟是定义在构造函数原型上的,所以并不能出现在实例guaiguai上,但是guaiguai却能访问和调用它们。
因此我们得出结论:
定义在构造函数原型对象上的属性和方法虽然不能直接表现在实例对象上,但是实例对象却可以访问或者调用它们
# 1.4 题目四
既然我们已经知道了实例自身的属性和定义在构造函数原型对象中的属性的区别,那么我们一般是如何区分它们的呢?
来看看这里:
function Cat (name) {
this.name = name
}
Cat.prototype.prototypeProp = '我是构造函数原型对象上的属性'
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.keys(guaiguai))
console.log(Object.getOwnPropertyNames(guaiguai))
这道题中,我分别用了三种方式来获取实例对象guaiguai上的属性名:
for...in...Object.keys()Object.getOwnPropertyNames()
输出的结果为:
'我是自身属性 name'
'我不是自身属性 prototypeProp'
'我不是自身属性 cleanTheBody'
'-分隔符-'
["name"]
["name"]
