0. 访问
书接上文:
new关键字它把构造器的this返回了出来,并在最开始的时候把对象和构造器就关联了起来。
咱们接下来讨论封装定义的第一句话
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;
总结来说,就是访问权限控制
先说最简单的访问权限控制,即防君子不防小人的做法:
1. 利用隐式变量
/*e.g.error*/
function Person(name, age) {
this.__name__ = name;
this.__age__ = age;
this.sayHello = ()=>console.log(this.__name__, 'say hello to you!');
}
但是这样写真的很随便,虽然说你用了隐式变量的命名方法,但是大家想改还是能改啊。
所以这里稍稍做一下升级
/*e.g.4*/
function Person(name, age) {
Object.defineProperties(this, {
__name__: {
value: name,
enumerable: false,
},
__age__: {
value: age,
enumerable: false,
},
});
this.sayHello = () => console.log(this.__name__, "say hello to you!");
}
for(i in p) console.log(i) // sayHello
这里的Object.defineProperties方法如果不清楚是什么可以看看这里
这样做完之后你可以通过__name__和 __age__属性访问,但是无法修改(默认writable属性是false)
但是教练啊,我想有getter,setter
2. 利用闭包搞个看不到的变量
如果不了解闭包,建议点击这里快速了解一下闭包【学习笔记-JS闭包】(属于自己和自己联动了)
不说废话,上代码
/*e.g.5*/
function Person(name, age){
this.sayHello = ()=>console.log(this.getName(), "say hello to you!");
this.getName = ()=>name;
this.setName = value=>{name=value};
this.getAge = ()=>age;
this.setAge = value=>{age=value};
}
具体get和set能做什么之讲到模式的时候会继续深入,这里就是它最简单的功能
当然,利用Object.defineProperties的get,set方法也能做到,本质也是闭包
/*e.g.4 plus*/
function Person(name, age) {
Object.defineProperties(this, {
'name': {
value: name,
enumerable: false,
get(){
return name;
},
set(value) {
name = value;
}
},
'age': {
value: age,
enumerable: false,
get(){
return age;
},
set(value) {
age = value;
}
},
});
this.sayHello = () => console.log(this.name, "say hello to you!");
}
3. 方法大战构造器
可能有很多小伙伴早早的有个疑惑,构造器是function,那是不是所有的function都能当做构造器来用呢?
从本质上来说,所有的function都可以搭配new关键字,但是并非所有的function都能作为构造器。
看下面这个列子
/*e.g.5*/
function Person1(name, age) {
console.log('this', this)
return {
name,
age,
sayHello(){
console.log(this.name, "say hello to you!");
}
}
}
/*e.g.5 plus*/
function Person2(p) {
return p
}
let Jack = {name: 'Jack'}
//this Person1 {}
let p1 = new Person1('Tom', 10) // {name: 'Tom', age: 10, sayHello: ƒ}
//this Window {window: Window...}
let p2 = Person1('Tom', 10) // {name: 'Tom', age: 10, sayHello: ƒ}
let p3 = new Person2(Jack) // {name: 'Jack'}
let p4 = Person2(Jack) // {name: 'Jack'}
p1 == p2 // false
p3 == p4 // true
观察一下结果
p1与p2 从样子上是一样的。都没有构造器,但是this指向还是变了的,也就是说,若有返回值的时候,则new关键字不再返回this,这是正常返回其返回值
p3与p4是为了证明 即便使用new关键字在有返回值的时候也不会重新构建对象,而是把运行结果直接返回给你。
但是有例外的情况,观察下面两个长的像方法的构造器
function cons1() {
return;
}
function cons2() {
return '1';
}
let c1 = new cons1(); //cons1 {}
let c2 = new cons2(); //cons2 {}
这里说一下结论:若方法返回的是对象,则使用new关键字依然会修改上下文(this)指向,但返回值按照原有对象放回。否则使用new关键字时会返回其上下文(this)
4. 深入:构造器的隐式拷贝行为
在看代码前咱们先讨论一个问题,为什么要有构造器。或者说构造器的目标是什么?
对,封装。封装的核心呢?一个是访问权限控制(这里js做的确实不是很好,虽然提供了技巧可以模拟,但也仅仅是模拟),另一个就是抽象了。
new这个关键字正式的翻译叫做实例化
let p = new Person()通过构造器Person实例化了一个对象
所以什么叫实例化,就是把一个抽象的概念的东西变成一个实实在在的,具体的东西。
具体的东西,它得是一个独立的个体对不对,所以,这个对象实际指向的内存地址应该是一个新的。
所以一个正常的构造器都会拷贝他作用域内的数据的。观察下面这个例子(最后一个例子——)
/*e.g.6*/
function CopyTest(){
this.dosome = window.dosome;
function timecount() {
let count = 0;
let timer = setInterval(function(){
count++
}, 1000)
function stopCount(){
clearInterval(timer)
}
return {
count: function(){return count},
stopCount
}
}
this.countShow = timecount();
}
function dosome(){
console.log('do some now')
}
let s1 = new CopyTest();
let s2 = new CopyTest();
s1.countShow == s2.countShow // false
s1.dosome == s2.dosome // true
/*这里是两套不同的定时器,可以随时调用stopCount()方法停止计时*/
s1.countShow.count()
s2.countShow.count()
在e.g.6中timecount方法位于CopyTest作用域内,所以它及其返回的对象完成了完整的拷贝,而dosome则是指向了全局上的dosome方法,处于CopyTest作用域外,拷贝的时候就不会去拷贝它了
延伸时间
/*e.g.6 plus*/
function dosome(obj){
console.log('do some now');
return obj.dosome;
}
s1.dosome(s1) == s1.dosome // 属于是属于是了
没什么好写的总结
面向对象的封装核心就在于new关键字上,若方法拥有返回值,那么它就不太合适作为一个构造器,但是构造器中是可以使用return关键字的。有的时候会有奇效,但是也容易导致混乱。本人才疏学浅,若文章中有纰漏请大家不吝赐教。感谢各位的阅读,最后祝您身体康健,再会。