JavaScript构造函数的prototype属性

JavaScript中没有类的概念,所以其在对象创建方面与面向对象语言有所不同。

JS中对象可以定义为”无序属性的集合”。其属性可以包含基本值,对象以及函数。对象实质上就是一组没有特定顺序的值,对象中每个属性、方法都有一个名字,每个名字都映射到了一个值,因此我们可以将对象想象称为一个散列表。

JS是一种基于对象的语言,对象的概念在JS体系中十分的重要,因此有必要清楚地了解一下JS中对象创建的常用方法及各自的局限性。

  • 使用Object或对象字面量创建对象
  • 工厂模式创建对象
  • 构造函数模式创建对象
  • 原型模式创建对象
  • 构造与原型混合模式创建对象

使用Object或对象字面量创建对象

在说工厂模式创建对象之前,我们不妨回顾一下JS中最基本的创建对象的方法,比如说我想创建一个student对象怎么办?最简单地,new一个Object:

var student = new Object();

student.name = "easy";

student.age = "20";

这样,一个student对象就创建完毕,拥有2个属性name以及age,分别赋值为"easy"和20。

如果你嫌这种方法有一种封装性不良的感觉,我们也可以使用对象字面量的方式来创建student对象:

var sutdent = {

name : "easy",

age : 20

};

这样看起来似乎就完美了。但是马上我们就会发现一个十分尖锐的问题:当我们要创建同类的student1,student2,…,studentn时,我们不得不将以上的代码重复n次。

var sutdent1 = {

name : "easy1",

age : 20

};

var sutdent2 = {

name : "easy2",

age : 20

};

...

var sutdentn = {

name : "easyn",

age : 20

};

能不能像工厂车间那样,有一个车床就不断生产出对象呢?我们看”工厂模式”。

工厂模式创建对象

JS中没有类的概念,那么我们不妨就使用一种函数将以上对象创建过程封装起来以便于重复调用,同时可以给出特定接口来初始化对象:

function createStudent(name, age) {

var obj = new Object();

obj.name = name;

obj.age = age;

return obj;

}

var student1 = createStudent("easy1", 20);

var student2 = createStudent("easy2", 20);

...

var studentn = createStudent("easyn", 20);

这样一来我们就可以通过createStudent函数源源不断地”生产”对象了。看起来已经高枕无忧了,但贪婪的人类总有不满足于现状的天性:我们不仅希望”产品”的生产可以像工厂车间一般源源不断,我们还想知道生产的产品究竟是哪一种类型的。

比如说,我们同时又定义了”生产”水果对象的createFruit()函数:

function createFruit(name, color) {

var obj = new Object();

obj.name = name;

obj.color = color;

return obj;

}

var v1 = createStudent("easy1", 20);

var v2 = createFruit("apple", "green");

对于以上代码创建的对象v1、v2,我们用instanceof操作符去检测,他们统统都是Object类型。我们的当然不满足于此,我们希望v1是Student类型的,而v2是Fruit类型的。为了实现这个目标,我们可以用自定义构造函数的方法来创建对象。

构造函数模式创建对象

在上面创建Object这样的原生对象的时候,我们就使用过其构造函数:

var obj = new Object();//这样创建一个原生对象

在创建原生数组Array类型对象时也使用过其构造函数:

var arr = new Array(10);  //构造一个初始长度为10的数组对象

在进行自定义构造函数创建对象之前,我们首先了解一下构造函数和普通函数有什么区别。

其一,实际上并不存在创建构造函数的特殊语法,其与普通函数唯一的区别在于调用方法。对于任意函数,使用new操作符调用,那么它就是构造函数;不使用new操作符调用,那么它就是普通函数。

其二,按照惯例,我们约定构造函数名以大写字母开头,普通函数以小写字母开头,这样有利于显性区分二者。例如上面的new Array(),new Object()。

其三,使用new操作符调用构造函数时,会经历(1)创建一个新对象;(2)将构造函数作用域赋给新对象(使this指向该新对象);(3)执行构造函数代码;(4)返回新对象;4个阶段。

了解了构造函数和普通函数的区别之后,我们使用构造函数将工厂模式的函数重写,并添加一个方法属性:

