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
证毕!完结,撒花!
别动手!这里确实丢失了一点中间的推理。丢失的部分是连个结论性的内容
- 在不修改原型指向的情况下,对象的double underscore proto指向它的构造器的Prototype。
- double underscore proto 指 proto__翻译做隐式原型链,为啥叫隐式呢?一般(双下划线)开头并结尾的属性和方法是不希望你直接使用和操作的,这种属性我们一般称它为隐式
- Prototype 原型,是所有方法上都具备的一个属性,所有原型的顶层在哪呢?在Function上,Function的隐式原型链指向 Function的原型,所有的方法的隐式原型链都执行Function的原型。Object是一个方法。Object的原型是被解释器赋予了初始值的。(看不懂可以略过)
- 对象可以无条件的直接调用它隐式原型链上的方法,访问隐式原型链上的属性。
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的构造方法体系也会让封装这个小事看起来有那么一点点的奇怪,这些问题,会在封装(下)中体现,欢迎喜欢的小伙伴多多关注点赞。
那么,下次再见~