类 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(); //

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

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

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

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

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

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

  3. JavaScript中的原型prototype和__proto__的区别及原型链概念

    问题 初学js的同学,总是搞不清楚js中的原型是什么东西,看着控制台打印出来的一串串__proto__,迷惑不已. 例如我定义一个Person,创建一个实例p,并打印实例. function Pers ...

  4. Javascript面向对象——创建对象、构造函数的原型

    Javascript面向对象--创建对象.构造函数的原型 其实:JavaScript中的创建类,就是创建一个构造函数,在创建对象时用到new这个关键字, 一.创建对象 1.函数创建对象 functio ...

  5. JavaScript中的类(class)、构造函数(constructor)、原型(prototype)

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

  6. JS中关于构造函数、原型链、prototype、constructor、instanceof、__proto__属性

    在Javascript不存在类(Class)的概念,javascript中不是基于类的,而是通过构造函数(constructor)和原型链(prototype chains)实现的.但是在ES6中引入 ...

  7. Javascript函数、构造函数、原型、类和对象

    函数 函数是JavaScript中特殊的对象,对函数执行typeof运算会返回字符串"function",因为函数也是对象,他们可以拥有属性和方法. 静态方法 函数在JS中定义了类 ...

  8. <JavaScript>constructor、prototype、__proto__和原型链

    在看了网上很多相关的文章,很多都是懵逼看完,并不是说各位前辈们写得不好,而是说实在不容易在一两次阅读中理解透.我在阅读了一些文章后,自己整理总结和绘制了一些相关的图,个人认为会更容易接受和理解,所以分 ...

  9. js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法。

    js最好的继承机制:用对象冒充继承构造函数的属性,用原型prototype继承对象的方法. function ClassA(sColor) { this.color = sColor; } Class ...

随机推荐

  1. hdu1285 拓扑序

    题意:有N个比赛队(1<=N<=500),编号依次为1,2,3,....,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩, ...

  2. network Driver , TDI(Transport Driver Interface) Drivers

    https://msdn.microsoft.com/en-us/library/windows/hardware/ff565094(v=vs.85).aspx https://msdn.micros ...

  3. Linux-Nginx之sendfile与上下文切换

    今天在看nginx thread pool的时候,频繁的看到sendfile,其实以前也经常看到sendfile,只是我平时选择性的忽视而已... 先说下sendfile,明天在好好聊下nginx 线 ...

  4. H2 database 行相加-行列转换

    create or replace view view_acceptCompanyasselect *  from  (select WARNIGID,max(CASEWHEN(zhtablename ...

  5. A better SHOW TABLE STATUS

    From command line we have the entire MySQL server on hands (if we have privileges too of course) but ...

  6. 当用GridView导出Execl的时候,会发生只能在执行 Render() 的过程中调用 RegisterForEventValidation的错误

    当用GridView导出Execl的时候,会发生只能在执行 Render() 的过程中调用 RegisterForEventValidation的错误提示. 有两种方法可以解决以上问题: 1.修改we ...

  7. 【转】 Ucenter同步登录原理解析

    应用中调用函数us_user_synlogin并输出 echo uc_user_synlogin($uid); 解析: 1. 该函数位于client.PHP中 2. 作用实质上是调用ucenter u ...

  8. android虚拟机(ROOT)权限

    自己找的一个比较好用的pc端安卓模拟器,蓝手指总所周知吧,这个是较高版本但不是最新的一个版本,关键是自带root功能,对于破解安卓存档类游戏还是有用的.安卓版本4.4.2 BlueStacks 0.9 ...

  9. centos6.5 安装iptables

    阿里云默认是没有安装iptables 安装 yum install -t iptables yum install iptables-services 检查iptables服务的状态 service ...

  10. 查看SqlServer的内存使用情况

    上一篇提到动态T-SQL会产生较多的执行计划,这些执行计划会占用多少内存呢?今天从徐海蔚的书中找到了答案.动态视图不仅可以查到执行计划的缓存,数据表的页面缓存也可以查到,将SQL整理一下,做个标记. ...