function Student(name, age) {//相当于java的构造函数,只是没有类的概念

this.name = name;

this.age = age;

this.alertName = function(){

alert(this.name)

};

}

function Fruit(name, color) {

this.name = name;

this.color = color;

this.alertName = function(){

alert(this.name)

};

}

这样我们再分别创建Student和Fruit的对象:

var v1 = new Student("easy", 20);

var v2 = new Fruit("apple", "green");

这时我们再来用instanceof操作符来检测以上对象类型就可以区分出Student以及Fruit了:

alert(v1 instanceof Student);  //true

alert(v2 instanceof Student);  //false

alert(v1 instanceof Fruit);  //false

alert(v2 instanceof Fruit);  //true

alert(v1 instanceof Object);  //true 任何对象均继承自Object

alert(v2 instanceof Object);  //true 任何对象均继承自Object

这样我们就解决了工厂模式无法区分对象类型的尴尬。那么使用构造方法来创建对象是否已经完美了呢?

我们知道在JS中,函数是对象。那么,当我们实例化不止一个Student对象的时候:

var v1 = new Student("easy1", 20);

var v2 = new Student("easy2", 20);

...

var vn = new Student("easyn", 20);

其中共同的alertName()函数也被实例化了n次,因为不同的Student对象并不共用alertName()函数,我们可以用以下方法来检测不同的Student对象并不共用alertName()函数:

alert(v1.alertName == v2.alertName);  //flase,因为this指向的是两个不同的Student对象

我们知道,this对象是在运行时基于函数的执行环境进行绑定的。在全局函数中,this对象等同于window;在对象方法中,this指向该对象。在上面的构造函数中:

this.alertName = function(){

alert(this.name)

};

我们在创建对象(执行alertName函数之前)时,就将alertName()函数绑定在了该对象上。我们完全可以在执行该函数的时候再这样做,办法是将对象方法移到构造函数外部:

function Student(name, age) {

this.name = name;

this.age = age;

this.alertName = alertName;

}

function alertName() {

alert(this.name);

}

var stu1 = new Student("easy1", 20);

var stu2 = new Student("easy2", 20);

在调用stu1.alertName()时,this对象才被绑定到stu1上,在没有调用时,this对象就等于window。

我们通过将alertName()函数定义为全局函数,这样对象中的alertName属性则被设置为指向该全局函数的指针。由此stu1和stu2共享了该全局函数,解决了内存浪费的问题。

但是,通过全局函数的方式解决对象内部共享的问题,终究不像一个好的解决方法。如果这样定义的全局函数多了,我们想要将自定义对象封装的初衷便几乎无法实现了。更好的方案是通过原型对象模式来解决。

原型模式创建对象

  • 函数的原型对象
  • 对象实例和原型对象的关联
  • 使用原型模型创建对象
  • 原型模型创建对象的局限性

函数的原型对象

在了解如何使用原型模式创建对象之前,有必要先搞清楚什么是原型对象。

我们创建的每一个函数都有一个prototype属性,该属性是一个指针,该指针指向了一个对象。对于我们创建的构造函数,该对象中包含可以由所有实例共享的属性和方法。如下如所示:

Function.prototype===Function.__proto__//结果为true

Object.prototype指向Object构造函数的原型对象,该原型对象中的一些属性和方法是所有实例共享的,也就是全局的属性和方法,例如toString ()、valueOf()

Array.prototype指向Array构造函数的原型对象,同上有所有实例共性的属性个方法。也就是常见的数组方法和属性。

String.prototype等都是同理。

这个属性在我们使用js系统或者自己创建的对象的时候,会默认的加上.任何对象都有constructor属性,继承自原型的,constructor会指向构造这个对象的构造器或者构造函数。

constructor可以被改写,所以使用要小心。

在默认情况下,所有原型对象会自动包含一个constructor属性,该属性也是一个指针,指向prototype所在的函数:

每个对象都有constructor属性,该属性指向这个对象的构造函数。

JavaScript中因为函数也是对象,那么构造函数也是一个对象。构造函数是函数的一个实例。因此构造函数对象的constructor指向构造函数对象的构造函数,这个构造函数是Function。所有函数的构造函数都是Function,所有函数都是Function的一个实例对象

