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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)