内容简介:由于JavaScript缺乏类,于是它用构造函数和原型对象来给对象带来与类相似的功能。构造函数就是使用new创建对象时调用的函数,与普通函数没区别,为了表现上有区别,约定函数名第一个字母大写,平时已经使用的了但不留意的构造函数:Object、Array、Function使用构建函数的好处是,你如果想要创建多个相同的对象,这时候就可以创建一个构造函数及引用类型;因为用同一个构造函数创建的对象具有相同的属性和方法。 你可以使用instanceof操作符 获取对象的类型,但不建议使用instanceof 来检查构
由于JavaScript缺乏类,于是它用构造函数和原型对象来给对象带来与类相似的功能。
1、构造函数:
构造函数就是使用new创建对象时调用的函数,与普通函数没区别,为了表现上有区别,约定函数名第一个字母大写,平时已经使用的了但不留意的构造函数:Object、Array、Function
function Person(){ //函数体 } var person1 = new Person(); var person2 = new Person(); 复制代码
使用构建函数的好处是,你如果想要创建多个相同的对象,这时候就可以创建一个构造函数及引用类型;因为用同一个构造函数创建的对象具有相同的属性和方法。 你可以使用instanceof操作符 获取对象的类型,但不建议使用instanceof 来检查构造函数的属性,因为构造函数的属性可以被覆盖,结果并不准确
console.log(person1 instanceof Person);//true console.log(person2 instanceof Person);//true 复制代码
你可以使用构造函数属性来检查一个对象的类型。每个对象在创建时都自动拥有一个构造属性,其中包含了一个指向构造函数的引用。那些通过对象字面量形式或Object构造函数创建出来的泛用对象,其构造函数属性指向Object;那些通过自定义构造函数创建出来的对象,其构造函数属性指向创建它的构造函数。
console.log(person1.constructor === Person);//true console.log(person1.constructor === Person);//true var sayNm = { name:'David' } var o = new Object(); console.log(sayNm.constructor === Object);//true console.log(o.constructor === Object);//true 复制代码
使用构造函数的目的就是轻松创建许多拥有相同属性和方法的对象;此时,你只要在构造函数内简单的给this添加任何你想要的属性即可;举例:
function Person(name){ this.name = name; this.sayName = function(){ console.log('这货是:'+this.name); } } var person1 = new Person('Jack'); var person2 = new Person('Luck'); person1.sayName();//这货是:Jack person2.sayName();//这货是:Luck 复制代码
注意可以在构造函数中显式调用return,如果返回值是一个对象,它回替代新建的对象实例返回;如果返回的是一个原始类型,它会被忽略,新创建的对象实例会被返回。
当Person不是被new调用时,构造函数中的this对象就是全局this对象。没有new,Person只不过是一个没有返回语句的普通函数。对this.name的赋值实际上是创建了一个全局name来保存传递给Person的参数。
接上例: var person1 = Person('Jack'); console.log(person1 instanceof Person);//false console.log(typeof person1);//undefined console.log(name);//Jack 复制代码
但是如果是在严格模式下'use strict';就直接抛出一个错
Cannot set property 'name' of undefined 复制代码
2、原型对象:
可以把原型对象看作是对象的基类。几乎所有的函数(除了一些内建函数)都有一个名为prototype的属性,该属性是一个原型对象用来创建新的对象实例。所有创建的对象实例共享该原型对象,且这些对象实例可以访问原型对象的属性。例如hasOwnProperty()方法被定义在泛用对象Object的原型对象中,但却可以被任何对象当作自己的属性来访问,举例:
var Mich = { name:'Mich', age:12 } console.log('name' in Mich);//true console.log(Mich.hasOwnProperty('age'));//true console.log('hasOwnProperty' in Mich);//true console.log(Mich.hasOwnProperty('hasOwnProperty'));//false console.log(Object.prototype.hasOwnProperty('hasOwnProperty'));//true 复制代码
可以用如下方法来鉴别一个属性是否是原型属性
function hasPrototypeProperty(object,name){ return name in object && !object.hasOwnProperty(name) } 复制代码
2.1[[Prototype]]属性
该属性是一个指向该实例使用的原型对象的指针。当你用new创建一个新对象时,构造函数的原想对象就会被赋给该对象的Prototype属性。
可以使用对象的Object.getPrototpeOf()方法读取Prototype的属性值。举例:
var Mich = { name:'Mich', age:12 } console.log(Object.getPrototypeOf(Mich) === Object.prototype);//true 复制代码
如上例,任何一个泛用对象,其[[Prototype]]属性始终指向Object.prototype。 isProtoyrpeOf()方法可以来检查某个对象是否是另一个对象的原型对象。
var object = {} console.log(Object.prototype.isPrototypeOf(object))//true 复制代码
解释:object是一个泛用对象,它的原型是Object.prototype,所以本例中isPrototypeOf()方法返回true
2.2在构造函数中使用原型对象
function Person(name){ this.name = name; } Person.prototype.sayName = function(){ console.log(this.name); } var person1 = new Person('Jack'); console.log(person1 instanceof Person);//true console.log(person1.constructor === Person);//true console.log(person1.constructor === Object);//false 复制代码
虽然可以在原型对象上添加属性和方法,这时候很多开发者都会使用一种看着更简洁的方式:直接用一个对象字面形式代替原型对象,如下:
function Person(name){ this.name = name; } Person.prototype = { sayName:function(){ console.log(this.name); }, toString:function(){ return '[Person'+this.name+']' } } var person1 = new Person('Jack'); console.log(person1 instanceof Person);//true console.log(person1.constructor === Person);//false console.log(person1.constructor === Object);//true 复制代码
这样写法,如果添加多个方法或属性时候,不需要多次写Person.prototype。但是有一个副作用,可看上面代码console的返回结果对比。
解释:使用对象字面形式改写原型对象 改变了构造函数的属性,因此它现在指向Object而不是Person。这是因为原型对象具有一个constructor属性,这是其他对象实例所没有的。当一个函数被创建时,它的prototype属性也被创建,且该原型对象的constructor属性指向该函数。当使用对象字面形式改写原型对象Person.prototype时,其constructor属性将被重置为泛用对象Object。为了避免这一点,需要在改写为对象字面形式时手动重置其constructor属性,如下:
function Person(name){ this.name = name; } Person.prototype = { constructor:Person, sayName:function(){ console.log(this.name); }, toString:function(){ return '[Person'+this.name+']' } } var person1 = new Person('Nicholas'); var person1 = new Person('Greg'); console.log(person1 instanceof Person);//true console.log(person1.constructor === Person);//true console.log(person1.constructor === Object);//false 复制代码
构造函数、原型对象和对象实例之间的关系最有趣的一个方面也许就是 对象实例 和 构造函数 之间没有直接联系,不过 对象实例 和 原型对象 以及 原型对象 和 构造函数 之间都有直接联系。
2.3改变原型对象记住,[[Prototype]]属性只是包含了一个指向原型对象的指针,任何对原型对象的改变都会立即反映到所有引用它的对象实例上。
function Person(name){ this.name = name; } Person.prototype = { constructor:Person, sayName:function(){ console.log(this.name); }, toString:function(){ return '[Person'+this.name+']' } } var person1 = new Person('Jack'); var person1 = new Person('Greg'); console.log('sayHi' in person1);//false 首先确定 sayHi 方法 不再person1及其原型对象上 //增加一个方法 Person.prototype.sayHi = function(){ console.log('Hi'); } person1.sayHi();//Hi person1.sayHi();//Hi 复制代码
2.4内建对象的原型对象
所有内建对象都有构造函数,因此也都有原型对象给你去改变。但不建议开发者内置对象原型去改变,因为不可预见因素太多、不利于开发。 例如,在数组原型对象上添加一个方法,直接修改Array.prototype
Array.prototype.sum = function(){ return this.reduce(function(previous,current){ return previous+current }) } var numbers = [1,2,3,4,5,6] var result = numbers.sum(); console.log(result);//21 复制代码
这个例子在Array.prototype上创建了一个名为sum()的方法,该方法对数组所有元素求和并返回。numbers数组通过原型对象自动拥有了这个方法,在sum()内部,this指向数组的对象实例numbers,于是该方法也可以自由使用数组他发方法,比如reduce()。 拓展一下,字符串、数字、布尔类型都有内建的原始封装类型来帮助我们像使用普通对象一样使用它们。如果改变原始封装类型的原型对象,就可以给这些原始值添加别的方法,如下例对字符串原型进行改变:
String.prototype.capitalize = function(){ return this.charAt(0).toUpperCase()+this.substring(1); } var mes = "hello world"; console.log(mes.capitalize()//"Hello world" 复制代码
总结
构造函数就算是用new操作符调用的普通函数,约定开头字母大写,可以用instanceof操作符或直接访问constructor属性来鉴别对象是被哪个构造函数创建的。
每个函数都有prototype属性,它定义了该构造函数创建的所有对象共享的属性。通常,共享的方法和原始值属性被定义在原型对象里,而其他属性都定义在构造函数里。constructor属性实际上被定义在原型对象里共所有对象实例共享。 原型对象被保存在对象实例内部的[[Prototype]]属性中。这个属性是一个引用而不是一个副本。由于JavaScript查找属性机制,你对原型对象的修改都立刻出现在所有对象实列中。 内建对象也有可以被修改的原型对象,但不建议在生产环境中使用。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 《恰如其分的软件架构》读后小结(一)
- 深入理解es6读后总结--块级作用域绑定
- 开始起飞-golang编码技巧分享--Dave Cheney博客读后整理
- 闲谈IPv6-源IP地址的选择(RFC3484读后感)
- 《Service Mesh 实战—基于 Linkerd 和 Kubernetes 的微服务实践》读后感
- 内存管理设计精要
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。