Function.constructor===Function

Object.constructor===Function

Array.constructor===Function   String.constructor===Function

对象实例和原型对象的关联

在调用构造函数创建新的实例时,该实例的内部会自动包含一个[[Prototype]]指针属性,该指针指便指向构造函数的原型对象。注意,这个指针关联的是实例与构造函数的原型对象而不是实例与构造函数:

在 JavaScript 中, constructor 属性返回对象的构造函数。

返回值是函数的引用,不是函数名:

JavaScript 数组 constructor 属性返回 function Array() { [native code] }

JavaScript 数字 constructor 属性返回 function Number() { [native code] }

JavaScript 字符串 constructor 属性返回 returns function String() { [native code] }

var fruits = ["Banana", "Orange", "Apple", "Mango"];

var ary=fruits.constructor;

alert(ary);//弹出function Array() { [native code] }

alert(fruits.constructor===ary);//弹出true

alert(fruits.constructor===Array);//也是弹出true

因此:Student.prototype.constructor===Student

使用原型模型创建对象

直接在原型对象中添加属性和方法

了解了原型对象之后,我们便可以通过在构造函数原型对象中添加属性和方法来实现对象间数据的共享了。例如:

function Student() {

}

Student.prototype.name = "easy";

Student.prototype.age = 20;

Student.prototype.alertName = function(){

alert(this.name);

};

var stu1 = new Student();

var stu2 = new Student();

stu1.alertName();  //easy

stu2.alertName();  //easy

alert(stu1.alertName == stu2.alertName);  //true 二者共享同一函数

以上代码,我们在Student的protptype对象中添加了name、age属性以及alertName()方法。但创建的stu1和stu2中并不包含name、age属性以及alertName()方法,而只包含一个[[prototype]]指针属性。当我们调用stu1.name或stu1.alertName()时,是如何找到对应的属性和方法的呢?

当我们需要读取对象的某个属性时,都会执行一次搜索。首先在该对象中查找该属性,若找到,返回该属性值;否则,到[[prototype]]指向的原型对象中继续查找。

由此我们也可以看出另外一层意思:如果对象实例中包含和原型对象中同名的属性或方法,则对象实例中的该同名属性或方法会屏蔽原型对象中的同名属性或方法。原因就是“首先在该对象中查找该属性,若找到,返回该属性值;”

拥有同名实例属性或方法的示意图:

上图中,我们在访问stu1.name是会得到”EasySir”:

alert(stu1.name);  //EasySir

通过对象字面量重写原型对象

很多时候,我们为了书写的方便以及直观上的”封装性”,我们往往采用对象字面量直接重写整个原型对象:

function Student() {

}

Student.prototype = {

constructor : Student,

name : "easy",

age : 20,

alertName : function() {

alert(this.name);

}

};

要特别注意,我们这里相当于用对象字面量重新创建了一个Object对象,然后使Student的prototype指针指向该对象。该对象在创建的过程中,自动获得了新的constructor属性,该属性指向Object的构造函数。因此,我们在以上代码中,增加了constructor : Student使其重新指回Student构造函数。

原型模型创建对象的局限性

原型模型在对象实例共享数据方面给我们带来了很大的便利,但通常情况下不同的实例会希望拥有属于自己单独的属性。我们将构造函数模型和原型模型结合使用即可兼得数据共享和”不共享”。

构造与原型混合模式创建对象

我们结合原型模式在共享方法属性以及构造函数模式在实例方法属性方面的优势,使用以下的方法创建对象:

//我们希望每个stu拥有属于自己的name和age属性

function Student(name, age) {

this.name = name;

this.age = age;

}

//所有的stu应该共享一个alertName()方法

Student.prototype = {

constructor : Student,

alertName : function() {

alert(this.name);

}

}

var stu1 = new Student("Jim", 20);

var stu2 = new Student("Tom", 21);

stu1.alertName();  //Jim  实例属性

stu2.alertName();  //Tom  实例属性

alert(stu1.alertName == stu2.alertName);  //true  共享函数

