你盼世界,我盼望你无bug
。Hello 你们好!我是霖呆呆!前端
(这个号称全掘金最臭不要脸的男人又成功用标题把你骗了进来,哈哈 😄)面试
"先给你个三连"
segmentfault
滴滴滴~ 又是一星期没见了markdown
看着右侧目录这么一大排的题目1、题目2、题目三...
你是不很开心,终于...又有题作了。ide
你会发现霖呆呆的文章每期都是这么丰满,字动不动就是上万,题目动不动就是好几十题,我也很担忧大家会不会不想去看。函数
包括我本身其实也不是特别愿意去看一些大篇幅的文章。工具
(固然,炒鸡棒的文章除外哈,好比掘金上都超好看)oop
所以最近我转化了一种思路,将一些知识点化为题目,让咱们在作题的同时来消化理解它。post
这样就避免了整篇文章都是概念性的东西,有些枯燥😅。性能
(不过对于一些硬性必须记的东西你们也千万不能偷懒得记着啊)
并且这几天我发现了一些很奇怪的事情,有些读者就是冲着我文章的评论来的。就好比个人那篇Promise面试题,一哥们就挑明了和我说:
"我是直接看的评论,很精彩"
我是直接看评论的...
你跳过霖呆呆辛辛苦苦写的1.1w字
的内容,点到评论区看评论?!😭
最最最主要的是!!!😭
你还不点赞...不点赞...点赞...赞!!!😭
我...设计师...给我配个我在风中凌乱的表情包。
[表情包风中凌乱~]
"你?!开除!"
哈哈哈~
玩归玩闹归闹,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
就是语法糖。
(三)
原型链继承的思惟导图
(这个暂时看不懂不要紧,在继承那一章节中会讲到)
把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口。
(虽然如下内容是概念部分,可是对你解题颇有帮助哦,请务必仔细阅读它 😁)
都知道ES6
的class
实际就是一个语法糖,那么在ES6
以前,是没有类这个概念的,所以是借助于原型对象和构造函数来实现。
var
声明的属性)this
设置,或者设置在构造函数原型对象上好比Cat.prototype.xxx
)Cat.xxx
),不须要实例就能够调用(例如Object.assign()
)(理解私有属性方法和公有属性方法)
好比我如今想要封装一个生产出猫,名为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
承接的就是公有小猫咪: "凭啥每次都是用我举例子"
霖呆呆: "由于你可爱撒~"
小猫咪: "嘻嘻"
(理解静态属性方法和公有属性方法)
咱们如今往刚刚的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
中的,属于公有方法(实例方法),也就是乖乖
这个实例能够用它来调用。
静态属性和方法:
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
)
(理解实例自身的属性和定义在构造函数原型对象中的属性的区别)
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
却能访问和调用它们。
所以咱们得出结论:
定义在构造函数原型对象上的属性和方法虽然不能直接表如今实例对象上,可是实例对象却能够访问或者调用它们
既然咱们已经知道了实例自身的属性和定义在构造函数原型对象中的属性的区别,那么咱们通常是如何区分它们的呢?
来看看这里:
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()
方法传入属性名来判断一个属性是否是实例自身的属性(上面👆的说法其实并不太严谨,由于要创建在可枚举属性的前提下(属性的enumerable
为true
),不过这边我不发散下去了...)
下面让咱们来作道题,看看你到底有没有掌握上面的知识点呢 😁。
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
才是给构造函数上增长属性)。
若是个人构造函数和构造函数原型对象上存在相同名称的属性咋办呢 🤔️ ?
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
)。
也就是大名鼎鼎的原型链查找。
咱要是没理解不要紧哈,一块儿来看下面一个例子。
如今我在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/)
"等会等会,让我缓一下"
"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()
方法传入属性名来判断一个属性是否是实例自身的属性在ES6
以后,新增了class
这个关键字。
它能够用来代替构造函数,达到建立“一类实例”的效果。
而且类的数据类型就是函数,因此用法上和构造函数很像,直接用new
命令来配合它建立一个实例。
还有一件事你可能不知道吧,那就是,类的全部方法都定义在类的prototype属性上面。
例如:
class Cat {
constructor() {}
toString () {}
toValue () {}
}
// 等同于
function Cat () {}
Cat.prototype = {
constructor() {}
toString () {}
toValue () {}
}
复制代码
这个能够看下面👇的题目2.2
来理解它。
如今咱们将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 () {} }
❤️跳
'我跳起来了~来追我啊'
复制代码
(弄懂在类中定义属性或方法的几种方式)
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()
复制代码
请仔细看看这道题,在这里面我用了四种不一样的方式来定义一些属性。
constructor
中var
一个变量,它只存在于constructor
这个构造函数中constructor
中使用this
定义的属性和方法会被定义到实例上class
中使用=
来定义一个属性和方法,效果与第二点相同,会被定义到实例上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 () {}
}
复制代码
(在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
中定义的实例属性和方法,因此并不会有descript
和actingCute
jump
中的this
指向的是实例对象guaiguai
,而且执行了'我跳起来了~来追我啊'
直接定义在class
中的属性或者方法就至关因而定义在实例对象
上,因此也属于实例方法,cleanThebody
会执行打印出'我会用唾液清洁身体'
使用了static
定义的属性和方法为静态属性和方法,并不存在于实例上,因此打印出undefined
和报错
actingCute
使用了static
修饰符,因此它是静态方法,存在于Cat
这个类上,所以它里面的this
指向这个类,而且执行了'一听到猫我就想到了它会卖萌'
descript
使用了static
修饰符,因此它是静态属性,打印出'我这个类是用来生产出一只猫的'
Cat.staticName = 'staticName'
就至关于定义了一个静态属性,因此打印出staticName
咱们再来看看这道题,友情提示,这是个坑 🤮...
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
复制代码
坑二 🤮...
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
上的type
为class
type
的时候,发现getType
函数中并无这个变量,因此就向外层查找,找到了window
中存在这个变量,所以打印出window
。(var type = 'constructor'
是函数constructor
中的变量, 你也能够理解为是constructor
函数的私有变量)既然作到了函数类型的题目,那怎么能不想到箭头函数呢?嘿嘿 。
阴笑~
让咱们将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
题,里面介绍了构造函数对象中普通函数和箭头函数的区别。
若是在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
里定义的。
那么,原型对象中相同名称的属性和方法呢?
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
题。
好吧 😅,如今能够加大难度了:
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
的时候,它会默认调用constructor
这个函数,来接收一些参数,并构造出一个新的实例对象(this
)并将它返回。class
没有定义constructor
,也会隐式生成一个constructor
方法(二) class中几种定义属性的区别::
在constructor
中var
一个变量,它只存在于constructor
这个构造函数中
在constructor
中使用this
定义的属性和方法会被定义到实例上
在class
中使用=
来定义一个属性和方法,效果与第二点相同,会被定义到实例上
在class
中直接定义一个方法,会被添加到原型对象prototype
上
在class
中使用了static
修饰符定义的属性和方法被认为是静态的,被添加到类自己,不会添加到实例上
(三) other:
class
本质虽然是个函数,可是并不会像函数同样提高至做用域最顶层class
中箭头函数等题目请参照构造函数来处理class
生成的实例对象,也会有沿着原型链查找的功能知识无价,支持原创。
参考文章:
你盼世界,我盼望你无bug
。这篇文章就介绍到这里,让咱们先打好面向对象的基础,才能挑战后面的魔鬼
题目 😄。(才能继承家产,哈哈哈)
系列中的继承
和多态
霖呆呆也会在近几天给更出来,请期待一小下吧 😄。
最后,正经的给个三连吧 😂。
喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai
或者扫一扫下面的二维码👇👇👇.
我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉
你的鼓励就是我持续创做的主要动力 😊.
相关推荐:
《【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)》