资讯专栏INFORMATION COLUMN

JavaScript的prototype的理解

icattlecoder / 1603人阅读

摘要:另外,首字母大写是我们用以区分构造函数和普通函数的语法习惯,没有强制规定一定要大写。这个对象和构造函数的指向的是同一个对象。

prototype是JavaScript比较难理解的一部分,绕来绕去的。不理出头绪来,理解原型的概念是件头疼的事情。
为了方便后面的说明,先从对象说起。

对象 广义对象

JavaScript里任何事物都是对象,不管什么,都是从Objec衍生出来的。function,array,string,{},都是对象。只是大家的功能各有不同。Object就像女娲,JavaScript世界的任何事物,都是它“创造”的。这里的对象是广义的泛指的对象,是一切事物的统称。

狭义对象

狭义对象是指一般的对象类型,通过var p = new Object()或者通过var p = {}的方式创建出来的东西。用来表示某一种事物,包含事物的属性和方法。后面我们说的对象就是狭义对象,专门指JavaScript里产生出来的实例对象。其他的函数、数组等我们只从它本身的功能角度来看待,不当作对象,看成是执行功能、储存数据的一种工具。

对象的产生 对象的创建

我们创建一个对象最常用的方法时var p = {},这只是JavaScript创建对象的快捷方式,其根本是通过var p = new Object()的方式产生。这种方式是通过构造函数来创建对象的。

构造函数 构造函数创建对象

如何通过构造函数来创建对象呢?网上有很多资料,这里简单叙述一下。有如下构造函数:

