详解Javascript中prototype属性(推荐)


Posted in Javascript onSeptember 03, 2016

在典型的面向对象的语言中,如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操作符来调用构造函数,并返回对象实例。

看一个最简单的一个例子。

function Person(){
 this.name = 'keith';
} 
 var boy = new Person();
console.log(boy.name); //'keith'

2.构造函数的缺点

所有的实例对象都可以继承构造函数中的属性和方法。但是,同一个对象实例之间,无法共享属性。

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属性,这个属性就是对象实例的原型对象。

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关键字来指向将要生成的对象实例。对象实例的属性,其实就是构造函数内部定义的属性。只要修改原型对象上的属性和方法,变动就会立刻体现在所有对象实例上。

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方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。如果实例对象自身有某个属性或方法,就不会去原型对象上查找。

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:一级级向上在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。

看概念可能比较晦涩,我们来看一个例子。但是理解了概念真的很重要。

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对象分别有什么属性和方法。

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对象所在的构造函数。

function A(){};
 console.log(A.prototype.constructor===A) //true

要注意的是,prototype是构造函数的属性,而constructor则是构造函数的prototype属性所指向的那个对象,也就是原型对象的属性。注意不要混淆。

console.log(A.hasOwnProperty('prototype')); //true
 console.log(A.prototype.hasOwnProperty('constructor')); //true

由于constructor属性是定义在原型(prototype)对象上面,意味着可以被所有实例对象继承。

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:分辨原型对象到底属于哪个构造函数

function A(){};
 var a=new A(); 
 console.log(a.constructor===A) //true
 console.log(a.constructor===Array) //false

上面代码表示,使用constructor属性,确定实例对象a的构造函数是A,而不是Array。

b:从实例新建另一个实例

function A() {};
 var a = new A();
var b = new a.constructor();
console.log(b instanceof A); //true

上面代码中,a是构造函数A的实例对象,可以从a.constructor间接调用构造函数。

c:调用自身的构造函数成为可能

A.prototype.hello = function() {
 return new this.constructor();
 }

d:提供了一种从构造函数继承另外一种构造函数的模式

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运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。

function A() {};
var a = new A();
console.log(a instanceof A); //true

因为instanceof对整个原型链上的对象都有效,所以同一个实例对象,可能会对多个构造函数都返回true。

function A() {};
 var a = new A(); 
console.log(a instanceof A); //true
console.log(a instanceof Object); //true

 注意,instanceof对象只能用于复杂数据类型(数组,对象等),不能用于简单数据类型(布尔值,数字,字符串等)。

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。

console.log(null instanceof Object); //false console.log(undefined instanceof Object); //false

利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题。

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属性(推荐)的相关知识,希望对大家有所帮助。

Javascript 相关文章推荐
永不消失的title提示代码
Feb 15 Javascript
Jquery 动态添加按钮实现代码
May 06 Javascript
jQuery EasyUI 的EasyLoader功能介绍
Sep 12 Javascript
Mac地址验证的javascript代码
Nov 09 Javascript
Lab.js初次使用笔记
Feb 28 Javascript
DOM操作一些常用的属性汇总
Mar 13 Javascript
灵活的理解JavaScript中的this指向
Feb 25 Javascript
vue+webpack实现异步组件加载的方法
Feb 03 Javascript
详解React+Koa实现服务端渲染(SSR)
May 23 Javascript
浅谈webpack 四个核心概念之Entry
Jun 12 Javascript
编写v-for循环的技巧汇总
Dec 01 Javascript
微信小程序实现可拖动悬浮图标(包括按钮角标的实现)
Dec 29 Javascript
jQuery基本选择器之标签名选择器
Sep 03 #Javascript
基于JS实现回到页面顶部的五种写法(从实现到增强)
Sep 03 #Javascript
jQuery dataTables与jQuery UI 对话框dialog的使用教程
Sep 02 #Javascript
AngularJs  unit-testing(单元测试)详解
Sep 02 #Javascript
AngularJs Managing Service Dependencies详解
Sep 02 #Javascript
AngularJs Injecting Services Into Controllers详解
Sep 02 #Javascript
AngularJs  Creating Services详解及示例代码
Sep 02 #Javascript
You might like
咖啡的植物学知识
2021/03/03 咖啡文化
php导入导出excel实例
2013/10/25 PHP
PHP中让curl支持sock5的代码实例
2015/01/21 PHP
php根据生日计算年龄的方法
2015/07/13 PHP
PHP学习笔记之php文件操作
2016/06/03 PHP
TP5框架页面跳转样式操作示例
2020/04/05 PHP
在Z-Blog中运行代码[html][/html](纯JS版)
2007/03/25 Javascript
javascript基本语法分析说明
2008/06/15 Javascript
ext checkboxgroup 回填数据解决
2009/08/21 Javascript
跟着JQuery API学Jquery 之二 属性
2010/04/09 Javascript
jquery根据name属性查找的小例子
2013/11/21 Javascript
实例解析js中try、catch、finally的执行规则
2017/02/24 Javascript
Vuejs实现带样式的单文件组件新方法
2017/05/02 Javascript
详解Vue webapp项目通过HBulider打包原生APP(vue+webpack+HBulider)
2019/02/02 Javascript
微信小程序实现元素渐入渐出动画效果封装方法
2019/05/18 Javascript
vue resource发送请求的几种方式
2019/09/30 Javascript
[01:01:31]2018DOTA2亚洲邀请赛3月29日小组赛B组 Mineski VS paiN
2018/03/30 DOTA
python学习之第三方包安装方法(两种方法)
2015/07/30 Python
基于Python中capitalize()与title()的区别详解
2017/12/09 Python
Opencv实现抠图背景图替换功能
2019/05/21 Python
python框架django项目部署相关知识详解
2019/11/04 Python
Python使用psutil获取进程信息的例子
2019/12/17 Python
Python实现病毒仿真器的方法示例(附demo)
2020/02/19 Python
基于python实现ROC曲线绘制广场解析
2020/06/28 Python
python ssh 执行shell命令的示例
2020/09/29 Python
资深生产主管自我评价
2013/09/22 职场文书
思想政治教育专业个人求职信范文
2013/12/20 职场文书
会计专业应届生自荐信
2014/02/07 职场文书
普通党员四风问题对照检查材料
2014/09/27 职场文书
2014年幼儿园教研工作总结
2014/12/04 职场文书
党员民主生活会材料
2014/12/15 职场文书
六一领导慰问欢迎词
2015/01/26 职场文书
2015年农村党员公开承诺事项
2015/04/28 职场文书
诚信考试主题班会
2015/08/17 职场文书
房屋转让协议书(标准范本)
2016/03/21 职场文书
简单总结SpringMVC拦截器的使用方法
2021/06/28 Java/Android