JavaScript中的原型Prototype【1】

2024-05-11 04:18
文章标签 java script prototype 原型

本文主要是介绍JavaScript中的原型Prototype【1】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载自: 原型Prototype

在看了不少资料之后,对于原型的理解就是两个字,共享。 在这一部分,将通过分析原型出现的原因,已经原型的应用-->继承。

原型Prototype的出现

在讨论原型的出现,就需要从对象的创建开始说起。

创建对象可以通过new Object()或者直接通过对象字面量{}完成,但是使用这种方法会导致在创建多个对象的时候,产生大量的代码。因此有了工厂模式:
1 工厂模式
工厂模式就是使用一个函数将需要创建的对象的属性都封装起来,如:

    function createPerson(name, age, job){var obj = new Object();obj.name = name;obj.age = age;obj.job = job;obj.alertName = function(){console.log(this.name);}return obj;}var personFirst = createPerson("June", 21, "Student");var PersonSecond = createPerson("Louis", 23, "Student");

这样就可以通过工厂模式创建对象,只需要将参数传入到createPerson函数中,函数会返回一个obj初始化后的对象。需要注意的是,使用工厂模式创建的对象,不能解决对象识别问题,即:

    personFirst instanceof object   //打印truepersonFirst instanceof createPerson     //打印false

2 构造函数模式

    function Person(name, age, job){this.name = name;this.age = age;this.job = job;this.alertName = function(){console.log(this.name);}}var personFirst = new Person("June", 21, "Student");var personSecond = new Person("Louis", 23, "Student");

1.首先构造函数和普通的函数是没有什么不同
2.要把一个函数当成构造函数使用,只需要使用new操作符
3.根据习惯,构造函数的第一个字母使用大写

  • new操作符创建对象的过程 创建一个新的对象 --> 将构造函数的作用域赋给新对象(即this指向了新对象) --> 对象的初始化 --> 返回新对象

  • 构造函数模式的不足

使用构造函数存在的最大问题就是每个实例都会将所有的属性创建一次。这个对于数值属性来说可以接受,但是如果函数方法每个实例都要创建一遍,则不合理。所以对于构造函数模式可以将方法转移到构造函数外部,如下:

    function Person(name, age, job){this.name = name;this.age = age;this.job = job;this.sayName = sayName;}function sayName(){console.log(this.name);}var personFirst = new Person("June", 21, "Student");var personSecond = new Person("Louis", 23, "Student");

但是对于这种方法,如果需要多个函数,则会导致很多个全局函数。对于构造函数模式的这个问题,我们可以通过原型来解决。

3 原型模式

  • 原型

在每个创建的函数中都会有一个原型prototype属性,这个属性是一个指针,指向一个对象。原型指向的这个对象包含可以由该函数创建的对象共享访问的属性和方法。简单的说就是该原型指向的对象中含有可以被共享访问的属性和方法,而只有该函数创建的实例才有权限访问。

    //构造函数function Person(){}//构造函数prototype指向的原型对象Person.prototype.name = "Louis";Person.prototype.age = 23;Person.prototype.job = "Student";Person.prototype.sayName = function(){console.log(this.name);}//使用构造函数创建的实例var personFirst = new Person();personFirst.sayName();var personSecond = new Person();personSecond.sayName();console.log(personFirst.sayName == personSecond.sayName);   //打印true

我们在Person函数的原型中添加的所有属性和方法都能够被personFrist和personSecond访问,而且需要注意的是共享访问,而不是各自保存一个副本。

  • 原型对象的理解
    1. 在创建了一个函数之后,JavaScript引擎就会为该函数创建一个prototype属性,这个属性指向原型对象。而原型对象中含有一个constructor的属性,这个属性指向含有原型对象所在的函数。就如之前的构造函数Person、Person.prototype、personFirst之间的关系如下图: 三者之间的关系示意图

由上图可以看到,其实实例和构造函数之间并没有直接的联系。每个实例中都含有[[prototype]]属性,这个属性指向Person.prototype这个原型对象,而Person.prototype原型对象中含有constructor属性则指向构造函数Person,构造函数也含有指向Person.prototype原型的属性prototype。
在personFirst中我们可以看到,该对象除了[[prototype]]属性之外,并没有其他的属性。但是我们能够通过personFirst.name得到“Louis"的值。这就是原型和对象实例查找属性的问题:

一个对象在读取某个属性值的时候,首先是先查找该对象是否包含这个属性,如果有则返回,不再向原型查找;如果没有则向上查找该对象指向的原型是否含有这个属性,如果有则返回原型中该属性的值,如果没有则继续向继承的原型链上查找,如果还是没有则直接返回undefined。
对于上面的例子,在使用personFirst.name访问取name属性值的时候,首先先找personFirst这个对象上是否含有属性值name,很明显并没有,接着则personFirst的原型链Person.prototype上查找name属性,发现有,而且值为"Louis",则将该值直接返回。

  1. 原型属性的屏蔽:当我们在对象实例上创建一个和原型上相同名称的属性时,根据属性的查找顺序,我们可以知道原型上相同名称的属性将会被屏蔽掉。Show the fllowing code!

      //构造函数function Person(){}//原型对象Person.prototype.name = "Louis";Perosn.prototype.age = 23;Person.prototype.job = "Student";Person.prototype.sayName = function(){console.log(this.name);}//实例var personFirst = new Person();personFirst.name = "John";personFirst.sayName()   //打印: John  --> 来自实例var personSecond = new Person();personSecond.sayName()  //打印:Louis    --> 来自原型

