类 Class

类的概念应该是面向对象语言的一个特色,但是JavaScript并不像Java,C++等高级语言那样拥有正式的类,而是多数通过构造器以及原型方式来仿造实现。在讨论构造器和原型方法前,我可以看看一种叫做“工厂模式”的仿造方法。

function start() {
alert("Bang!!");
} function createCar(color, title) {
var car = {};
car.color = color;
car.title = title;
car.start = start;
return car;
} var car1 = createCar("red", "BMW");
var car2 = createCar("yellow", "BYD");

这种方式显然可以实现class的功能,但是外形上怎么也无法说它是个class以及class实例的创建过程。因此,出现了“构造函数模式”,它的关键在于构造器(Constructor)概念的引入。

构造器 Constructor

我们先来看“构造函数模式”的具体做法:

function start() {
alert("Bang!!");
} function Car(color, title) {
this.color = color;
this.title = title;
this.start = start;
} var car1 = new Car("red", "BMW");
var car2 = new Car("yellow", "BYD");

这个看起来有点类的样子了吧(先不提那个难看的外置function)?我们发现,那个constructor(Car)其实就是一个简单的function,它与“工厂方式”中的createCar()区别就在于:

  1、方法名开头字母大写(注:构造函数方法名开头字母大写是国际惯例);

  2、没有了空对象的创建和返回;

    3、使用this做引用。

那原来的那个空对象的创建以及返回的步骤去哪了呢?这两个步骤,现在都由创建实例时的“new”实现了。“new”这个操作符负责创建一个空对象,然后将那个叫做构造器的function添加到实例对象中并触发它,这样这个function实际上就是这个对象的一个method,function中的this指向的便是这个对象,最后将这个对象返回。根据如上分析,我们可以把这个过程简单分解为如下代码:

var obj = {};
obj.constructor = Car;
obj.constructor("red", "BMW");
return obj;

“构造函数模式”方式虽然与高级面向对象语言中的类创建方式已经很接近(使用”new“创建),但是貌似那个游离在类之外的function start()其实却是个相当有碍观瞻的瑕疵。我们应该想一种办法让这个方法与类挂钩,让它成为类的一个属性,不是全局的。于是,这就产生了“构造函数+原型法”的类构造方法。

原型 prototype

在”构造函数+原型法“中,我们对于类的method期待得到的效果是:

  1、仅是类的method而不是全局的

  2、只在类被定义时创建一个method实例,然后被所有类的实例共用。

由这两个目标,我们很容易想到高级面向对象语言Java的private static变量的特点。JavaScript没有为我们提供这么简单的符号来实现这个复杂功能,但是却有一个属性可以帮我们仿造出这种效果:prototype。我们先来看几段prototype的使用代码。

function Car() {
} Car.prototype.material = "steel";
var car1 = new Car();
var car2 = new Car();
console.log(car1.material);//steel
console.log(car2.material);//steel car1.material = "iron";
console.log(car1.material);//iron
console.log(car2.material);//steel
console.log(Car.prototype.material); //steel Car.prototype.material = "wood";
var car2 = new Car();
console.log(car1.material); //iron
console.log(car2.material); //wood
console.log(Car.prototype.material); //wood

分析该段代码前,需要明确两个概念:对象的直属属性和继承属性。直接在构造函数中通过this.someproperty = xxx这种形式定义的someproperty属性叫做对象的直属属性,而通过如行代码那样Car.prototype.material = "steel";这种形式定义的material属性叫做继承属性。由上面这段代码,我们可以总结出prototype属性的如下特点:

prototype属性的特点
    1、prototype是function下的属性(其实任意object都拥有该属性,function是对象的一种);
    2、prototype属性的值是一个对象,因此可任意添加子属性;
    3、类的实例可以直接通过”.”来直接获取prototype下的任意子属性;
    4、所有以此function作为构造函数创建的类实例共用prototype中的属性及值;
    5、类的实例没有prototype属性;
    6、可以直接通过 “实例.属性 = xxx” 的方式修改继承属性,修改后的值将覆盖继承自prototype的属性,但此修改不影响prototype本身,也不影响其它类实例;
    7、继承属性修改后,该属性就成为类实例的直属属性;
    8、可以直接修改prototype的属性值,此改变将作用于此类下的所有实例,但无法改变直属属性值。

【注:对象实例在读取某属性时,如果在本身的直属属性中没有查找到该属性,那么就会去查找function下的prototype的属性。所以类的实例可以直接通过”.”来直接获取prototype下的任意子属性】

