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

1,366 阅读5分钟

0. JS是面向对象的么?

我的观点是,js确实是面向对象的一门语言,不过,它的机制非常的诡异。受限制于它诞生的环境,它确实算不上一门优秀的语言,但并不妨碍它具有面向对象的特征:封装,继承,多态。

今天就先来介绍JS的封装。

1. 封装的定义:

封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。

2. JS的封装

可能有的同学要说了,太简单了,js的封装就是这样的

/*e.g.1*/
var a = {};
a.name = 'Jack';
a.age = 18;
a.isRun = false;
a.run = function(){
  a.isRun = true;
  return a.name + ' is run now!'
}
a.stop = function() {
  a.isRun = false;
  return a.name + ' is stop now!'
}

确实,你可以通过a对象,访问它的属性,它的方法,a把属性它方法综合成为了一个整体。但是请注意这句话将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,里面提到了一个关键字抽象

那么,'Jack'它抽象么?18它抽象么?

显然这里的东西都是具象的,这只是一个对象。一门语言,只有对象他就面向对象了么?不可能啊。

所以咱们是怎么完成抽象的呢?

就是通过关键字new

3. 关键字new实现的封装

简单说一下new的使用

/*e.g.2*/
/*Person is a constructor*/
function Person(name, age){
  this.name = name;
  this.age = age;
  this.run = function(){
    this.isRun = true; // 这里不是很标准,应该提前声明一下,但是js也是一门动态语言,稍稍偷懒一下
    return this.name + ' is run now!'
  }
  this.stop = function() {
    this.isRun = false;
    return this.name + ' is stop now!'
  }
}

let Jack = new Person('Jack', 18) //Person {name: 'Jack', age: 18, run: ƒ, stop: ƒ}

咱们这里就基于一个抽象的构造器生成了一个对象。

一个标准的构造器里面应该具有的相关的属性,方法,是否初始化则取决于你的使用方法,并不是必须的。

4. new返回了什么

显然,返回了一个对象。但是这个对象和普通的对象不太一样

/*e.g.1*/
a;//{name: 'Jack', age: 18, isRun: false, run: ƒ, stop: ƒ}
/*e.g.2*/
let Jack = new Person('Jack', 18) //Person {name: 'Jack', age: 18, run: ƒ, stop: ƒ}

除开isRun的部分,大家发现,例2的返回值比例多了一个Person。这个东西就是这个对象的构造器。每个对象有它的构造器

好了,一定有人要反驳我了:“胡说,明明a就没有构造器,你甚至都没有用关键字new,哪来的构造器”

接下来我为大家证明一下,a确实有构造器,但是会稍稍涉及到一点点继承的知识,这里先不过多的纠结它,就认为是个默认写法吧。因为面向对象它是一个闭环的知识链,在对她理解的过程中一定会有没法用现有知识解释的问题,等这个闭环全部听完之后,这些问题也就迎刃而解了。

/*继续e.g.1代码*/
//在Object的原型上添加方法hello,并输出字符串hello
Object.prototype.hello = ()=>{console.log('hello')}

a.hello()// hello

证毕!完结,撒花!

别动手!这里确实丢失了一点中间的推理。丢失的部分是连个结论性的内容

  1. 在不修改原型指向的情况下,对象的double underscore proto指向它的构造器的Prototype
  2. double underscore protoproto__翻译做隐式原型链,为啥叫隐式呢?一般(双下划线)开头并结尾的属性和方法是不希望你直接使用和操作的,这种属性我们一般称它为隐式
  3. Prototype 原型,是所有方法上都具备的一个属性,所有原型的顶层在哪呢?在Function上,Function的隐式原型链指向 Function的原型,所有的方法的隐式原型链都执行Function的原型。Object是一个方法。Object的原型是被解释器赋予了初始值的。(看不懂可以略过)
  4. 对象可以无条件的直接调用它隐式原型链上的方法,访问隐式原型链上的属性。
  5. let b = new Object() b的值为{}

我这里给Object的原型上添加了一个属性(方法),a就可以调用这个方法了

5. 为什么是this

显然,我们要使用this来构筑这个构造器。this到底是个什么。

this:一段作用域内的上下文。

改造一下e.g.2的代码,咱们来看看this是什么

/*e.g.3*/
function Person(name, age){
  console.log(this)
  this.name = name;
  this.age = age;
  this.run = function(){
    this.isRun = true; // 这里不是很标准,应该提前声明一下,但是js也是一门动态语言,稍稍偷懒一下
    return this.name + ' is run now!'
  }
  this.stop = function() {
    this.isRun = false;
    return this.name + ' is stop now!'
  }
}

let Jack = new Person('Jack', 18) //Person {}
Person('Tom', 5) //Window {window: Window, …}

可以看到,new关键字这里this指向了一个空对象,且指向了构造器。而不使用关键字new则指向了全局window

所以new 构造器这里返回的是构造器中的this

结语

这里简单的介绍了new关键字的作用,整篇文章是基于ES5的写法,并未使用ES6中提供的class关键字。另一方面,对于把方法封装的部分,大家发现了,该行为其实和构造的类的关系更为紧密,和实现的关系并不大,所以针对方法的封装会在继承章节中继续深入探讨。 同时,还有一个问题没有解决。

封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别

这里依照现有的体系是无法完成的 另一方面,基于this的构造方法体系也会让封装这个小事看起来有那么一点点的奇怪,这些问题,会在封装(下)中体现,欢迎喜欢的小伙伴多多关注点赞。

那么,下次再见~