js中的prototype原型解析
在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例。但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于‘类的',而是通过构造函数(constructor)和原型链(prototype chains)实现的。但是在ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让原型对象的写法更加清晰、更像面向对象编程的语法而已。
按照我的习惯,在写文章前我会给出文章目录。
以下内容会分为如下小节:
1.构造函数的简单介绍
2.构造函数的缺点
3.prototype属性的作用
4.原型链(prototype chain)
5.constructor属性
5.1:constructor属性的作用
6.instanceof运算符
1.构造函数的简单介绍
在我的一篇Javascript 中构造函数与new命令的密切关系文章中,详细了介绍了构造函数的概念和特点,new命令的原理和用法等,如果对于构造函数不熟悉的同学,可以前往细细品味。以下做一个简单的回顾。
所谓构造函数,就是提供了一个生成对象的模板并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。总的来说,构造函数就是对象的模板,对象就是构造函数的实例。
构造函数的特点有:
a:构造函数的函数名首字母必须大写。
b:内部使用this对象,来指向将要生成的对象实例。
c:使用new操作符来调用构造函数,并返回对象实例。
看一个最简单的一个例子。
1
2
3
4
5
|
function Person(){ this .name = 'keith' ; } var boy = new Person(); console.log(boy.name); //'keith' |
2.构造函数的缺点
所有的实例对象都可以继承构造函数中的属性和方法。但是,同一个对象实例之间,无法共享属性。
1
2
3
4
5
6
7
8
9
10
11
12
|
function Person(name,height){ this .name=name; this .height=height; this .hobby= function (){ return 'watching movies' ; } } var boy= new Person( 'keith' ,180); var girl= new Person( 'rascal' ,153); console.log(boy.name); //'keith' console.log(girl.name); //'rascal' console.log(boy.hobby===girl.hobby); //false |
上面代码中,一个构造函数Person生成了两个对象实例boy和girl,并且有两个属性和一个方法。但是,它们的hobby方法是不一样的。也就是说,每当你使用new来调用构造函数放回一个对象实例的时候,都会创建一个hobby方法。这既没有必要,又浪费资源,因为所有hobby方法都是童颜的行为,完全可以被两个对象实例共享。
所以,构造函数的缺点就是:同一个构造函数的对象实例之间无法共享属性或方法。
3.prototype属性的作用
为了解决构造函数的对象实例之间无法共享属性的缺点,js提供了prototype属性。
js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象,只有null除外,它没有自己的原型对象。
原型对象上的所有属性和方法,都会被对象实例所共享。
通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的prototype属性。每一个构造函数都有一个prototype属性,这个属性就是对象实例的原型对象。
1
2
3
4
5
6
7
8
9
10
11
12
|
function Person(name,height){ this .name=name; this .height=height; } Person.prototype.hobby= function (){ return 'watching movies' ; } var boy= new Person( 'keith' ,180); var girl= new Person( 'rascal' ,153); console.log(boy.name); //'keith' console.log(girl.name); //'rascal' console.log(boy.hobby===girl.hobby); //true |
上面代码中,如果将hobby方法放在原型对象上,那么两个实例对象都共享着同一个方法。我希望大家都能理解的是,对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。
原型对象的属性不是对象实例的属性。对象实例的属性是继承自构造函数定义的属性,因为构造函数内部有一个this关键字来指向将要生成的对象实例。对象实例的属性,其实就是构造函数内部定义的属性。只要修改原型对象上的属性和方法,变动就会立刻体现在所有对象实例上。
1
2
3
4
5
6
|
Person.prototype.hobby= function (){ return 'swimming' ; } console.log(boy.hobby===girl.hobby); //true console.log(boy.hobby()); //'swimming' console.log(girl.hobby()); //'swimming' |
上面代码中,当修改了原型对象的hobby方法之后,两个对象实例都发生了变化。这是因为对象实例其实是没有hobby方法,都是读取原型对象的hobby方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。如果实例对象自身有某个属性或方法,就不会去原型对象上查找。
1
2
3
4
5
|
boy.hobby= function (){ return 'play basketball' ; } console.log(boy.hobby()); //'play basketball' console.log(girl.hobby()); //'swimming' |
上面代码中,boy对象实例的hobby方法修改时,就不会在继承原型对象上的hobby方法了。不过girl仍然会继承原型对象的方法。
总结一下:
a:原型对象的作用,就是定义所有对象实例所共享的属性和方法。
b:prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。
4.原型链(prototype chains)
对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型对象本身对于对象实例来说也是对象,它也有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。所有一切的对象的原型顶端,都是Object.prototype,即Object构造函数的prototype属性指向的那个对象。
当然,Object.prototype对象也有自己的原型对象,那就是没有任何属性和方法的null对象,而null对象没有自己的原型。
1 console.log(Object.getPrototypeOf(Object.prototype)); //null
2 console.log(Person.prototype.isPrototypeOf(boy)) //true
原型链(prototype chain)的特点有:
a:读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
b:如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。
c:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
看概念可能比较晦涩,我们来看一个例子。但是理解了概念真的很重要。
1
2
3
4
|
var arr=[1,2,3]; console.log(arr.length); //3 console.log(arr.valueOf()) //[1,2,3] console.log(arr.join( '|' )) //1|2|3 |
上面代码中,定了一个数组arr,数组里面有三个元素。我们并没有给数组添加任何属性和方法,可是却在调用length,join(),valueOf()时,却不会报错。
length属性是继承自Array.prototype的,属于原型对象上的一个属性。join方法也是继承自Array.prototype的,属于原型对象上的一个方法。这两个方法是所有数组所共享的。当实例对象上没有这个length属性时,就会去原型对象查找。
valueOf方法是继承自Object.prototype的。首先,arr数组是没有valueOf方法的,所以就到原型对象Array.prototype查找。然后,发现Array.prototype对象上没有valueOf方法。最后,再到它的原型对象Object.prototype查找。
来看看Array.prototype对象和Object.prototype对象分别有什么属性和方法。
1
2
3
4
|
console.log(Object.getOwnPropertyNames(Array.prototype)) //["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push", "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf", "forEach", "map", "filter", "reduce", "reduceRight", "some", "every", "find", "findIndex", "copyWithin", "fill", "entries", "keys", "values", "includes", "constructor", "$set", "$remove"] console.log(Object.getOwnPropertyNames(Object.prototype)) // ["toSource", "toString", "toLocaleString", "valueOf", "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor"] |
我相信,大家看到这里,对prototype还是似懂非懂的。这很正常,毕竟是js中比较重要又比较抽象的概念,不可能那么快就掌握,再啃多几篇,说不定掌握其精髓。在某乎上,有一个活生生的实例,可能也是大家会遇到的问题。可以看看 js构造函数和原型对象。
5.constructor属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
1
2
|
function A(){}; console.log(A.prototype.constructor===A) //true |
要注意的是,prototype是构造函数的属性,而constructor则是构造函数的prototype属性所指向的那个对象,也就是原型对象的属性。注意不要混淆。
1
2
|
console.log(A.hasOwnProperty( 'prototype' )); //true console.log(A.prototype.hasOwnProperty( 'constructor' )); //true |
由于constructor属性是定义在原型(prototype)对象上面,意味着可以被所有实例对象继承。
1
2
3
4
|
function A(){}; var a= new A(); console.log(a.constructor); //A() console.log(a.constructor===A.prototype.constructor); //true |
上面代码中,a是构造函数A的实例对象,但是a自身没有contructor属性,该属性其实是读取原型链上面的
A.prototype.constructor属性。
5.1:constructor属性的作用
a:分辨原型对象到底属于哪个构造函数
1
2
3
4
|
function A(){}; var a= new A(); console.log(a.constructor===A) //true console.log(a.constructor===Array) //false |
上面代码表示,使用constructor属性,确定实例对象a的构造函数是A,而不是Array。
b:从实例新建另一个实例
1
2
3
4
|
function A() {}; var a = new A(); var b = new a.constructor(); console.log(b instanceof A); //true |
上面代码中,a是构造函数A的实例对象,可以从a.constructor间接调用构造函数。
c:调用自身的构造函数成为可能
1
2
3
|
A.prototype.hello = function () { return new this .constructor(); } |
d:提供了一种从构造函数继承另外一种构造函数的模式
1
2
3
4
5
|
function Father() {} function Son() { Son.height.constructor.call( this ); } Son.height = new Father(); |
上面代码中,Father和Son都是构造函数,在Son内部的this上调用Father,就会形成Son继承Father的效果。
e:由于constructor属性是一种原型对象和构造函数的关系,所以在修改原型对象的时候,一定要注意constructor的指向问题。
解决方法有两种,要么将constructor属性指向原来的构造函数,要么只在原型对象上添加属性和方法,避免instanceof失真。
6.instanceof运算符
instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。
1
2
3
|
function A() {}; var a = new A(); console.log(a instanceof A); //true |
因为instanceof对整个原型链上的对象都有效,所以同一个实例对象,可能会对多个构造函数都返回true。
1
2
3
4
|
function A() {}; var a = new A(); console.log(a instanceof A); //true console.log(a instanceof Object); //true |
注意,instanceof对象只能用于复杂数据类型(数组,对象等),不能用于简单数据类型(布尔值,数字,字符串等)。
1
2
3
4
5
6
7
8
|
var x = [1]; var o = {}; var b = true ; var c = 'string' ; console.log(x instanceof Array); //true console.log(o instanceof Object); //true console.log(b instanceof Boolean); //false console.log(c instanceof String); //false |
此外,null和undefined都不是对象,所以instanceof 总是返回false。
1
|
console.log( null instanceof Object); //false console.log(undefined instanceof Object); //false |
利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题。
1
2
3
4
5
6
7
|
function Keith(name,height) { if (! this instanceof Keith) { return new Keith(name,height); } this .name = name; this .height = height; } |
上面代码中,使用了instanceof运算符来判断函数体内的this关键字是否指向构造函数Keith的实例,如果不是,就表明忘记加new命令,此时构造函数会返回一个对象实例,避免出现意想不到的结果。
因为限于篇幅的原因,暂时介绍到这里。
我会在下次的分享中谈谈原型(prototype)对象的一些原生方法,如Object.getPrototypeOf(),Object.setPrototypeOf()等,并且介绍获取原生对象方法的比较。
以上所述是小编给大家介绍的详解Javascript中prototype属性(推荐)的相关知识,希望对大家有所帮助。
js中的prototype原型解析的更多相关文章
- 论js中的prototype
今天在阅读代码时,碰到了prototype //判断是否是数组function isArray(obj) { return Object.prototype.toString.call(obj) == ...
- JScript中的prototype(原型)属性研究
今天看到同事使用js中的Prototype,感觉很是新鲜.由此想深入学习一下prototype(英['prəʊtətaɪp] 美['protə'taɪp]n. 原型:标准,模范),在学习prototy ...
- JS中对于prototype的理解
JS中的prototype是JS中比较难理解的一个部分 本文基于下面几个知识点: 1 原型法设计模式 在.Net中可以使用clone()来实现原型法 原型法的主要思想是,现在有1个类A,我想要创建一个 ...
- 帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)
作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞 ...
- JS中的prototype (转载)
JS中的prototype JS中的phototype是JS中比较难理解的一个部分 本文基于下面几个知识点: 1 原型法设计模式 在.Net中可以使用clone()来实现原型法 原型法的主要思想是 ...
- JS中的prototype、__proto__与constructor属性
作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞 ...
- JS中的prototype、__proto__与constructor
1.前言 作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关 ...
- JS中的prototype、__proto__与constructor(图解)
作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞 ...
- [JavaScript] JS中对Base64的解析
JS中对Base64的解析 <script type="text/javascript"> /** * UTF16和UTF8转换对照表 * U+00000000 – U ...
随机推荐
- GDOI2018 Day1 题目总结
T1:农场 题意:有一个长为 $n$ 的序列 $a$,要求将其分成尽可能多的部分,使得每一部分的 $a_i$ 的和相等.求最多能分成的部分数. $30\%:1\le n\le 1000$ $80\%: ...
- js实现数字键盘
效果图: 1.引入jquery.js文件 2.css样式 <style type="text/css"> #numberkeyboard { border: 1px s ...
- php 访问错误日志
/usr/local/php/var/log/php-fpm.log」—————————
- sqlserver运行脚本数据出现 内存不足的解决办法
但是当数据库导出脚本很大,用Microsoft SQL Server Management Studio执行脚本时,往往会遇到“内存不足”的提示. 解决办法: 用微软自带的sqlcmd工具,可以导入执 ...
- 用vue前后端分离项目开发记录
一:软件安装 1.1 检测node 是否安装 1.2 安装淘宝镜像 cnpm 1.3 安装vue-cli 1.4 检查是否安装vue-cli脚手架成功 1.5安装webpack 模块管理工具 二:创建 ...
- RESTful记录-RESTful服务
按照REST架构,一个RESTful Web服务不应该继续服务器的客户端的状态.这种限制被称为无状态.它负责客户以它的上下文传递给服务器,然后服务器可以存储这样的上下文,以处理客户端的进一步请求.例如 ...
- 设计模式之Mixin模式
介绍 mixin模式就是一些提供能够被一个或者一组子类简单继承功能的类,意在重用其功能.在面向对象的语言中,我们会通过接口继承的方式来实现功能的复用.但是在javascript中,我们没办法通过接口继 ...
- p 最多两行 多的显示省略号
-webkit-line-clamp: 2 -webkit-box-orient: vertical; }
- Sublime Text 之运行 js 方法[2015-5-6更新mac下执行js]
昨天说完<Sublime Text 2 绿化与汉化 [Windows篇]>,今天我们来说说怎么用st直接运行 js 吧.群里的小伙伴一直对我的 ST 能直接运行js感到非常好奇,今天我就公 ...
- 【51Nod】1519 拆方块 贪心+递推
[题目]1519 拆方块 [题意]给定n个正整数,\(A_i\)表示第i堆叠了\(A_i\)个石子.每轮操作将至少有一面裸露的石子消除,问几轮所有石子均被消除.\(n \leq 10^5\). [算法 ...