当我们为对象添加一个属性的时候,这个属性就会屏蔽原型对象中的同名属性;也就是说如果添加的这个属性只会阻止我们访问原型中的那个对象,但是不会修改原型中的属性。不过,我们可以使用delete操作符来删除对象实例属性,从而恢复访问原型中的对象。

    function Person(){}Person.prototype.name = "Louis";Person.prototype.age = 23;Person.prototype.job = "Student";Person.prototype.sayName = function(){console.log(this.name);}var person1 = new Person();var person2 = new Person();person1.name = "June";console.log(person1.name);      //打印: June --> 来自对象console.log(person2.name);      //打印: Louis--> 来自原型delete person1.name;console.log(person1.name);      //打印: Louis--> 来自原型
  • in操作符
    1. hasOwnProperty && in 操作符 使用hasOwnProperty()方法可以检测是一个属性是存在于实例中,还是原型中。只有当给定的属性存在于对象实例中,才会返回true。而对于in操作符,则无论属性是否存在于实例还是原型中,只要能够访问到这个属性,则返回true。
    //使用hasOwnProperty()方法function Person(){};Person.prototype.name = "Louis";Person.prototype.age = 23;Person.prototype.job = "Student";Person.prototype.sayName = function(){console.log(this.name);}var personFirst = new Person();var personSecond = new Person();console.log(personFirst.hasOwnProperty("name"));    //false --> 来自原型console.log(personSecond.hasOwnProperty("age"));    //false --> 来自原型personFirst.name = "June";console.log(personFirst.hasOwnProperty("name"));    //true --> 来自对象实例
    //使用in操作符function Person(){};Person.prototype.name = "Louis";Person.prototype.age = 23;Person.prototype.job = "Student";Person.prototype.sayName = function(){console.log(this.name);}var personFirst = new Person();var personSecond = new Person();console.log("name" in personFirst); //true --> 来自原型console.log("age" in personSecond); //true --> 来自原型personFirst.name = "June";console.log("name" in personFirst); //true --> 来自实例对象

下面的代码用来检测属性是否存在于原型中

    function hasPrototypeProperty(obj, name){return !obj.hasOwnProperty(name) && (name in obj);}
  • 原型的字面量写法
    1. 上面使用Person.prototype.xxx的方法书写原型,每次需要添加属性的时候都要重复书写前面部分的代码。对于这种情况,我们可以使用原型的字面量来创建原型对象。
    function Person(){}Person.prototype = {name: "Louis",age : 23,job : "Student",sayName : function(){console.log(this.name);}}

使用原型字面量有一点需要注意的是,原型对象的constructor属性不再指向原来的构造函数Person();
这是因为使用Person.prototype = {}创建原型,相当于Person.prototype是Object的实例,而实例中存在的[[prototype]]指向的是Object的原型,原型中的constructor属性指向的就是Object对象,所以如果constructor属性很重要,则需要在创建原型的字面量上重新指定,如:

    function Person(){}Person.prototype = {constructor : Person,name: "Louis",age : 23,job : "Student",sayName : function(){console.log(this.name);}}
  • 原型的动态性
    1. 原型的动态性体现在只要对原型对象所做的任何修改都能够立即从实例上得到反映。
    var friend = new Person();Person.prototype.sayHi = function(){console.log("halo Louis");}friend.sayHi();     //打印: halo Louis

需要注意的是,给原型添加属性和方法都能够在实例上得到反映,但是如果重新改写原型,则会切断实例和原型之间最初的关系。
需要谨记的是:实例中的指针仅仅指向原型,而不是指向构造函数
重写原型之后构造函数、对象实例、原型之间的关系如下图:

重写原型之后构造函数、对象实例、原型之间的关系

  • 原型的弊端
    1.原型的弊端也是显而易见的,对于数值型属性而言,每一个属性也都是共享的。对于所创建的每一个实例都具有相同的属性值,其实也不合理。应该是数值型属性为各自所有,而方法属性则可以进行共享,所以一般在创建构造函数的时候都是使用构造函数模式和原型模式相结合进行,如:
    //属性私有function Person(name, age, job){this.name = name;this.age = age;this.job = job;}//方法共有Person.prototype.sayName = function(){console.log(this.name);}var personFirst = new Person("June", 21, "Student");var personSecond = new Person("Louis", 23, "Student");personFirst.sayName();  //打印: JunepersonSecond.sayName(); //打印: Louis

由于篇幅过长,关于原型的应用继承放在原型Prototype[2]

参考文章

[1]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
[2]http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/
[3]http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
[4]http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
[5]JavaScript高级程序设计(3nd Edition)第六章


这篇关于JavaScript中的原型Prototype【1】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/978480

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2