🔥【何不三连】比继承家业还要简单的JS继承题-封装篇(牛刀小试)

前言

你盼世界,我盼望你无bug。Hello 你们好!我是霖呆呆!前端

(这个号称全掘金最臭不要脸的男人又成功用标题把你骗了进来,哈哈 😄)面试

"先给你个三连"segmentfault

滴滴滴~ 又是一星期没见了markdown

看着右侧目录这么一大排的题目1、题目2、题目三...你是不很开心,终于...又有题作了。ide

你会发现霖呆呆的文章每期都是这么丰满,字动不动就是上万,题目动不动就是好几十题,我也很担忧大家会不会不想去看。函数

包括我本身其实也不是特别愿意去看一些大篇幅的文章。工具

(固然,炒鸡棒的文章除外哈,好比掘金上都超好看)oop

所以最近我转化了一种思路,将一些知识点化为题目,让咱们在作题的同时来消化理解它。post

这样就避免了整篇文章都是概念性的东西,有些枯燥😅。性能

(不过对于一些硬性必须记的东西你们也千万不能偷懒得记着啊)

并且这几天我发现了一些很奇怪的事情,有些读者就是冲着我文章的评论来的。就好比个人那篇Promise面试题,一哥们就挑明了和我说:

"我是直接看的评论,很精彩"

我是直接看评论的...

你跳过霖呆呆辛辛苦苦写的1.1w字的内容,点到评论区看评论?!😭

最最最主要的是!!!😭

你还不点赞...不点赞...点赞...赞!!!😭

我...设计师...给我配个我在风中凌乱的表情包。

[表情包风中凌乱~]

"你?!开除!"

哈哈哈~

玩归玩闹归闹,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以前的封装

(虽然如下内容是概念部分,可是对你解题颇有帮助哦,请务必仔细阅读它 😁)

都知道ES6class实际就是一个语法糖,那么在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(){} }
❤️跳
我跳起来了~来追我啊
复制代码

能够看到,咱们生产出名字叫作乖乖的小猫咪只有这几个属性能访问到(也就是能被肉眼看到),为公有属性:

  • name
  • color
  • jump

而私有属性,是咱们看不到的:

  • heart
  • somach
  • heartbeat

因此若是你想要直接使用它是不可以的:

// 私有
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
'我会用唾液清洁身体'
复制代码

能够看到,咱们定义的descriptactingCute是定义在构造函数Cat上的,因此能够直接被Cat调用,为静态属性和方法。

可是descriptactingCute并不能存在于乖乖这个实例上,descript只是对构造函数Cat的描述,并非对乖乖的描述,因此打印出undefined

不过清洁身体是定义在原型对象prototype中的,属于公有方法(实例方法),也就是乖乖这个实例能够用它来调用。

静态属性和方法:

  • descript
  • actingCute

实例(公有)属性和方法:

  • name
  • color
  • jump
  • cleanTheBody

小结:

也很好区分:

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

(也有小伙伴可能会有疑问,这个静态属性和方法是有什么用的啊,感受咱们编码的时候并无用到过啊。Really? 哈哈 😄,Promise.all()、Promise.race()、Object.assign()、Array.from()这些不就是吗?)

(至于实例方法,想一想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"]
复制代码

由此能够得出:

  • 使用for...in...能获取到实例对象自身的属性和原型链上的属性
  • 使用Object.keys()Object.getOwnPropertyNames()只能获取实例对象自身的属性
  • 能够经过.hasOwnProperty()方法传入属性名来判断一个属性是否是实例自身的属性

(上面👆的说法其实并不太严谨,由于要创建在可枚举属性的前提下(属性的enumerabletrue),不过这边我不发散下去了...)

1.5 题目五

下面让咱们来作道题,看看你到底有没有掌握上面的知识点呢 😁。

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)
复制代码

答案:

'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
复制代码