function Person(name) {
    this.name = name; // 属性
    this.run = function() { // 方法
        console.log("I"m running");
    }
}

上面是一个简单的构造函数。构造函数它首先是一个函数,跟我们平常写的函数是同一类。不同的是它里面有一个this指针,指向通过它产生的实例对象。另外,首字母大写是我们用以区分构造函数和普通函数的语法习惯,没有强制规定一定要大写。但为了方便理解最好用首字母大写的命名习惯来命名。

var p = new Person();

通过new的方式创建实例。上面变量p就是一个实例对象,它包含了一个name属性和一个run方法。用new 构造函数创建对象的过程有两步:

在内存中开辟一块内存地址,用以存放新的实例对象

实例对象调用构造函数,那么里面的this指针指向这个实例,从而为对象设置了name属性和run方法。

name属性和run方法将是实例对象自身的东西,属于对象自有。相当于你看到的结果是这样一个对象:

p = {
    name: "Pelemy",
    run: functioin() {
        cosole.log("I"m running");
    }
}
实例对象重复创建的问题

按照上面的步骤创建对象,每执行一次 new Person() 就会有一个新的对象产生。

var p1 = new Person(); // 新对象,新的内存空间
var p2 = new Person(); // 新对象,新的内存空间
console.log(p1 === p2); // false
console.log(p1.run === p2.run) // false

每个对象都有自己name属性和run方法。这里有个问题,每个实例对象run方法的实现都是一样的,这是一个雷同的方法。雷同的方法每个人都有一个,这很浪费资源。如果有一个地方共享,同一种类的对象都去共享的地方取就好了,不需要每个都留一个备份在自己身上。为了处理这种情况,prototype产生了。

构造函数的prototype属性

构造函数中this指针设置的属性和方法将是新实例对象自身拥有的属性和方法,我们叫本地属性和方法。为了使各个产生的实例对象不重复设置相同的属性或方法,JavaScript把这部分放到了构造函数的一个地方,这个地方就是构造函数的prototype属性指向的对象(注意这里的对象也是前面说的狭义对象)。prototype本意是原型、蓝图,在这里我认为把它叫做“引用属性”来理解更贴切。还是以前面的例子。

function Person(name) {
    this.name = name; // 定义本地属性
}
Person.prototype.run = function() { // 定义引用方法
    console.log("I"m running");
}

这里可能会突然让人头疼。Person.prototype.run突然多了这么长一串,连续三个点。我们一个个看。首先把prototype当作一个属性,就像我们常写一个对象的属性那样,比如 car.color, car.speed。这里也一样,prototype是构造函数的一个属性,它的值是一个对象

Person.prototype = {
 // properties and methods
}

这个对象就是将来我们用来存放共享属性和方法的。这些属性和方法可以被实例对象引用。注意是引用,也就是自己没有,指向那里去调用就行了。然后在这个对象里定义run方法

Person.prototype = {
   // properties and methods
   run: function() {
     console.log("I"m running");
   }
}

当然,我们这里只是为了多定义一个run方法,而不是定义整个prototype的对象(这样会把这个对象的其他方法擦掉,只剩下run方法)。所以定义整个引用方法的方式就是
object.run = ... 即
Person.prototype.run = ...
这样新创建的实例再也不用自己定义这个方法,只要从共享对象上引用就好了。举例:

function Person(name) {
    this.name = name; // 属性
    this.myfunction = function() {
        console.log("just work for me");
    }
}
Person.prototype.run = function (distance) {
    console.log("I"ve run " + distance + " meters");
}
var p1 = new Person("Pelemy");
var p2 = new Person("Summmy");
console.log(p1.name); // Pelemy
p1.run(100); // I"ve run 100 meters
console.log(p2.name); // Summy
p2.run(200); // I"ve run 200 meters

p1,p2的本身没有run方法(构造函数里没定义),但都能执行到,他们是引用prototype里的run方法。执行方法时,先在本地查找是否有这个方法,没有则向上寻找prototype对象是否有,有则执行这个方法。这里可以通过hasOwnProperty方法来判断本地是否有这个方法,沿用上面的例子

console.log(p1.hasOwnProperty("run")); // false
console.log(p1.hasOwnProperty("myfunction"); // true;
console.log(p1.hasOwnProperty("name"); // true;

把构造函数的定义视为本地属性定义,把prototype属性对象视为引用属性定义,这样分开来理解,就会轻松多了。

对象的引用对象与构造函数的prototype属性的关系

实例对象创建后,构造函数中的属性和方法成为本地属性和方法,prototype中的属性和方法成为引用属性和方法。我想知道一个实例对象产生后,怎么知道这个对象是从哪里引用的?对象不像构造函数,生下来就有prototype属性,连接着引用对象。但对象有一个只读的属性__proto__,指向的就是它的引用对象(这叫隐式原型对象)。这个对象和构造函数的prototype指向的是同一个对象。因为引用对象是放在那里供别人引用的,不会复制或重新产生,所以它是被直接定义到实例对象的__proto__的。

function Person(name) {
    this.name = name; // 属性
}

var p = new Person();
console.log(p.__proto__ === Person.prototype); // true
prototype对象怎么产生

这个prototype对象是怎么来的呢?构造函数在JavaScript中产生时,随即就有一个它的引用对象(prototype属性指向的对象)产生了。它是伴随着构造函数产生的。

延伸

prototype是构造函数生来就有的属性,对象是没有的。

构造函数的prototype属性的值是一个对象。即Person.prototype是一个对象

prototype的属性和方法是共用属性和方法,是所有实例对象都有,不同的属性和方法在构造函数实现,按这样创建对象就是类的继承的方式了。产生的实例对象相当于都从父类继承过来,这就是为什么把引用的这个对象叫原型(不叫引用或共享)的原因了。

总结

构造函数中定义的属性和方法是本地属性和方法,prototype指向的对象定义的属性和方法是引用属性和方法。

prototype定义的属性和方法能被实例共同引用,是共同的部分,相当于每个对象都有和引用对象同样的属性和方法,而自身的方法就通过构造函数来呈现。

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

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

相关文章

  • 深入理解JavaScript原型与继承

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

    mengbo 评论0 收藏0
  • 理解JavaScriptprototype和__proto__

    摘要:这篇文章的的目的试图通过最简单的表述让大家理解和先把最重要的几点列出来大家可以带着这几个核心要点阅读下面的文章是用来在原型链上查找你需要的方法的实际对象所有的对象都有这个属性这个属性被引擎用作继承使用根据的规范这个属性应该是一个内在的属性但 这篇文章的的目的试图通过最简单的表述,让大家理解prototype和__proto__ 先把最重要的几点列出来,大家可以带着这几个核心要点阅读下面...

    tanglijun 评论0 收藏0
  • 深入理解Javascript原型关系

    摘要:如下所示在规范中,已经正式把属性添加到规范中也可以通过设置和获取对象的原型对象对象之间的关系可以用下图来表示但规范主要介绍了如何利用构造函数去构建原型关系。 前言 在软件工程中,代码重用的模式极为重要,因为他们可以显著地减少软件开发的成本。在那些主流的基于类的语言(比如Java,C++)中都是通过继承(extend)来实现代码复用,同时类继承引入了一套类型规范。而JavaScript是...

    ethernet 评论0 收藏0
  • Javascript面向对象编程

    摘要:如果要理解基于原型实现面向对象的思想,那么理解中得三个重要概念构造函数原型原型链对帮助理解基于原型的面向对象思想就显得尤为重要。函数对象的原型在中,函数是一种特殊的对象,所有的函数都是构造函数的实例。 介绍 和java这种基于类(class-base)的面向对象的编程语言不同,javascript没有类这样的概念,但是javascript也是面向对象的语言,这种面向对象的方式成为 基...

    wanglu1209 评论0 收藏0
  • 理解 JavaScript call()/apply()/bind()

    摘要:理解文章中已经比较全面的分析了在中的指向问题,用一句话来总结就是的指向一定是在执行时决定的,指向被调用函数的对象。与和直接执行原函数不同的是,返回的是一个新函数。这个新函数包裹了原函数,并且绑定了的指向为传入的。 理解 JavaScript this 文章中已经比较全面的分析了 this 在 JavaScript 中的指向问题,用一句话来总结就是:this 的指向一定是在执行时决定的,...

    duan199226 评论0 收藏0

发表评论

0条评论

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