资讯专栏INFORMATION COLUMN

JavaScript构造器理解

PiscesYE / 1204人阅读

摘要:类类的概念应该是面向对象语言的一个特色,但是并不像,等高级语言那样拥有正式的类,而是多数通过构造器以及原型方式来仿造实现。因此,出现了构造函数方式,它的关键在于构造器概念的引入。于是,这就产生了构造函数原型法的类构造方法。

  

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

工厂模式



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

构造函数方式

构造器(Constructor)



这个看起来有点类的样子了吧(先不提那个难看的外置function)?我们发现,那个constructor其实就是一个简单的function,它与“工厂方式”中的createCar()区别就在于:
1、方法名大写
2、没有了空对象的创建和返回
3、使用this做引用。
那原来的那个空对象的创建以及返回的步骤去哪了呢?这两个步骤,现在都由创建实例时的“new”实现了。“new”这个操作符负责创建一个空对象,然后将那个叫做构造器的function添加到实例对象中并触发它,这样这个function实际上就是这个对象的一个method,function中的this指向的便是这个对象,最后将这个对象返回。根据如上分析,我们可以把这个过程简单分解为如下代码:


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

构造函数 + 原型

从上面的构造函数模式创建对象的例子上可以看到,每创建一个对象实例,每个对象中都有material()这个成员方法,这样看起来是不是会浪费内存空间,降低执行效率。所以JavaScript中提供了原型的方法可以解决这个问题。

  

原型:proto
在JavaScript中每个函数都有一个原型属性,即prototype,当调用构造函数进行创建对象的时候,所有该构造函数原型的属性在创建的对象上都可用。按照这样的想法多个CreateCar都可以共享一个原型material.

构造函数+原型法中,我们对于类的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(); > > document.write(car1.material); //prints "steel" > document.write(car2.material); //prints "steel" > > //car1.prototype.material = "iron" //compile error:car1.prototype is > undefined car1.material = "iron"; > > document.write(car1.material); //prints "iron" > document.write(car2.material); //prints "steel" > document.write(Car.prototype.material); //prints "steel" > > Car.prototype.material = "wood"; var car3 = new Car(); > document.write(car1.material); //prints "iron" > document.write(car2.material ); //prints "wood" > document.write(car3.material ); //prints "wood" > document.write(Car.prototype.material); //prints "wood"

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

prototype是function下的属性(其实任意object都拥有该属性,function是对象的一种)

prototype属性的值是一个对象,因此可任意添加子属性(line 4)

类的实例可以直接通过"."来直接获取prototype下的任意子属性(line 9)

所有以此function作为构造函数创建的类实例共用prototype中的属性及值(ling 9,10)

类的实例没有prototype属性(line 12)

可以直接通过 "实例.属性 = xxx" 的方式修改继承属性,修改后的值将覆盖继承自prototype的属性,但此修改不影响prototype本身,也不影响其它类实例(line 15,16,17)

继承属性修改后,该属性就成为类实例的直属属性

可以直接修改prototype的属性值,此改变将作用于此类下的所有实例,但无法改变直属属性值(极晚绑定line 21-24)

  

PS:对象实例在读取某属性时,如果在本身的直属属性中没有查找到该属性,那么就会去查找function下的prototype的属性。

Tip:我们可以通过hasOwnProperty方法来判断某属性是直属于对象还是继承自它的prototype属性
    car1.hasOwnProperty("material"); // true
    car2.hasOwnProperty("material"); // false
    "material" in car2;// true

构造函数+原型方式代码如下



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

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._initialized = true;
   }
}

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

  

对于所有用字面量创建的对象而言,其prototype对象均为Object.prototype(作为一个特殊对象,Object.prototype没有原型对象):

var x = {a:18, b:28};

console.log(x.__proto__);//Object {}

  

而对于所有用new操作符创建的对象而言,其prototype对象均为constructor函数的prototype属性:



var x = {a:18, b:28}; function Test(c){ this.c = c; } Test.prototype = x; var t = new Test(38); console.log(t);//Object {c=38, a=18, b=28} console.log(t.__proto__);//Object {a=18, b=28} console.log(t.__proto__.__proto__);//Object {}
  

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


整理于:
http://dbear.iteye.com/blog/613745
http://www.cnblogs.com/tomxu/archive/2012/02/21/2352994.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85657.html

相关文章

  • 面向对象的 JavaScript

    摘要:是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。而在基于原型的面向对象方式中,对象则是依靠构造器利用原型构造出来的。 JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来人们对这一门语言的误解,即认为 JavaScript 不是一门面向对象的语言,或者只是部分具备一些面向对象的特征。本文将回归面向对象本意,从对语言感悟的角度阐述为什...

    novo 评论0 收藏0
  • 理解javascript核心知识点

    摘要:作用域链的作用就是做标示符解析。事件循环还有个明显的特点单线程。早期都是用作开发,单线程可以比较好当规避同步问题,降低了开发门槛。单线程需要解决的是效率问题,里的解决思想是异步非阻塞。 0、前言 本人在大学时非常痴迷java,认为java就是世界上最好的语言,偶尔在项目中会用到一些javascript,但基本没放在眼里。较全面的接触javascript是在实习的时候,通过这次的了解发现...

    laznrbfe 评论0 收藏0
  • 讲清楚之 javascript原形

    摘要:构造函数和实例都通过属性指向了原形。代码示例是构造函数的实例的属性与的属性保存的值相等,即他们指向同一个对象原形。 讲清楚之javascript原型 标签: javascript javascript 中原形是一个比较难于理解的概念。javascript 权威指南在原形这一章也花了大量的篇幅进行介绍,也许你已经读过javascript 权威指南,或者已经是读第N篇了,然而这篇文章的目...

    高胜山 评论0 收藏0
  • 深入理解JavaScript原型与继承

    摘要:深入理解原型与继承看过不少书籍,不少文章,对于原型与继承的说明基本上让人不明觉厉,特别是对于习惯了面向对象编程的人来说更难理解,这里我就给大家说说我的理解。 深入理解:JavaScript原型与继承 看过不少书籍,不少文章,对于原型与继承的说明基本上让人不明觉厉,特别是对于习惯了面向对象编程的人来说更难理解,这里我就给大家说说我的理解。 首先JavaScript是一门基于原型编程的语言...

    mengbo 评论0 收藏0
  • JavaScript 工厂函数 vs 构造函数

    摘要:当谈到语言与其他编程语言相比时,你可能会听到一些令人困惑东西,其中之一是工厂函数和构造函数。好的,让我们用构造函数做同样的实验。当我们使用工厂函数创建对象时,它的指向,而当从构造函数创建对象时,它指向它的构造函数原型对象。 showImg(https://segmentfault.com/img/bVbr58T?w=1600&h=900); 当谈到JavaScript语言与其他编程语言...

    RayKr 评论0 收藏0

发表评论

0条评论

PiscesYE

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<