解析:

  • name为公有属性,实例访问它打印出'lindaidai'
  • evil为私有属性,实例访问它打印出'undefined'
  • drawing是共有(实例)方法,实例调用它打印出'我要画一幅国画'
  • pickNose是私有方法,实例调用它会报错,由于它并不存在于实例上
  • fight是静态方法,实例调用它报错,由于它并不存在于实例上
  • wc存在于构造函数的原型对象中,使用实例调用它打印出'我是个我会wc'
  • fight存在于构造函数上,使用构造函数调用它打印出'打架'
  • wc存在于构造函数的原型对象中,并不存在于构造函数中,因此报错
  • sex为公有(实例)属性,并不存在于构造函数上,使用构造函数访问它为undefined

这里你们可能会有一个疑惑点了,为何最后一个Person.sex也会是undefined呢?

我明明已经这样写了:

function Person (sex) {
this.sex = sex
}
复制代码

看起来sex是定义在Person里的呀。

注意了,this.sex表示的是给使用构造函数建立的实例上增长属性sex,而不是给构造函数自己增长(只有Person.sex才是给构造函数上增长属性)。

1.6 题目六

若是个人构造函数和构造函数原型对象上存在相同名称的属性咋办呢 🤔️ ?

function Cat () {
this.color = 'white'
this.getColor = function () {
console.log(this.color)
}
}
Cat.prototype.color = 'black'
var cat = new Cat()
cat.getColor()
复制代码

这里的执行结果为:

'white'
复制代码

其实这个很好理解,你原型对象上虽然有一个名叫color的属性,可是我实例对象本身就也有一个啊,那我为何要用你的呢?只有我本身没有,我才会到你那里去拿。

因此这也就引出了另外一个常常听到的概念:

当访问一个对象的属性 / 方法时,它不只仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。

也就是大名鼎鼎的原型链查找

咱要是没理解不要紧哈,一块儿来看下面一个例子。

1.7 题目七

如今我在Cat的原型对象上,还有它原型对象的原型对象上都定义一个叫作color的属性。

(原型对象本质也是个对象,因此它的__proto__也就是Object.prototype)

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()
console.log(cat)
console.log(cat.feature)
复制代码

而后让咱们来看看结果:

'white'
Cat {color: "white", getColor: ƒ}
'cute'
复制代码

看到了不。

color这个属性仍是以它自身的white为主,可是feature这个属性没在实例cat上吧,因此它就会向上一层一层查找,结果在Object.prototype中找到了,所以打印出cute

整个过程就是这样:

