学习笔记-JS的面向对象之封装(下)

356 阅读4分钟

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.definePropertiesget,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关键字的。有的时候会有奇效,但是也容易导致混乱。本人才疏学浅,若文章中有纰漏请大家不吝赐教。感谢各位的阅读,最后祝您身体康健,再会。