好好理解下prototype的这些特点,我们不难看出,在prototype中定义的属性与Java类中的static属性特点极为相近,适合定义那些所有类实例都可共用的一些属性的值,比如汽车的构造材料这一类。

了解了prototype的这些特点,我们可以言归正传,把它应用到类方法的仿造和实现上了。其实做法很简单,就是把value换成function,先看下面的代码:

function Car() {
} Car.prototype.start = function() {
alert("Bang!!");
}; var car1 = new Car();
var car2 = new Car();
car1.start();
car2.start();

得益于prototype的强大特性,我们知道这个method确实是可以这么被绑定到类上的。但是,最有用的是,那个method(start)只会在执行Car.prototype.start =这个定义的时候创建一个实例,之后通过new创建的对象实例里面使用的这个method都是它的引用,因此不会有内存浪费。

好了,method的问题终于解决了,那么整理一下,下面写出使用“构造函数模式+原型模式”构造一个类的完整代码:

function Car(color, title) {
this.color = color;
this.title = title;
} Car.prototype.material = "steel";
Car.prototype.start = function () {
alert("Bang!!!");
};
Car.prototype.repaint = function (color) {
this.color = color;
}; var car1 = new Car("red", "BMW");
var car2 = new Car("yellow", "VW"); car1.repaint("pink");
console.log(car1.color); //pink
console.log(car2.color); //yellow

怎么样,这个跟高级面向对象语言中的class的样子更~加类似了吧?Perfect ?NO!!!上述写法只是在“语义”上达到了对类属性和方法的封装,很多面向对象思想的完美主义者希望在“视觉”上也达到封装,因此就产生了“动态原型法”,请看下面的代码:

function Car(color, title) {
this.color = color;
this.title = title; if (typeof Car._initialized == "undefined") {
Car.prototype.start = function() {
alert("Bang!!!");
};
Car.prototype.material = "steel";
Car._initializde = true;
}
}

我们看,其实Car.prototype的属性定义是可以被放进Car function的定义之中的,这样就达到了“视觉”封装。但是我们没有单纯的move,我们需要加一个条件,让这些赋值操作只执行一次,而不是每次创建对象实例的时候都执行,造成内存空间的浪费。添加_initialized 属性的目的就在于此。

注意:

对于类的属性值,我们上面只考虑了primitive type(原始类型)和function的类型情况,如果属性值是对象,比如Array,那情况就又复杂了,思考下面的一段代码。

function Car() {
} Car.prorotype.drivers = ["Kitten", "Bear"]; var car1 = new Car();
var car2 = new Car(); car1.drivers.push("Point"); console.log(car1.drivers); //Kitten,Bear,Point
console.log(car2.drivers); //Kitten,Bear,Point

可以看到,我们只是想对car1drivers做改变,但是car2的居然也改了,这显然是我们不希望出现的结果。为什么会这样呢?因为Car.prototype.drivers只是引用而已。在使用new关键字创建类实例时,附加给类实例的属性的属性值也是跟它指向相同值的引用。如果它所指向的数据是primitive type(原始类型)或function自然没有问题,因为没有什么操作可以直接修改这些值,我们只能修改引用本身,让它们指向新的值,但这种改变根本不会影响其它指向这个值的引用。但如果指向的是数组就不一样了,在任何一个指向它的引用上实施.push操作,都会影响这个Array的值,并且这种改变会对所有引用都生效。因此,像Array这种复杂数据类型是不应该放在prototype里面做属性值的,这样,上述代码就应该更改为:

function Car() {
this.drivers = ["Kitten", "Bear"];
} var car1 = new Car();
var car2 = new Car(); car1.drivers.push("Point"); console.log(car1.drivers); //Kitten,Bear,Point
console.log(car2.drivers); //Kitten,Bear

下面我们来创建一个完成的类:

function Car() {
this.drivers = ["Kitten", "Bear"];
if (typeof Car._initialized == "undefined") {
Car.prototype.getDiversNumber = function () {
alert(this.drivers.length);
};
Car._initialized = true;
}
} var car1 = new Car();
var car2 = new Car(); car1.drivers.push("Point"); console.log(car1.drivers); //Kitten,Bear,Point
console.log(car2.drivers); //Kitten,Bear
car1.getDiversNumber(); //
car2.getDiversNumber(); //

总结出来一句话就是:用构造函数模式定义对象的所有非函数属性,用原型模式定义对象的函数属性。