(偷个懒,盗个图😂,图片来源https://muyiy.cn/idea/)

1.8 题目八

"等会等会,让我缓一下"

"wc,我忽然就想明白了不少事情!"

好比下面这种写法:

var obj = { name: 'obj' }
console.log(obj.toString())
console.log(obj.hasOwnProperty('name'))
console.log(Object.prototype)
复制代码

为何个人obj中明明就没有toString()、hasOwnProperty()方法,可是我却能够调用它。

如今我知道了,原来obj本质是个Object类型。

使用var obj = { name: 'obj' }就至关因而调用了new Object

var obj = new  Object({ 'name': 'obj' })
复制代码

这样的话,我固然就可使用Object.prototype上的方法啦!

执行结果为:

(obj.toString()这里的结果为[object Object]应该都知道是什么缘由吧?)

总结-构造函数

如今在来回头看看那句话:

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

是否是好理解多了呢?

而后让咱们对构造函数配合原型对象封装来作一个总结吧:

(一) 私有属性、公有属性、静态属性概念:

  • 私有属性和方法:只能在构造函数内访问不能被外部所访问(在构造函数内使用var声明的属性),见题1.1
  • 公有属性和方法(或实例方法):对象外能够访问到对象内的属性和方法(在构造函数内使用this设置,或者设置在构造函数原型对象上好比Cat.prototype.xxx),见题1.2
  • 静态属性和方法:定义在构造函数上的方法(好比Cat.xxx),不须要实例就能够调用(例如Object.assign())

(二) 实例对象上的属性和构造函数原型上的属性:

  • 定义在构造函数原型对象上的属性和方法虽然不能直接表如今实例对象上,可是实例对象却能够访问或者调用它们。(见题1.3)
  • 当访问一个对象的属性 / 方法时,它不只仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。(见题1.7)

(三) 遍历实例对象属性的三种方法:

  • 使用for...in...能获取到实例对象自身的属性和原型链上的属性
  • 使用Object.keys()Object.getOwnPropertyNames()只能获取实例对象自身的属性
  • 能够经过.hasOwnProperty()方法传入属性名来判断一个属性是否是实例自身的属性

2. ES6以后的封装

ES6以后,新增了class 这个关键字。

它能够用来代替构造函数,达到建立“一类实例”的效果。

而且类的数据类型就是函数,因此用法上和构造函数很像,直接用new命令来配合它建立一个实例。

还有一件事你可能不知道吧,那就是,类的全部方法都定义在类的prototype属性上面

例如:

class Cat {
constructor() {}
toString () {}
toValue () {}
}
// 等同于
function Cat () {}
Cat.prototype = {
constructor() {}
toString () {}
toValue () {}
}
复制代码

这个能够看下面👇的题目2.2来理解它。

2.1 题目一

如今咱们将1.1的题目换成class版本的来看看。

class Cat {
constructor (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()
复制代码

其实你会发现,当你使用class的时候,它会默认调用constructor这个函数,来接收一些参数,并构造出一个新的实例对象(this)并将它返回,所以它被称为constructor构造方法(函数)。

(另外,其实若是你的class没有定义constructor,也会隐式生成一个constructor方法)

能够看到,通过用class改造后的Cat

公有(实例)属性和方法:

  • name
  • color
  • jump

而对于私有属性,我的感受上述的heart不该该叫作私有属性,它只不过被局限于constructor这个构造函数中,是这个做用域下的变量而已。

执行结果:

Cat{ name: 'guaiguai', color: 'white', jump: function () {} }
❤️跳
'我跳起来了~来追我啊'
复制代码

2.2 题目二

(弄懂在类中定义属性或方法的几种方式)

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)
console.log(Object.keys(guaiguai))
guaiguai.cleanTheBody()
guaiguai.hideTheShit()
复制代码

请仔细看看这道题,在这里面我用了四种不一样的方式来定义一些属性。

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

至此,这道题的答案为:

Cat {color: "white", name: "guaiguai", cleanTheBody: ƒ, jump: ƒ}
["color", "cleanTheBody", "name", "jump"]
'我会用唾液清洁身体'
'我在臭臭完以后会把它藏起来'
复制代码

解析:

  • heart只能在constructor函数中使用,所以不会出如今实例上。
  • name、jump、color、cleanTheBody知足于上面👆说到的第二点和第三点
  • hideTheShit是在类里直接定义的,知足于上面👆说的第四点,所以它不会被Object.keys()获取到。
  • hideTheShit虽然是在原型对象中,可是也仍是能被实例对象所调用,所以最后一段代码也会被执行'我在臭臭完以后会把它藏起来'

这四种定义的方式已经介绍完了 😁,相信你们比较迷惑的一点就是如下这两种方式的定义吧:

class Cat {
cleanTheBody = function () {}
hideTheShit () {}
}
复制代码

看起来都是定义一个函数呀,为何第一个就能够在实例对象中,而第二个是在原型对象中呢 🤔️ ?

其实不须要特地的去记住它,你只须要知道:在类的全部方法都定义在类的prototype属性上面

这里的cleanTheBody你能够理解为它和color同样只是一个普通的变量,只不过这个变量是个函数,因此它并不算是定义在类上的函数,所以不会存在于原型对象上。

hideTheShit是实实在在的定义在类上的方法,因此它和constructor方法同样,都是在类的原型对象上。

转化为伪代码就是:

class Cat {
constructor() {}
hideTheShit () {}
}
// 等同于
function Cat () {}
Cat.prototype = {
constructor() {}
hideTheShit () {}
}
复制代码

2.3 题目三

(在class定义静态属性和方法)

前面咱们给Cat定义静态属性和方法是采用这种方式,Cat.xxx

function Cat () {...}
Cat.descript = '我这个构造函数是用来生产出一只猫的'
Cat.actingCute = function () {
console.log('一听到猫我就想到了它会卖萌')
}
复制代码

class中你也可使用Cat.xxx这种方式定义,由于前面说过了,class本质也是个对象。

但除此以外,你还可使用static标识符表示它是一个静态的属性或者方法:

class Cat {
static descript = '我这个类是用来生产出一只猫的'
static actingCute () {
console.log('一听到猫我就想到了它会卖萌')
}
// static actingCute = function () {} // 这种写法也是设置静态的方法
}
复制代码

OK👌,如今让咱们来作作下面这道题吧 😊:

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)
guaiguai.jump()
guaiguai.cleanTheBody()
console.log(guaiguai.descript)
guaiguai.actingCute()

Cat.actingCute()
console.log(Cat.descript)
console.log(Cat.staticName)
复制代码

结果:

❤️跳
Cat{ name: 'guaiguai', color: 'white', jump: function(){}, cleanTheBody: function(){} }
Cat{ name: 'guaiguai', color: 'white', jump: function(){}, cleanTheBody: function(){} }
'我跳起来了~来追我啊'
'我会用唾液清洁身体'
undefined
Uncaught TypeError: guaiguai.actingCute is not a function

class Cat{...}
'一听到猫我就想到了它会卖萌'
'我这个类是用来生产出一只猫的'
'staticName'
复制代码

结果分析:

  • 首先在构造guaiguai这个对象的时候会执行heartbeat方法,打印出❤️跳

  • 其次打印出的guaiguai它只会拥有class中定义的实例属性和方法,因此并不会有descriptactingCute

  • jump中的this指向的是实例对象guaiguai,而且执行了'我跳起来了~来追我啊'

  • 直接定义在class中的属性或者方法就至关因而定义在实例对象上,因此也属于实例方法,cleanThebody会执行打印出'我会用唾液清洁身体'

  • 使用了static定义的属性和方法为静态属性和方法,并不存在于实例上,因此打印出undefined和报错

  • actingCute使用了static修饰符,因此它是静态方法,存在于Cat这个类上,所以它里面的this指向这个类,而且执行了'一听到猫我就想到了它会卖萌'

  • descript使用了static修饰符,因此它是静态属性,打印出'我这个类是用来生产出一只猫的'

  • Cat.staticName = 'staticName'就至关于定义了一个静态属性,因此打印出staticName

2.4 题目四

咱们再来看看这道题,友情提示,这是个坑 🤮...

var a = new A()
function A () {}
console.log(a)

var b = new B()
class B {}
console.log(b)
复制代码

你开始的预想是否是:

A{}
B{}
复制代码

😁,结果却发现报错了:

A {}
Uncaught ReferenceError: Cannot access 'B' before initialization
复制代码

那是由于,函数A是会被提高至做用域的最顶层,因此能够在定义函数A以前使用new A()

可是类却不存在这种提高机制,因此当你执行new B()的时候它就会告诉你在B没有初始化以前不能使用它。

尽管咱们知道,class它的本质也是一个函数:

console.log(typeof B) // function
复制代码

2.5 题目五

坑二 🤮...

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的理解。

答案为:

'class'
'window'
复制代码

解析:

  • 调用getType函数的是guaiguai,因此里面的this指向了guaiguai,而guaiguai上的typeclass
  • 当要打印出type的时候,发现getType函数中并无这个变量,因此就向外层查找,找到了window中存在这个变量,所以打印出window。(var type = 'constructor'是函数constructor中的变量, 你也能够理解为是constructor函数的私有变量)

2.6 题目六

既然作到了函数类型的题目,那怎么能不想到箭头函数呢?嘿嘿 。

阴笑~

让咱们将2.5中的getType函数换成箭头函数看看?

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()
console.log(guaiguai)
复制代码

如今调用guaiguai.getType()你以为会是啥?

"既然箭头函数内的this是由外层做用域决定的,那这里外层做用域是window,固然this.type就是'window'咯"

咦~

还记得我以前说过的,class的本质是个函数吗?因此你碰到class内有箭头函数的题目,把它当成构造函数建立对象来处理就能够了。

在构造函数中若是使用了箭头函数的话,this指向的就是这个实例对象。

所以将class转化为构造函数的话,伪代码为:

function Cat () {
this.type = 'class'
this.getType = () => {
console.log(this.type)
console.log(type)
}
}
Cat.prototype.constructor = function () {
this.name = 'guaiguai'
var type = 'constructor'
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.constructor()
guaiguai.getType()
console.log(guaiguai)
复制代码

别的都好理解,这里为啥,constructor要放在原型对象中,而且要在var guaiguai = new Cat()下面再调用它呢?

嘻嘻,还记得在2.2中咱们就说过了吗,任何放在类上的方法都至关于写在原型对象上,而且在使用类的时候,会隐式执行constructor函数。这两段代码就是为了模拟这个操做。

这样的话,上面👆两个题目的结果都是:

'class'
'window'
Cat {type: "class", name: "guaiguai", getType: ƒ}
复制代码

哇~

有点意思哈~

class还能这样玩?😁

霖呆呆你....你臭不要脸

不过上面对于箭头函数还有不理解的小伙伴能够查看这篇

《【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)》

文章中的7.4题,里面介绍了构造函数对象中普通函数和箭头函数的区别

2.7 题目七

若是在class中存在两个相同的属性或者方法会怎么样呢 🤔️?

class Cat {
constructor () {
this.name = 'cat1'
}
name = 'cat2'
getName = function () {
console.log(this.name)
}
}
var cat = new Cat()
cat.getName()
复制代码

这道题中,咱们调用getName方法,打印出的会是:

'cat1'
复制代码

因此能够看出constructor中定义的相同名称的属性和方法会覆盖在class里定义的。

2.8 题目八

那么,原型对象中相同名称的属性和方法呢?

class Cat {
constructor () {
this.name = 'cat1'
}
name = 'cat2'
getName = function () {
console.log(this.name)
}
}
Cat.prototype.name = 'cat3'
var cat = new Cat()
cat.getName()
复制代码

答案:

'cat1'
复制代码

没错,仍是以constructor中的为准。这里和构造函数中同名属性的处理方式是同样的,能够看上面👆的1.7题。

2.9 题目九

好吧 😅,如今能够加大难度了:

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()
console.log(guaiguai)
复制代码

首先咱们很清楚,若是type打印出的是window那就表示使用的是第二个getType,不然表示用的是第一个getType

那么根据题2.7,咱们能够看出,第一个getType是会覆盖第二个的,因此执行结果为:

'class'
'constructor'
Cat {type: "class", name: "guaiguai", getType: ƒ}
复制代码

总结-class

来吧,对class实现封装也来作个总结呗:

(一) class的基本概念:

  • 当你使用class的时候,它会默认调用constructor这个函数,来接收一些参数,并构造出一个新的实例对象(this)并将它返回。
  • 若是你的class没有定义constructor,也会隐式生成一个constructor方法

(二) class中几种定义属性的区别:

  • constructorvar一个变量,它只存在于constructor这个构造函数中

  • constructor中使用this定义的属性和方法会被定义到实例上

  • class中使用=来定义一个属性和方法,效果与第二点相同,会被定义到实例上

  • class中直接定义一个方法,会被添加到原型对象prototype

  • class中使用了static修饰符定义的属性和方法被认为是静态的,被添加到类自己,不会添加到实例上

(三) other:

  • class本质虽然是个函数,可是并不会像函数同样提高至做用域最顶层
  • 如遇class中箭头函数等题目请参照构造函数来处理
  • 使用class生成的实例对象,也会有沿着原型链查找的功能

后语

知识无价,支持原创。

参考文章:

你盼世界,我盼望你无bug。这篇文章就介绍到这里,让咱们先打好面向对象的基础,才能挑战后面的魔鬼题目 😄。(才能继承家产,哈哈哈)

系列中的继承多态霖呆呆也会在近几天给更出来,请期待一小下吧 😄。

最后,正经的给个三连吧 😂。

喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai 或者扫一扫下面的二维码👇👇👇.

我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉

你的鼓励就是我持续创做的主要动力 😊.

相关推荐:

《全网最详bpmn.js教材》

《你的掘金文章本能够这么炫(博客美化工具一波带走)》

《【建议改为】读完这篇你还不懂Babel我给你寄口罩》

《【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)》

《【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)》

《💦【何不三连】作完这48道题完全弄懂JS继承(1.7w字含辛整理-返璞归真)》

相关文章
相关标签/搜索