搜索
首页web前端js教程JavaScript中的prototype原型学习指南_基础知识

原型是什么

Function 类型有一个属性 prototype,直接翻译过来就是原型。这个属性就是一个指针,指向一个对象,这个对象包含一些属性和方法,这些属性和方法会被当前函数生成的所有实例(对象)所共享。

这句话根据前面所说的,细细琢磨下来,就可以得到下面代码:

function Person(){
 ...
}
Person.prototype = {
 country : 'china',
 sayName : function(){
  ...
 }
}

先创建了一个 Function 类型的实例 person,然后 person 的方法 prototype 是一个对象,就声明指向了一个对象。这个对象里面的属性和方法,会被当前 person 函数生成的实例所共享。也就是说:

person1 = new Person();
person2 = new Person();

person1 和 person2 都是通过 Person 这个 Function 类型实例,再次生成的实例,它们俩都有共同的属性 country 和方法 sayName,因为它们都有某个指针(__proto__),直接指向 Person.prototype 所指向的对象。不过要注意 __proto__ 这个指针是不标准的,只有 Chrome 和 Firefox 等浏览器自己定义的,实际中,也不会用到这个属性,只是作为理解 prototype 来用:

201659145452510.png (668×270)

关于原型等用法,后面会更具体的讲到。

创建对象的模式

下面,我们就来看下创建对象的方法和常用模式,以及它们之间的优缺点。

1.工厂模式

就像工厂一样,抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节。通过使用函数代替部分重复工作,代码如下:

function createPerson(name, age, job){
 var o = new Object();
 o.name = name;
 o.age = age;
 o.job = job;
 o.sayName = function(){
  alert(this.name);
 };
 return o;
}
var person1 = createPerson("jiangshui","22","engineer");

这样就创建出来了一个人,工厂模式解决了多个相似对象重复创建问题,但是没有解决对象识别问题。只是单纯的创建了一个对象,而不管这个对象是从人类模版还是动物模版创建的,无法区分这个对象的类型。

2.构造函数模式

创建一个自定义的构造函数,从而定义自定义对象类型的属性和方法。

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = jpb;
 this.sayName = function(){
  alert(this.name);
 };
};
var person1 = new Person(...);

3.构造函数模式与工厂模式区别:

  • 没有显式的创建对象。
  • 直接将属性和方法赋值 this 对象。
  • 没有 return 语句。

Person 是 Function 类型的对象,new 之后,会继续产生一个对象,但这个新产生的对象,由于在函数中传递进去参数,并赋值给了 this 指针,那么传递进去的内容,就变成了新产生对象的属性或方法。

构造函数默认习惯是首字母大写,上面代码执行经历了下面几个步骤:

  • 创建一个新对象
  • 将构造函数作用域赋值给新对象
  • 执行构造函数中的代码
  • 返回新对象

这样生成的实例中,都默认包含一个 constructor 属性指向构造函数,例如:

alert(person1.constructor == Person);

所以用构造函数模式,有类型的区分,可以将它的实例标识为一种特定的类型。

此外,构造函数就是普通的函数,因为要反馈得到新对象,所以用 new 来调用。如果不用的话,直接执行就跟普通函数一样,例如上面,执行 Person.sayName() 会弹出 window.name,因为函数在 window 下面执行,所以 this 指向 window。

构造函数模式也是有缺陷的,构造函数模式里面的方法,在每个实例上都重新创建了一遍,因此不同实例上的同名函数是不相等的。例如:

person1.sayName == person2.sayName; //false

也就是说,由构造函数生成的每个对象实例,属性和方法都是独有的,都是复制了一遍。属性独有是必须的,因为这正是对象之间不同的地方,但是很多方法功能和代码都是一样的,重复复制多次,显然就会浪费资源。

所以我们可以把函数放在外面,然后在构造函数里面,用指针指向这个函数,那么生成的实例中,方法存储的就是一个指向某函数的指针,也就共用一个函数了:

function Person(name, age){
 this.name = name;
 this.age = age;
 this.sayName = sayName;
}
function sayName(){
 alert(this.name);
}

但是这样,这个函数就变成了全局函数,而且与 Person 构造函数关联性不强,没有封装性可言。

下面有请原型模式登场。

原型模式

前面已经介绍了一部分关于原型的基础知识。简单的说,就是每个函数都有一个 prototype 属性,指向一个对象(原型对象),这个对象里面可以放一些属性或者方法。然后这个函数生成的实例,会有一个不规范的属性(__proto__)指向原型。

由此来看,你应该可以理解:prototype 产生的属性和方法是所有实例共享的。