以上,在构造函数中定义实例属性,在原型中定义共享属性的模式,是目前使用最广泛的方式。通常情况下,我们都会默认使用这种方式来定义引用类型变量。

原文地址:https://www.cnblogs.com/susan-home/p/8653220.html

时间: 2024-11-01 16:30:09

JavaScript构造函数的prototype属性的相关文章

定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;定义在构造函数的prototype属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法. 如果我们的应用需要创建很多新的对象,并且这些对象还有许多的方法,为了节省内存,我们建议把这些方法都定义在构造函数的prototype属性上。

定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;定义在构造函数的prototype属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法. 如果我们的应用需要创建很多新的对象,并且这些对象还有许多的方法,为了节省内存,我们建议把这些方法都定义在构造函数的prototype属性上.当然,在某些情况下,我们需要将某些方法定义在构造函数中,这种情况一般是因为我们需要访问构造函数内部的私有变量.

javascript 构造函数中的属性与原型上属性优先级的比较

备注: 下面这个问题是我前天看书上发现的. 按照我以前的理解, a.rename()这个方法传什么值,结果都会弹出 小a,但我看书上的demo 弹出的是大A.... 我的困惑是:  js的构造函数中的属性与方法, 不是比原型对象上的属性与方法优先级要高吗?为什么会弹出的是大A? PS: 小弟很想有人能用通俗易通的语言, 简单的描述下,为什么会弹出的是 A, 而不是小 a.在此谢谢了  function F(name) { this.name = name; } var a = new F("a&

javascript Array对象prototype属性

prototype 属性使您有能力向对象添加属性和方法. 语法: object.prototype.name=value 例子: <script type="text/javascript"> function employee(name,job,born) { this.name=name; this.job=job; this.born=born; } var bill=new employee("Bill Gates","Engineer

JavaScript中的prototype属性

1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 <script type="text/javascript"> 7 /* 8 每个函数都有一个prototype属性,这个属性是指向一个对象的引用,这个对象称为原型对象

Javascript中prototype属性的详解

原文链接:http://www.cnblogs.com/Uncle-Keith/p/5834289.html 在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于‘类的’,而是通过构造函数(constructor)和原型链(prototype chains)实现的.但是在ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对

JavaScript:prototype属性使用方法

原文:https://www.cnblogs.com/lidabo/archive/2012/01/05/2313481.html https://www.cnblogs.com/wdlhao/p/5743770.html prototype就是"一个给类的对象添加方法的方法",使用prototype属性,可以给类动态地添加方法,以便在JavaScript中实现"继承"的效果. 具体来说,prototype 是在 IE 4 及其以后版本引入的一个针对于某一类的对象的

Javascript中prototype属性

prototype作为JS相对比较难理解的一个知识点,在这里发表下自己的理解. 本文将包含以下几部分内容: 1.js prototype的简单介绍, 2.js构造函数的介绍, 3.prototype的深入理解, 4.constructor. 一.在其他的面向对象语音中,比如Java,存在类(class)的概念,对象就是类的实例.但是再js当中呢,是没有类的概念的,平常时说的加一个class类是指在样式css中加一个类class.js中一切皆对象,所有的东西都是对象(除了null和undefine

为什么实例没有prototype属性?什么时候对象会有prototype属性呢?

为什么实例没有prototype属性?什么时候对象会有prototype属性呢? javascript loudou 1月12日提问 关注 9 关注 收藏 6 收藏,554 浏览 问题对人有帮助,内容完整,我也想知道答案0 问题没有实际价值,缺少关键内容,没有改进余地 function Foo() {} var foo = new Foo(); console.log(foo.prototype);// undefined console.log(foo.__proto__ === Foo.pr

《prototype属性的理解》

1.对象:对象是JS的基本数据类型(原始类型(数字.字符串和布尔值),对象类型).对象是一种复合值:它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值. 2.三类JS对象和两类属性: 内置对象:是由ECMAScript规范定义的对象或类.例如,数组(Array).函数(Function).日期(Date)和正则表达式(RegExp)都是内置对象 宿主对象:由JS解释器所嵌入的宿主环境定义的.客户端JS中表示网页结构的HTMLElement对象均是宿主对象,既然宿主环境定义的方法可以