JavaScript中的类(class)、构造函数(constructor)、原型(prototype)的更多相关文章

  1. javascript 中isPrototypeOf 、hasOwnProperty、constructor、prototype等用法

    hasOwnProperty:是用来判断一个对象是否有你给出名称的属性或对象,此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员. isPrototypeOf是用来判断要检查 ...

  2. JavaScript 类、构造函数、原型

    类.构造函数.原型  :本质均为函数 利用的原理是:词法作用域,调用对象及作用域链  闭包  属性查找方式    设计和new运算符一起使用的函数叫做构造函数. 构造函数的工作:初始化一个新创建的对象 ...

  3. JavaScript中的类

          JavaScript类的相关知识 1.例子 /* 例1 */// 定义一个构造函数function Range(from, to){ this.from = from; this.to = ...

  4. 深入理解JavaScript中创建对象模式的演变(原型)

    深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...

  5. JavaScript中定义类的方式详解

    本文实例讲述了JavaScript中定义类的方式.分享给大家供大家参考,具体如下: Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的exte ...

  6. Javascript中的类实现

    Javascript本身并不支持面向对象,它没有访问控制符,它没有定义类的关键字class,它没有支持继承的extend或冒号,它也没有用来支持虚函数的virtual,不过,Javascript是一门 ...

  7. JavaScript中创建类,赋值给ajax中的data参数

    缘由:因为要给根据是否选中checkbox来动态增加ajax中data的属性(ajax的data属性格式的几种方法,参考http://www.jb51.net/article/46676.htm) d ...

  8. 关于构造函数和原型prototype对象的理解

    构造函数     1.什么是构造函数 构造函数,主要用于对象创建的初始化,和new运算符一起用于创建对象,一个类可以有多个构造函数,因为函数名相同,所以只能通过参数的个数和类型不同进行区分,即构造函数 ...

  9. 一、javascript中的类

    1.找出对象的构造器----constructor/instanceof constructor是用模版实例化对象的时候附带的一个额外属性,这个属性指向创建该对象时所使用的javascript构造函数 ...

随机推荐

  1. nginx配置基于域名的虚拟主机

    其实基于域名和基于ip的虚拟主机配置是差不多的,在配置基于ip的虚拟主机上我们只需要修改几个地方就能变成基于域名的虚拟主机,一个是要修改域名,一个是host文件直接看代码 [root@localhos ...

  2. Singer 学习十二 指南

    版本0.3.0 tap是一个应用程序,需要一个配置文件和可选的状态文件作为输入,并产生有序的流记录, 状态和模式信息作为输出. 一个记录是任何类型的JSON编码的数据.tap 状态消息用于保留一个调用 ...

  3. 使用 --image-repository 解决kubeadm 安装k8s 集群 谷歌镜像墙的问题

    从网上我们看到的好多kubeadm 安装k8s 的时候都说需要下拉取镜像,然后修改,实际上 我们可以使用配置参数,快速的跳过墙的问题 说明: 基础镜像,我们仍然存在,拉取的问题,但是dockerhub ...

  4. Dev TextEdit 输入提示

    TextEdit.Properties.NullValuePromptShowForEmptyValue = true; TextEdit.Properties.NullValuePrompt = “ ...

  5. ML(附录1)——梯度下降

    梯度下降是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以).在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的 ...

  6. mongodb之 oplog 日志详解

    1:oplog简介 oplog是local库下的一个固定集合,Secondary就是通过查看Primary 的oplog这个集合来进行复制的.每个节点都有oplog,记录这从主节点复制过来的信息,这样 ...

  7. python 术语

    python 术语 术语英文 术语中文 说明 PyPI(Python Package Index) 搜索python包的网站:https://pypi.org/ pip.easy_install 包管 ...

  8. SPA项目中,404页面 和 登陆页面 对应的路由,应该怎样控制?

    SPA项目中,404页面 和 登陆页面 对应的路由,应该怎样控制? 可以这样做: 登陆之前,所有页面跳到 登陆页面:包括随便输入的路由地址. 登陆后,跳到相应页面:随便输入的.不存在的路由地址,才跳到 ...

  9. python一些东西

    assert repr() >>> a ['d', 'c', 'c', 'f', 'a', 'a', 'b', 'c', 'd'] >>> sorted(a) [' ...

  10. Jmeter JDBC Request--测试数据库连接 拒绝解决方案

    有时会遇到回应信息如下: 1 Cannot create PoolableConnectionFactory (Access denied for user 'root'@'10.0.1.23' (u ...