这样正好解决了上面构造函数模式中,实例中函数的共用问题。例如下面代码:

function Person(){
 ....
}
Person.prototype.name = "jiangshui";
Person.prototype.sayName = function(){
 alert(this.name);
};

var person1 = new Person();
person1.sayName(); //jiangshui

或者

Person.prototype = {
 constructor : Person,
 name : "jiangshui",
 sayName : function(){
  alert(this.name);
 }
};

第二种方法覆盖了整个 prototype 对象,所以需要手动指定 constructor 属性,指向构造函数否则会指向 Object。

梳理一下它们的关系:

  • Person.prototype -》 原型对象,可以定义一些属性或者参数,被所有实例共用。
  • Person.prototype.constructor == Person —》 原型对象有个默认的属性 constructor 指向该原型对象所属的构造函数(注意另一种写法会覆盖掉这个属性,需要重新指定)。
  • person1 = new Person() -》构造函数生成实例,实例包含了构造函数的内容和原型对象的内容。
  • person1.__proto__ -》指向创建这个实例的原型对象(不规范,不要用)。

使用 isPrototypeOf() 可以确定对象之间的关系。例如:

Person.prototype.isPrototypeOf(person1);

当代码读取某个对象的某个属性,会执行搜索。先从当前对象开始,如果没有,则搜索指针指向的原型对象,而不会搜索构造函数。对象实例可以访问但是不能重写原型对象的值。如果实例中设置了与原型对象同名的属性,则搜索过程,在实例中结束而不会访问原型对象,所以达到覆盖的目的。因此即使这个属性设置为 null,也表示在实例中已经存在该属性,而不会取消掉这个属性,从而可以访问原型对应属性。

所以需要使用 delete 操作符,完全删除实例属性,从而可以重新访问原型。

原型是动态的,对原型对象所做的任何修改,都能立即从实例上反映出来。原因是实例与原型之间的松散链接关系,每次调用实例的属性方法,都会进行一次查询,如果原型变了,查询结果也就变了。

了解原型之后,我们也可以对原生对象添加新方法或属性。Object、Array、String 等原生引用类型,与上面构造函数类似,我们可以用 prototype 扩充它们的方法。例如:

String.prototype.startsWith = function(text){
 return this.indexOf(text) == 0;
};
var msg = "Hello World";
msg.startsWith("Hello");

这段代码为 String 这个原生引用类型,增加了一个 startsWith 方法,功能就是传递进去一个参数,看看要测试的字符串是否以参数开始。由于原型的动态性,所以只要执行一下,所有字符串类型的变量全都获得了这个方法。

但是不推荐使用这个方法,如果用的太多,代码太多,会导致维护困难、代码混乱等情况。一般情况下,会先继承某个原生引用类型,然后再在新自定义的类型上创建。关于继承,后面会再总结。

原型模式也不是万能的,原型中的所有属性和方法是被所有实例共享的,所以对于函数之类非常合适,而对于包含引用类型的属性来说,就会产生一些冲突。例如:

function Person(){}
Person.prototype = {
 constructor : Person,
 friends : ["greg","jack"]
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("tom");
console.log(person2.friends);

你会在 console 中看到,person2 的 friends 多了一个 tom,这并不是我想要的,但是对 person1 定义他的朋友时,的确影响到了实例 person2。

所以我们要结合原型模式和构造函数模式来使用。

组合使用构造函数模式和原型模式

这就是最常用的模式,构造函数用来定义实例属性,通过传递参数实现自定义;原型用来定义方法或者需要所有实例共享的属性。这样,既实现了自定义,又保证了共用,还避免了问题。

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.friends = ["greg","jack"];
}
Person.prototype = {
 constructor : Person,
 sayName : function(){
  alert(this.name);
 }
};
var jiangshui = new Person("jiangshui","22","engineer");

实际应用示例

OK,到了这里,你可能会看懂原型是啥,以及如何创建对象,可是,这些又有什么用?确实,我之前的工作,一直也就是用 jQuery 写一些代码就可以了,根本用不到封装然后生成对象实现功能等。那这些究竟有什么用?

这种开发方式主要用于模块化和组建化的开发。比如你常用的弹窗功能,你当然可以把弹窗有关代码,每次都粘贴复制,然后修改一下就可以用在项目里面了。更好的选择是把你的弹窗功能代码,抽象封装成这样的一个组件,这样当你需要用弹窗的时候,只需要传递参数生成一个弹窗实例,就可以调用了。

原型对象和原型链

