JavaScript面向对象 构造函数和原型链javascript
首先,咱们要先了解一下类的概念,JavaScript 自己是一种面向对象的语言,它所涉及的元素根据其属性的不一样都依附于某一个特定的类。咱们所常见的类包括:数组变量(Array)、逻辑变量(Boolean)、日期变量(Date)、结构变量(Function)、数值变量(Number)、对象变量(Object)、字符串变量(String) 等,而相关的类的方法,也是程序员常常用到的(在这里要区分一下类的注意和属性发方法),例如数组的push方法、日期的get系列方法、字符串的split方法等等,
可是在实际的编程过程当中不知道有没有感受到现有方法的不足?prototype 方法应运而生!下面,将经过实例由浅入深讲解 prototype 的具体使用方法:html
一、最简单的例子,了解 prototype:java
(1) Number.add(num):做用,数字相加程序员
实现方法:Number.prototype.add = function(num){return(this+num);}
试验:alert((3).add(15)) -> 显示 18正则表达式
(2) Boolean.rev(): 做用,布尔变量取反编程
实现方法:Boolean.prototype.rev = function(){return(!this);}
试验:alert((true).rev()) -> 显示 false数组
是否是很简单?这一节仅仅是告诉读者又这么一种方法,这种方法是这样运用的。ide
二、已有方法的实现和加强,初识 prototype:函数
(1) Array.push(new_element)
做用:在数组末尾加入一个新的元素
实现方法:post
让咱们进一步来加强他,让他能够一次增长多个元素!
实现方法:
应该不难看懂吧?以此类推,你能够考虑一下如何经过加强 Array.pop 来实现删除任意位置,任意多个元素(具体代码就再也不细说了)
(2) String.length
做用:这其实是 String 类的一个属性,可是因为 JavaScript 将全角、半角均视为是一个字符,在一些实际运用中可能会形成必定的问题,如今咱们经过 prototype 来弥补这部不足。
实现方法:
试验:alert("EaseWe空间Spaces".cnLength()) -> 显示 16
这里用到了一些正则表达式的方法和全角字符的编码原理,因为属于另两个比较大的类别,本文不加说明,请参考相关材料。
三、新功能的实现,深刻 prototype:在实际编程中所用到的确定不仅是已有方法的加强,更多的实行的功能的要求,下面我就举两个用 prototype 解决实际问题的例子:
(1) String.left()
问题:用过 vb 的应该都知道left函数,从字符串左边取 n 个字符,可是不足是将全角、半角均视为是一个字符,形成在中英文混排的版面中不能截取等长的字符串
做用:从字符串左边截取 n 个字符,并支持全角半角字符的区分
实现方法:
试验:
alert("EaseWe空间Spaces".left(8)) -> 显示 EaseWe空间
alert("EaseWe空间Spaces".left(8,true)) -> 显示 EaseWe空
本方法用到了上面所提到的String.Tlength()方法,自定义方法之间也能组合出一些不错的新方法呀!
(2) Date.DayDiff()
做用:计算出两个日期型变量的间隔时间(年、月、日、周)
实现方法:
试验:alert((new Date()).DayDiff((new Date(2002,0,1)))) -> 显示 329
alert((new Date()).DayDiff((new Date(2002,0,1)),"m")) -> 显示 10
固然,也能够进一步扩充,得出响应的小时、分钟,甚至是秒。
(3) Number.fact()
做用:某一数字的阶乘
实现方法:
试验:alert((4).fact()) -> 显示 24
这个方法主要是说明了递归的方法在 prototype 方法中也是可行的!
JavaScript可以实现的面向对象的特征有:
·公有属性(public field)
·公有方法(public Method)
·私有属性(private field)
·私有方法(private field)
·方法重载(method overload)
·构造函数(constructor)
·事件(event)
·单一继承(single inherit)
·子类重写父类的属性或方法(override)
·静态属性或方法(static member)
例子一(JavaScript中容许添加行为的类型):能够在类型上使用proptotype来为类型添加行为。这些行为只能在类型的实例上体现。 JS中容许的类型有Array, Boolean, Date, Enumerator, Error, Function, Number, Object, RegExp, String
例子二(prototype使用的限制):在实例上不能使用prototype,不然发生编译错误
例子三(如何定义类型上的静态成员):能够为类型定义“静态”的属性和方法,直接在类型上调用便可
例子五():这个例子演示了一般的在JavaScript中定义一个类型的方法
例子六(JavaScript中容许添加行为的类型):能够在外部使用prototype为自定义的类型添加属性和方法。
例子八():能够在对象上改变属性。(这个是确定的)也能够在对象上改变方法。(和广泛的面向对象的概念不一样)
例子九():能够在对象上增长属性或方法
例子十(如何让一个类型继承于另外一个类型):这个例子说明了一个类型如何从另外一个类型继承。
例子十一(如何在子类中从新定义父类的成员):这个例子说明了子类如何重写父类的属性或方法。
1.构造函数的简单介绍
2.构造函数的缺点
3.prototype属性的做用
4.原型链(prototype chain)
5.constructor属性
5.1:constructor属性的做用
6.instanceof运算符
1.构造函数的简单介绍
在个人一篇Javascript 中构造函数与new命令的密切关系文章中,详细了介绍了构造函数的概念和特色,new命令的原理和用法等,若是对于构造函数不熟悉的同窗,能够前往细细品味。如下作一个简单的回顾。
所谓构造函数,就是提供了一个生成对象的模板并描述对象的基本结构的函数。一个构造函数,能够生成多个对象,每一个对象都有相同的结构。总的来讲,构造函数就是对象的模板,对象就是构造函数的实例。
构造函数的特色有:
a:构造函数的函数名首字母必须大写。
b:内部使用this对象,来指向将要生成的对象实例。
c:使用new操做符来调用构造函数,并返回对象实例。
看一个最简单的一个例子。
1 function Person(){ 2 this.name = 'keith'; 3 } 4 5 var boy = new Person(); 6 console.log(boy.name); //'keith'
2.构造函数的缺点
全部的实例对象均可以继承构造函数中的属性和方法。可是,同一个对象实例之间,没法共享属性。
1 function Person(name,height){ 2 this.name=name; 3 this.height=height; 4 this.hobby=function(){ 5 return 'watching movies'; 6 } 7 } 8 9 var boy=new Person('keith',180); 10 var girl=new Person('rascal',153); 11 12 console.log(boy.name); //'keith' 13 console.log(girl.name); //'rascal' 14 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 function Person(name,height){ 2 this.name=name; 3 this.height=height; 4 } 5 6 Person.prototype.hobby=function(){ 7 return 'watching movies'; 8 } 9 10 var boy=new Person('keith',180); 11 var girl=new Person('rascal',153); 12 13 console.log(boy.name); //'keith' 14 console.log(girl.name); //'rascal' 15 console.log(boy.hobby===girl.hobby); //true
上面代码中,若是将hobby方法放在原型对象上,那么两个实例对象都共享着同一个方法。我但愿你们都能理解的是,对于构造函数来讲,prototype是做为构造函数的属性;对于对象实例来讲,prototype是对象实例的原型对象。因此prototype便是属性,又是对象。
原型对象的属性不是对象实例的属性。对象实例的属性是继承自构造函数定义的属性,由于构造函数内部有一个this关键字来指向将要生成的对象实例。对象实例的属性,其实就是构造函数内部定义的属性。只要修改原型对象上的属性和方法,变更就会马上体如今全部对象实例上。
1 Person.prototype.hobby=function(){ 2 return 'swimming'; 3 } 4 console.log(boy.hobby===girl.hobby); //true 5 console.log(boy.hobby()); //'swimming' 6 console.log(girl.hobby()); //'swimming'
上面代码中,当修改了原型对象的hobby方法以后,两个对象实例都发生了变化。这是由于对象实例实际上是没有hobby方法,都是读取原型对象的hobby方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。若是实例对象自身有某个属性或方法,就不会去原型对象上查找。
1 boy.hobby=function(){ 2 return 'play basketball'; 3 } 4 console.log(boy.hobby()); //'play basketball' 5 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 var arr=[1,2,3]; 2 console.log(arr.length); //3 3 console.log(arr.valueOf()) //[1,2,3] 4 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 console.log(Object.getOwnPropertyNames(Array.prototype)) 2 //["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"] 3 console.log(Object.getOwnPropertyNames(Object.prototype)) 4 // ["toSource", "toString", "toLocaleString", "valueOf", "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor"]
我相信,你们看到这里,对prototype仍是似懂非懂的。这很正常,毕竟是js中比较重要又比较抽象的概念,不可能那么快就掌握,再啃多几篇,说不定掌握其精髓。在某乎上,有一个活生生的实例,可能也是你们会遇到的问题。能够看看 js构造函数和原型对象。
5.constructor属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。
1 function A(){}; 2 console.log(A.prototype.constructor===A) //true
要注意的是,prototype是构造函数的属性,而constructor则是构造函数的prototype属性所指向的那个对象,也就是原型对象的属性。注意不要混淆。
1 console.log(A.hasOwnProperty('prototype')); //true 2 console.log(A.prototype.hasOwnProperty('constructor')); //true
因为constructor
属性是定义在原型(prototype)
对象上面,意味着能够被全部实例对象继承。
1 function A(){}; 2 var a=new A(); 3 4 console.log(a.constructor); //A() 5 console.log(a.constructor===A.prototype.constructor);//true
上面代码中,a
是构造函数A
的实例对象,可是a
自身没有contructor
属性,该属性实际上是读取原型链上面的A.prototype.constructor
属性。
5.1:constructor属性的做用
a:分辨原型对象到底属于哪一个构造函数
1 function A(){}; 2 var a=new A(); 3 4 console.log(a.constructor===A) //true 5 console.log(a.constructor===Array) //false
上面代码表示,使用constructor
属性,肯定实例对象a
的构造函数是A
,而不是Array
。
b:从实例新建另外一个实例
1 function A() {}; 2 var a = new A(); 3 var b = new a.constructor(); 4 console.log(b instanceof A); //true
上面代码中,a
是构造函数A
的实例对象,能够从a.constructor
间接调用构造函数。
c:调用自身的构造函数成为可能
1 A.prototype.hello = function() { 2 return new this.constructor(); 3 }
d:提供了一种从构造函数继承另一种构造函数的模式
1 function Father() {} 2 3 function Son() { 4 Son.height.constructor.call(this); 5 } 6 7 Son.height = new Father();
上面代码中,Father
和Son
都是构造函数,在Son
内部的this
上调用Father
,就会造成Son
继承Father
的效果。
e:因为constructor属性是一种原型对象和构造函数的关系,因此在修改原型对象的时候,必定要注意constructor的指向问题。
解决方法有两种,要么将constructor属性指向原来的构造函数,要么只在原型对象上添加属性和方法,避免instanceof失真。
6.instanceof运算符
instanceof
运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。
1 function A() {}; 2 var a = new A(); 3 console.log(a instanceof A); //true
由于instanceof对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true。
1 function A() {}; 2 var a = new A(); 3 console.log(a instanceof A); //true 4 console.log(a instanceof Object); //true
注意,instanceof对象只能用于复杂数据类型(数组,对象等),不能用于简单数据类型(布尔值,数字,字符串等)。
1 var x = [1]; 2 var o = {}; 3 var b = true; 4 var c = 'string'; 5 console.log(x instanceof Array); //true 6 console.log(o instanceof Object); //true 7 console.log(b instanceof Boolean); //false 8 console.log(c instanceof String); //false
此外,null和undefined都不是对象,因此instanceof 老是返回false。
1 console.log(null instanceof Object); //false 2 console.log(undefined instanceof Object); //false
利用instanceof
运算符,还能够巧妙地解决,调用构造函数时,忘了加new
命令的问题。
1 function Keith(name,height) { 2 if (! this instanceof Keith) { 3 return new Keith(name,height); 4 } 5 this.name = name; 6 this.height = height; 7 }
上面代码中,使用了instanceof运算符来判断函数体内的this关键字是否指向构造函数Keith的实例,若是不是,就代表忘记加new命令,此时构造函数会返回一个对象实例,避免出现意想不到的结果。
由于限于篇幅的缘由,暂时介绍到这里。
我会在下次的分享中谈谈原型(prototype)对象的一些原生方法,如Object.getPrototypeOf(),Object.setPrototypeOf()等,而且介绍获取原生对象方法的比较。