详解JS类概念的实现
2016-07-18 11:27
507 查看
众所周知,JS并没有类(class)的概念,虽然说ES6开始有了类的概念,但是,这并不是说JS有了像Ruby、Java这些基于类的面向对象语言一样,有了全新的继承模型。ES6中的类,仅仅只是基于现有的原型继承的一种语法糖,下面我们好好分析一下,具体是如何实现的
在讲正题之前,我们先来讨论一下各种面试题都可能出现的一个问题,什么是
类:定义某一事物的抽象特点,包含属性和方法,举个栗子,
对象:类的一个实例,还是举个栗子,小明家的白色的狗和小红家红色的狗。
属性:对象的特征,比如刚提到的狗皮毛的颜色。
方法:对象的行为,比如刚才提到的狗的吠叫能力。
封装性:通过限制只有特定类的对象可以访问特定类的成员,一般包含
继承性:一个类会有
多态性:多意为‘许多’,态意为‘形态’。不同类可以定义相同的方法或属性。
抽象性:复杂现实问题转化为类定义的途径,包括以上所有内容。
由于JS并没有
在js里面,我们通常都是通过构造函数来创建
为什么通过
我们来具体看看当
一个新实例被创建。它继承自
构造函数被执行,相应的参数会被传入,同时上下文(
除非明确返回值,否则返回新的实例
至此,我们实现了OOP里面的类(Dog)、对象(d1,d2)、和属性(name)的概念,
注: 新创建的实例,都包含一个
接下来,我们即将讨论如何定义方法,其实,我们完全可以这样定义我们的方法,如:
但是,一般我们不推荐这么做,正如我们所知
比较浪费内存。但是我们通常可以用
好像扯得有点远,我们回归我们的主角
我们可以通过
注:所有对象的
上面已经描述如何定义一个
null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链
mozilla给出一个挺好的例子:
现在我们可以通过我们理解的构造函数和原型对象来实现继承的概念了,代码如下:
注: 子类在定义prototype时,不可直接使用
但是,当我想找一下
如下图:
所以,我们需要修改一下我们的实现,代码如下:
注:
6 开始, [[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问
自此,我们已经实现继承的概念,父类有自己的方法,子类继承了父类的属性和方法,而且还可以定义自己的属性和方法。
在ES6中,我们只需通过
全选<button href="javascript:void(0);" _xhe_href="javascript:void(0);" class="copyCode btn btn-xs" data-clipboard-text="" "use="" strict";"="" data-toggle="tooltip" data-placement="bottom" title="" style="color: rgb(255, 255, 255); font-style: inherit; font-variant: inherit; font-stretch: inherit; font-size: 12px; line-height: 1.5; font-family: inherit; margin: 0px 0px 0px 5px; overflow: visible; cursor: pointer; vertical-align: middle; border: 1px solid transparent; white-space: nowrap; padding: 1px 5px; border-radius: 3px; -webkit-user-select: none; box-shadow: rgba(0, 0, 0, 0.0980392) 0px 1px 2px; background-image: none; background-color: rgba(0, 0, 0, 0.74902);">复制放进笔记
源引:https://segmentfault.com/a/1190000004700001
面向对象思想
在讲正题之前,我们先来讨论一下各种面试题都可能出现的一个问题,什么是面向对象编程(OOP)?
类:定义某一事物的抽象特点,包含属性和方法,举个栗子,
狗这个类包含狗的一些基础特征,如毛皮颜色,吠叫等能力。
对象:类的一个实例,还是举个栗子,小明家的白色的狗和小红家红色的狗。
属性:对象的特征,比如刚提到的狗皮毛的颜色。
方法:对象的行为,比如刚才提到的狗的吠叫能力。
封装性:通过限制只有特定类的对象可以访问特定类的成员,一般包含
public
protected
private三种,不同语言的实现不同。
继承性:一个类会有
子类,这个
子类是更具体化的一个抽象,它包含
父类的一些属性和方法,并且有可能有不同于
父类的属性和方法。
多态性:多意为‘许多’,态意为‘形态’。不同类可以定义相同的方法或属性。
抽象性:复杂现实问题转化为类定义的途径,包括以上所有内容。
如何实现对象(类)的定义
由于JS并没有类(class)的概念,更多的时候我们把它叫做
对象(function),然后把
对象叫做
实例(instance),跟团队里面的人讨论OOP的时候,经常会有概念上的一些误解,特此说明一下。
构造函数:一个指明了对象类型的函数,通常我们可以通过构造函数类创建
在js里面,我们通常都是通过构造函数来创建对象(class),然后通过
new这个关键字来实例化一个对象,如:
function Dog(name){ this.name = name; } var d1 = new Dog("dodo"); d1.constructor // Dog(name){ // this.name = name; // } var d2 = new Dog('do2do');
为什么通过
构造函数可以实现
对象(class)属性的定义呢?首先,我们必须理解这个语法
new constructor[([arguments])]
我们来具体看看当
new Dog('name')时,具体做了哪些事情
一个新实例被创建。它继承自
Dog.prototype
构造函数被执行,相应的参数会被传入,同时上下文(
this)会指向这个新的实例
除非明确返回值,否则返回新的实例
至此,我们实现了OOP里面的类(Dog)、对象(d1,d2)、和属性(name)的概念,
d1和
d2有相同的
name属性,但是值并不相同,即属性是私有的。
注: 新创建的实例,都包含一个
constructor属性,该属性指向他们的构造函数
Dog
原型对象(prototype)
接下来,我们即将讨论如何定义方法,其实,我们完全可以这样定义我们的方法,如:function Dog(name){ this.name = name; this.bark = function(){ console.log(this.name + " bark"); }; } var d1 = new Dog("dodo"); d1.bark(); // dodo bark
但是,一般我们不推荐这么做,正如我们所知
Dog是一个构造函数,每次实例化时,都会执行这个函数,也就是说,
bark这个方法每次都会被定义,
比较浪费内存。但是我们通常可以用
constructor和闭包的方式来实现私有属性,如:
function Dog(name){ this.name = name; // barkCount 是私有属性,因为实例并不知道这个属性 var barkCount = 0; this.bark = function(){ barkCount ++; console.log(this.name + " bark"); }; this.getBarkCount = function(){ console.log(this.name + " has barked " + barkCount + " times"); }; } var d1 = new Dog("dodo"); d1.bark(); d1.bark(); d1.getBarkCount(); // dodo has barked 2 times
好像扯得有点远,我们回归我们的主角
prototype,函数
Dog有一个特殊的属性,这个属性就叫原型,如上所述,当用
new运算符创建实例时,会把
Dog的原型对象的引用复制到新的实例内部的[[Prototype]]属性,即
d1.[[Prototype]] = Dog.prototype,因为所有的实例的[[Prototype]]都指向
Dog的原型对象,那么,我们就可以很方便的定义我们的方法了,如:
function Dog(name){ this.name = name; } Dog.prototype = { bark: function(){ console.log(this.name + " bark"); } }; var d1 = new Dog("dodo"); d1.bark(); // dodo bark
我们可以通过
d1.__proto__ == Dog.prototype,来验证我们的想法。用原型对象还有一个好处,由于实例化的对象的[[Prototype]]指向
Dog的原型对象,那么我们可以通过添加
Dog的原型对象的方法,来添加已经实例化后的实例
d1的方法。如:
Dog.prototype.run = function(){ console.log(this.name + " is running!"); } d1.run(); // dodo is running!
注:所有对象的
__proto__都指向其构造器的
prototype
原型链
上面已经描述如何定义一个类,接下来我们将要了解,如何实现
类的继承。在此之前,我们先了解js里一个老生常谈的概念:原型链:每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为
null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链
mozilla给出一个挺好的例子:
// 假定有一个对象 o, 其自身的属性(own properties)有 a 和 b: // {a: 1, b: 2} // o 的原型 o.[[Prototype]]有属性 b 和 c: // {b: 3, c: 4} // 最后, o.[[Prototype]].[[Prototype]] 是 null. // 这就是原型链的末尾,即 null, // 根据定义,null 没有[[Prototype]]. // 综上,整个原型链如下: // {a:1, b:2} ---> {b:3, c:4} ---> null console.log(o.a); // 1 // a是o的自身属性吗?是的,该属性的值为1 console.log(o.b); // 2 // b是o的自身属性吗?是的,该属性的值为2 // o.[[Prototype]]上还有一个'b'属性,但是它不会被访问到.这种情况称为"属性遮蔽 (property shadowing)". console.log(o.c); // 4 // c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有. // c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4 console.log(o.d); // undefined // d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有. // d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有. // o.[[Prototype]].[[Prototype]]为null,停止搜索, // 没有d属性,返回undefined
现在我们可以通过我们理解的构造函数和原型对象来实现继承的概念了,代码如下:
function Dog(name){ this.name = name; } // 这种写法会修改dog实例的constructor,可以通过Dog.prototype.constructor = Dog来重置 Dog.prototype = { bark: function(){ console.log(this.name + " bark"); } }; // 重置Dog实例的构造函数为本身 Dog.prototype.constructor = Dog; // Haski 的构造函数 function Haski(name){ // 继承Dog的构造函数 Dog.call(this, name); // 可以补充更多Haski的属性 this.type = "Haski"; }; // 1. 设置Haski的prototype为Dog的实例对象 // 2. 此时Haski的原型链是 Haski -> Dog的实例 -> Dog -> Object // 3. 此时,Haski包含了Dog的所有属性和方法,而且还有一个指针,指向Dog的原型对象 // 4. 这种做法是不推荐的,下面会改进 Haski.prototype = new Dog(); // 重置Haski实例的构造函数为本身 Haski.prototype.constructor = Haski; // 可以为子类添加更多的方法 Haski.prototype.say = function(){ console.log("I'm " + this.name); } var ha = new Haski("Ha"); // Ha bark ha.bark(); // Ha bark ha.say(); // I'm Ha
注: 子类在定义prototype时,不可直接使用
Haski.prototype = {}定义,这样会重写Haski的原型链,把Haski的原型当做
Object的实例,而非
Dog的实例
但是,当我想找一下
ha的原型链时,会发现
ha的原型对象指向的是
Dog的实例,而且还有一个值为
undefined的
name属性,在实例化时,name是没必要的,
如下图:
所以,我们需要修改一下我们的实现,代码如下:
// 修改前 Haski.prototype = new Dog(); // 修改后 Haski.prototype = Object.create(Dog.prototype);
注:
__proto__方法已弃用,从 ECMAScript
6 开始, [[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问
自此,我们已经实现继承的概念,父类有自己的方法,子类继承了父类的属性和方法,而且还可以定义自己的属性和方法。
ES6 如何实现
'use strict'; // 声明 Dog 类 class Dog { // 构造函数 constructor(name){ this.name = name; } // 普通方法 dark(){ console.log(this.name + "bark"); } // 静态方法,也叫类方法 static staticMethod(){ console.log("I'm static method!"); } } // 通过`extends`关键字来实现继承 class Haski extends Dog { constructor(name){ // 调用父类的构造函数 super(name); this.type = "Haski"; } // 定义子类方法 say(){ console.log("I'm" + this.name); } }
在ES6中,我们只需通过
class
extends
super
constructor即可比较方便的完成原来使用JS比较难理解的实现,我们可以通过babel的解析器,来看看babel是怎么把这些语法糖转成JS的实现的。具体代码可以参考
全选<button href="javascript:void(0);" _xhe_href="javascript:void(0);" class="copyCode btn btn-xs" data-clipboard-text="" "use="" strict";"="" data-toggle="tooltip" data-placement="bottom" title="" style="color: rgb(255, 255, 255); font-style: inherit; font-variant: inherit; font-stretch: inherit; font-size: 12px; line-height: 1.5; font-family: inherit; margin: 0px 0px 0px 5px; overflow: visible; cursor: pointer; vertical-align: middle; border: 1px solid transparent; white-space: nowrap; padding: 1px 5px; border-radius: 3px; -webkit-user-select: none; box-shadow: rgba(0, 0, 0, 0.0980392) 0px 1px 2px; background-image: none; background-color: rgba(0, 0, 0, 0.74902);">复制放进笔记
"use strict"; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // 声明 Dog 类 var Dog = function () { // 构造函数 function Dog(name) { _classCallCheck(this, Dog); this.name = name; } // 普通方法 _createClass(Dog, [{ key: "dark", value: function dark() { console.log(this.name + "bark"); } // 静态方法,也叫类方法 }], [{ key: "staticMethod", value: function staticMethod() { console.log("I'm static method!"); } }]); return Dog; }(); // 通过`extends`关键字来实现继承 var Haski = function (_Dog) { _inherits(Haski, _Dog); function Haski(name) { _classCallCheck(this, Haski); var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Haski).call(this, name)); // 调用父类的构造函数 _this.type = "Haski"; return _this; } _createClass(Haski, [{ key: "say", value: function say() { console.log("I'm" + this.name); } }]); return Haski; }(Dog);
源引:https://segmentfault.com/a/1190000004700001
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 最后一次说说闭包
- Ajax
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- Redux系列02:一个炒鸡简单的react+redux例子
- JavaScript 各种遍历方式详解