在Javascript中,万物皆对象,但对象也有区别,大致可以分为两类,即:普通对象(Object)和函数对象(Function)。

一般而言,通过new Function产生的对象是函数对象,其他对象都是普通对象。

举例说明:

function f1(){
  //todo
}
var f2 = function(){
  //todo
};
var f3 = new Function('x','console.log(x)');
 
var o1 = {};
var o2 = new Object();
var o3 = new f1();
 
console.log(
  typeof f1,//function
  typeof f2,//function
  typeof f3,//function
  typeof o1,//object
  typeof o2,//object
  typeof o3 //object
);
>> function function function object object object

f1属于函数的声明,最常见的函数定义方式,f2实际上是一个匿名函数,把这个匿名函数赋值给了f2,属于函数表达式,f3不常见,但也是一种函数对象。

Function是JS自带的对象,f1,f2在创建的时候,JS会自动通过new Function()的方式来构建这些对象,因此,这三个对象都是通过new Function()创建的。

在Javascript中创建对象有两种方式:对象字面量和使用new表达式,o1和o2的创建恰好对应了这两种方式,重点讲一下o3, 如果用Java和C#的思路来理解的话,o3是f1的实例对象,o3和f1是同一类型,至少我以前这么认为,其实不然…

那么怎么理解呢? 很简单,看o3是不是通过new Function产生的, 显然不是,既然不是函数对象,那就是普通对象 。

通过对函数对象和普通对象的简单理解之后,我们再来了解一下Javascript中的原型和原型链:

在JS中,每当创建一个函数对象f1 时,该对象中都会内置一些属性,其中包括prototype和__proto__,  prototype即原型对象,它记录着f1的一些属性和方法。

需要注意的是,prototype 对f1是不可见的,也就是说,f1不会查找prototype中的属性和方法。

function f(){}
f.prototype.foo = "abc";
console.log(f.foo); //undefined

那么,prototype有什么用呢? 其实prototype的主要作用就是继承。 通俗一点讲,prototype中定义的属性和方法都是留给自己的“后代”用的,因此,子类完全可以访问prototype中的属性和方法。

想要知道f1是如何把prototype留给“后代”,我们需要了解一下JS中的原型链,此时,JS中的 __proto__ 入场了,这哥们长的很奇特,隐藏的也很深,以致于你经常见不到它,但它在普通对象和函数对象中都存在, 它的作用就是保存父类的prototype对象,JS在通过new 表达式创建一个对象的时候,通常会把父类的prototype赋值给新对象的__proto__属性,这样,就形成了一代代传承…

function f(){}
f.prototype.foo = "abc";
var obj = new f();
console.log(obj.foo); //abc

现在我们知道,obj中__proto__保存的是f的prototype, 那么f的prototype中的__proto__中保存的是什么呢? 看下图:

201659150626664.png (515×122)

如图所示,f.prototype的__proto__中保存的是Object.prototype,Object.prototype对象中也有__proto__,而从输出结果看,Object.prototype.__proto__ 是null,表示obj对象原型链的终结。如下图所示:

201659150641500.png (607×148)

obj对象拥有这样一个原型链以后,当obj.foo执行时,obj会先查找自身是否有该属性,但不会查找自己的prototype,当找不到foo时,obj就沿着原型链依次去查找…

在上面的例子中,我们在f的prototype上定义了foo属性,这时obj就会在原型链上找到这个属性并执行。

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
es6数组怎么去掉重复并且重新排序es6数组怎么去掉重复并且重新排序May 05, 2022 pm 07:08 PM

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

JavaScript的Symbol类型、隐藏属性及全局注册表详解JavaScript的Symbol类型、隐藏属性及全局注册表详解Jun 02, 2022 am 11:50 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

原来利用纯CSS也能实现文字轮播与图片轮播!原来利用纯CSS也能实现文字轮播与图片轮播!Jun 10, 2022 pm 01:00 PM

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

JavaScript对象的构造函数和new操作符(实例详解)JavaScript对象的构造函数和new操作符(实例详解)May 10, 2022 pm 06:16 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

javascript怎么移除元素点击事件javascript怎么移除元素点击事件Apr 11, 2022 pm 04:51 PM

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

JavaScript面向对象详细解析之属性描述符JavaScript面向对象详细解析之属性描述符May 27, 2022 pm 05:29 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

foreach是es6里的吗foreach是es6里的吗May 05, 2022 pm 05:59 PM

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。

整理总结JavaScript常见的BOM操作整理总结JavaScript常见的BOM操作Jun 01, 2022 am 11:43 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具