js——封装
封装基本概念
// 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 () {}

此处补充:
proto属性的两边各由两个下划线构成,读作“dunder proto”,“double underscore proto”的缩写,实际上,该属性在ES标准定义中的名字应该是[[Prototype]],具体实现是由浏览器代理自己实现,谷歌浏览器的实现就是将[[Prototype]]命名为proto,大家清楚这个标准定义与具体实现的区别即可(名字有所差异,功能是一样的)
原型链继承图

封装:
把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口
es6前:
首先,es6之前,没有类的概念,只能通过原型对象和构造函数模拟实现java中类的功能
- 私有属性和方法:只能在构造函数内访问不能被外部访问的属性和方法(在构造函数内使用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()
console.log(guaiguai.heart)
console.log(guaiguai.stomach)
guaiguai.heartbeat()
//Cat{ name: 'guaiguai', color: 'white', jump: function(){} }
//❤️跳
//我跳起来了~来追我啊
// undefined
// undefined
// 报错
分析:
实例化对象能被访问到的为公有属性:name,color,jump
访问不到的为私有属性:heart,somach,heartbeat
1.2
对构造函数Cat添加描述,表明是生产猫的,设置静态属性descript
设置卖萌为Cat的静态方法
设置清洁身体的动作为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('我跳起来了~来追我啊')
}
}
// 这段是新增的代码
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
实例(公有)属性和方法:name,color,jump,cleanTheBody
小结:
- 在构造函数上也就是使用Cat.xxx定义的是静态属性和方法
- 在构造函数内使用this设置,或者设置在构造函数原型对象上比如Cat.prototye.xxx,就是公有属性和方法(实例方法)
1.3
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))
//'我是自身属性' name
//'我不是自身属性' prototypeProp
//'我不是自身属性' cleanTheBody
//-分隔符-
//[name]
//[name]
分析:
使用for...in...能获取到实例对象自身的属性和原型链上的属性
使用Object.keys()和Object.getOwnPropertyNames()只能获取实例对象自身的属性
可以通过.hasOwnProperty()方法传入属性名来判断一个属性是不是实例自身的属性
注:仅限于属性为可枚举属性
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
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
分析:
原型链查找:
当访问一个对象的属性 / 方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。
1.7
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:f}
//cute

1.8
var obj = {
name: 'obj'
}
console.log(obj.toString())
console.log(obj.hasOwnProperty('name'))
console.log(Object.prototype)
//[object Object]
//true
//...
总结-构造函数
(一) 私有属性、公有属性、静态属性概念:
- 私有属性和方法:只能在构造函数内访问不能被外部所访问(在构造函数内使用
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后:
类的所有方法都定义在类的prototype属性上面
2.1
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()
//Cat{ name: 'guaiguai', color: 'white', jump: function () {} }
//❤️跳
//'我跳起来了~来追我啊'
分析:
用class声明的Cat,公有(实例)属性和方法:name,color,jump
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()
//Cat {color: "white", name: "guaiguai", cleanTheBody: ƒ, jump: ƒ}
//["color", "cleanTheBody", "name", "jump"]
//'我会用唾液清洁身体'
//'我在臭臭完之后会把它藏起来'
分析:
在constructor中var一个变量,它只存在于constructor这个构造函数中
在constructor中使用this定义的属性和方法会被定义到实例上
在class中使用=来定义一个属性和方法,效果与第二点相同,会被定义到实例上
在class中直接定义一个方法,会被添加到原型对象prototype上
heart只能在constructor函数中使用,因此不会出现在实例上。
name、jump、color、cleanTheBody满足于上面👆说到的第二点和第三点
hideTheShit是在类里直接定义的,满足于上面👆说的第四点,因此它不会被Object.keys()获取到。
hideTheShit虽然是在原型对象中,但是也还是能被实例对象所调用,因此最后一段代码也会被执行’我在臭臭完之后会把它藏起来’
2.3
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 functionclass
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
2.4
var a = new A()
function A () {}
console.log(a)
var b = new B()
class B {}
console.log(b)
//A {}
//Uncaught ReferenceError: Cannot access 'B' before initialization
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
//window
分析:
调用getType函数的是guaiguai,所以里面的this指向了guaiguai,而guaiguai上的type为class
当要打印出type的时候,发现getType函数中并没有这个变量,所以就向外层查找,找到了window中存在这个变量,因此打印出window。(var type = 'constructor'是函数constructor中的变量, 你也可以理解为是constructor函数的私有变量)
2.6
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)
//class
//window
//Cat{name:guaiguai,type:class,getType:f}
2.7
class Cat {
constructor () {
this.name = 'cat1'
}
name = 'cat2'
getName = function () {
console.log(this.name)
}
}
var cat = new Cat()
cat.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
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)
//class
//constructor
//Cat{name:guaiguai,type:class,getType:f}
总结-class
(一) class的基本概念:
- 当你使用
class的时候,它会默认调用constructor这个函数,来接收一些参数,并构造出一个新的实例对象(this)并将它返回。 - 如果你的
class没有定义constructor,也会隐式生成一个constructor方法
(二) class中几种定义属性的区别::
- 在
constructor中var一个变量,它只存在于constructor这个构造函数中 - 在
constructor中使用this定义的属性和方法会被定义到实例上 - 在
class中使用=来定义一个属性和方法,效果与第二点相同,会被定义到实例上 - 在
class中直接定义一个方法,会被添加到原型对象prototype上 - 在
class中使用了static修饰符定义的属性和方法被认为是静态的,被添加到类本身,不会添加到实例上
(三) other:
class本质虽然是个函数,但是并不会像函数一样提升至作用域最顶层- 如遇
class中箭头函数等题目请参照构造函数来处理 - 使用
class生成的实例对象,也会有沿着原型链查找的功能
参考文章:
作者:LinDaiDai_霖呆呆
原文链接
注:呆呆大佬的文章让我获益良多,此处仅作记录,证明我看过,我做过,哈哈
本文详细探讨了JavaScript中如何使用构造函数、原型对象和类(ES6引入)来封装猫类,包括私有属性和方法、公有属性、静态属性与方法的区分。实例展示了如何创建猫实例,访问和操作不同类型的属性,并讨论了类中属性定义的规则和原型链的查找机制。

1038

被折叠的 条评论
为什么被折叠?



