前言:
js是一门面向对象的语言,但是又没有类的概念,虽然后来加入了class,但也就是个语法糖而已。但并不妨碍它是一门面向对象的语言,在js里,一切皆对象。要真正的理解js中的对象,就像高中英语老师说的那样:have a long way to go。
最近用vue写代码时发现data、methods 的写法似曾相识,翻阅之前的笔记发现和对象的代码封装(命名空间极为相似)以及main.js里new Vue的写法让我想到了构造函数。所以本篇文章主要写一写对象命名空间和构造函数以及原型链的理解
面向对象的特点是:封装、继承、多态
一点疑问:
使用vue脚手架写代码时,可不可以不在data、methods里写代码?
先上代码:
<script> var a = 100 console.log(a, '我是a') function fn () { console.log('我就要用脚手架写js代码') } fn() export default { data() { return { key: value } } </script>
结论:
经过测试,发现真的可以,也就是说,只要我高兴我就用vue脚手架写js代码没有任何毛病。
接下来就是data了,看着就是个函数,怎么感觉似曾相识呢?
1.封装:
封装的本质是将具有关联的代码组合在一起,其优势是能够保证代码复用且易于维护,函数是最典型也是最基础的代码封装形式,面向对象思想中的封装仍以函数为基础,但提供了更高级的封装形式。
在日常开发中,封装几乎无处不在,如封装一个日期处理函数,在不同的地方都可以使用。
封装之:命名空间
以普通对象(命名空间)形式封装的代码只是单纯把一系列的变量或函数组合到一起,所有的数据变量都被用来共享(使用 this 访问)
这段代码和vue中的非常相似:
<script> // 普通对象(命名空间)形式的封装 let Obj = { name: 'A', setName: function (name) { this.name = this.name; }, getName() { console.log(this.name); } } </script>
小结:命名空间式的封装无法保证数据的独立性
封装之:构造函数
构造函数相当于一个"模子",能够像字面量那样创建出对象来,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
<script> function Person() { this.name = '佚名'; // 设置名字 this.setName = function (name) { this.name = name; } // 读取名字 this.getName = () => { console.log(this.name); } } // 实例对像,获得了构造函数中封装的所有逻辑 let p1 = new Person(); p1.setName('小明'); console.log(p1.--name);// 小明 // 实例对象 let p2 = new Person(); console.log(p2.name); // 佚名 </script>
封装之:原型对象
实际上每一个构造函数都有一个名为
prototype
的属性,译成中文是原型的意思,prototype
的是对象类据类型,称为构造函数的原型对象,每个原型对象都具有constructor
属性代表了该原型对象对应的构造函数。
<script> function Person() { // 此处未定义任何方法 } // 为构造函数的原型对象添加方法 Person.prototype.sayHi = function () { console.log('Hi~'); } // 实例化 let p1 = new Person(); p1.sayHi(); // 输出结果为 Hi~ </script>
2.继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。
举个例子:中国人有中国人的特征,美国人有美国人的特征,非洲人…但是大家都是人,并且两只眼睛一个鼻子两条腿。所以我们可以把眼睛鼻子腿抽象出来变成大家都有的,
继承-原型继承
简单理解就是,葫芦娃兄弟,都是一根藤上的,但是各不相同,也算变相的继承,如葫芦娃长的像个葫芦。
基于构造函数原型对象实现面向对象的继承特性。
重点关注最后两行代码:
逻辑是:中国人有独特的特点,但是把两只眼睛两条腿的特点放到原型上,那么就可以继承
把公共的特点people放到Chinese的原型上,一定要用constructor指回去
<script> // 中国人 function Chinese() { this.skin = 'yellow'; this.language = '中文'; } // 日本人 function Japanese() { this.skin = 'yellow'; this.language = '日文'; } // 人们【共有】的行为特征 let people = { // 人的特征 arms: 2, legs: 2, eyes:2, // 人的行为 walk: function () {}, sleep: function () {}, sing: function () {} } // 为 prototype 重新赋值 Chinese.prototype = people; Chinese.prototype.constructor = Chinese; </script>
上述代码中是以命名空间的形式实现的继承,事实上 JavaScript 中继承更常见的是借助构造函数来实现:
<script> // 所有人 function Person() { // 人的特征 this.arms = 2; this.legs = 2; this.eyes = 2; // 人的行为 this.walk = function () {} this.sing = function () {} this.sleep = function () {} } // 封装中国人的行为特征 function Chinese() { // 中国人的特征 this.skin = 'yellow'; this.language = '中文'; } // 封装日本人的行为特征 function Japanese() { // 日本人的特征 this.skin = 'yellow'; this.language = '日文'; } // human 是构造函数 Person 的实例 let human = new Person(); // 中国人 Chinese.prototype = human; Chinese.prototype.constructor = Chinese; // 日本人 Japanese.prototype = human; Japanese.prototype.constructor = Japanese; </script>
继承-原型链
简单理解就是,葫芦娃兄弟继承这根藤,而这根藤也有自己的爹,也继承自己爹,就像葫芦娃。葫芦娃当然也能继承藤的爹。就像孙子遗传爷爷的特点,或者别人问孙子姓什么,你说我爷爷姓王,也算继承了。
原型链的定义(非标准):
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
作用:用于查找成员提供机制 <script> // Person 构造函数 function Person() { this.arms = 2; this.walk = function () {} } // Person 原型对象 Person.prototype.legs = 2; Person.prototype.eyes = 2; Person.prototype.sing = function () {} Person.prototype.sleep = function () {} // Chinese 构造函数 function Chinese() { this.skin = 'yellow'; this.language = '中文'; } // Chinese 原型对象 Chinese.prototype = new Person(); Chinese.prototype.constructor = Chinese; // 实例化 let c1 = new Chinese(); console.log(c1); </script>
在 JavaScript 对象中包括了一个非标准备的属性 __proto__ 它指向了构造函数的原型对象,通过它可以清楚的查看原型对象的链状结构。
由此可得出JavaScript 中对象的工作机制:
当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享
小结:面向对象(OOP)是编程时的一种指导思想,需要通过不断的实践才能体会面向对象编程的优势,在 JavaScript 中面向对象编程的实现是以构造函数和原型对象为核心的,因此掌握构造函数和原型对象的语法是灵活运用面向对象的基础。
原型链的查找机制
第一,每一个构造函数都有一个属性,叫做prototype,它指向原型对象
第二,每一个原型对象都有一个constructor,指回构造函数
第三,每一个实例对象都有一个--proto--,指向原型对象
故,构造函数上设置很多的方法和属性,实例对象都可以使用,典型代表就是Vue,原型上太多方法和属性了。
先去原型对象上找,找不着就去原型对象的上一层原型对象上找…直到找到null,再找就报错了。
不容易理解的点:
1.是在构造函数上的prototype上设置属性和方法,实例对象没有,实例对象定义的方法或者属性叫实例成员。简单理解为,你爹省了2个娃,但是你找了个老婆,但是那是你老婆,和他没啥关系。
2. 设置原型时,一定要指回去。不然就找不到构造函数了。
// 用于设置原型 A.prototype = new Person() // 设置原型的构造器 A.prototype.constructor=A
prototype.constructor仅仅可以用于识别对象是由哪个构造函数初始化的
3.最后的总结:
__proto__和constructor属性是对象所独有的
prototype属性是函数所独有的,但是函数也是一种对象,所以函数也拥有__proto__和constructor属性。
prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法
constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。