构造函数主要用于初始化新对象。按照惯例,构造函数名第一个字母都要大写。数组
构造函数有别于其它函数在于它使用new操做符来调用生成一个实例对象。换句话说,若是一个函数使用new操做符来调用,则将其称为构造函数。安全
function User(name, age) { this.name = name; this.age = age; } // 调用 var jenemy = new User('jenemy', 25); jenemy.name; // jenemy
与函数调用和方法调用的不一样点在于,构造函数调用是将一个全新的对象做为this变量的值,并隐式返回这个新对象做为调用的结果。函数
typeof User; // function typeof jenemy; // object
在JavaScript中每个对象都有一个constructor属性指向建立这个对象的函数,函数一样也是一个对象。所以有this
Person.constructor === Function; // true jenemy.constructor === User; // true
若是调用构造函数时忘记使用new操做符,那么构造函数将做为一个普通函数调用,此时this将会被绑定到全局对象中。firefox
var xiaolu = User('xiaolu', 25); xiaolu.name; // Uncaught TypeError: Cannot read property 'name' of undefined window.name; // xiaolu window.age; // 25
更加健壮的方式是不管如何调用都按构造函数来工做。prototype
function User(name, age) { if (!(this instanceof User)) { return new User(name, age); } this.name = name; this.age = age; } var jenemy = new User('jenemy', 25); var xiaolu = User('xiaolu', 25); jenemy instanceof User; // true xiaolu instanceof User; // true
这种模式虽然可以解决问题,但带来了另一个问题:执行了二次User()
函数的调用,所以代价有点高。此外,若是参数是可变的,这种方式也很难适用。设计
这里可使用ES5的Object.create()
来解决上述问题。指针
function User(name, age) { var self = this instanceof User ? this : Object.create(User.prototype); self.name = name; self.age = age; return self; }
Object.create()
方法是建立一个拥有指定原型和若干个指定属性的对象。这里须要注意的是它的第二个参数和Object.defineProperties()
的第二个参数是同样的。code
因为Object.create()
只有在ES5才是有效的,所以须要对Object.create()
进行Polyfill对象
if (typeof Object.create != 'function') { // 使用匿名函数封装全部私有变量 Object.create = (function() { function Temp() {}; // 更加安全的引用Object.prototype.hasOwnProperty var hasOwn = Object.prototype.hasOwnProperty; return function(O) { // 若是 O 不是 Object 或者 null,抛出一个 TypeError 异常 if (typeof O != 'object') { throw TypeError('Object prototype may only be an Object or null'); } Temp.prototype = O; var obj = new Temp(); Temp.prototype = null; // 释放临时对象资源 // 若是存在参数 Properties,而不是undefined,那么就把自身属性添加到 obj 上 if (arguments.length > 1) { var Properties = Object(arguments[1]); for (var prop in Properties) { if (hasOwn.call(Properties, prop)) { obj[prop] = Properties[prop]; } } } return obj; }; }) (); }
上述polyfill实现了Object.create()
的全部功能,其实这里只须要每个参数就能够了,所以能够简化一下
if (Object.create != 'function') { Object.create = function(O) { function Temp() {}; if (typeof O != 'object') { throw TypeError('Object prototype may only be an Object or null'); } Temp.prototype = O; return new Temp(); }; }
在JavaScript中prototype属性保存了引用类型全部实例方法的真正所在。拿数组操做来说,push()
方法其实是保存在prototype名下,只不过是经过其对象的实例访问罢了。
// 实例化一个数组对象 var person = new Array(); // 调用实例化方法push person.push('jenemy'); // 一样也能够直接调用Array.prototype.push方法。 // 同时注意将this指向当前person数组对象 Array.prototype.push.call(person, 'xiaolu'); person.length; // 2
不管何时,只要咱们建立了一个新函数,就会根据一组特定的规则为该函数建立一个prototype属性,这个属性指向函数的原型对象。
在上面介绍构造函数的时候提到过每一个对象都有一个constructor属性指向建立它的函数。所以全部原型对象都会默认得到一个constructor属性指向prototype属性所在函数的指针。而后,当调用构造函数实例化一个新对象后,该实例内部会有一个标准的指针 [[Prototype]] 指向构造函数的实例对象。虽然没有一个标准的方式去访问 [[Prototype]],但 firefox、Safari 和 Chrome在每一个对象上都支持一个属性__proto__
。
注意一点的是__proto__
实际上只存在于构造函数实例与构造函数原型之间,而不是存在于实例与构造函数之间。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name; } var jenemy = new Person('jenemy', 25); var xiaolu = new Person('xiaolu', 25); jenemy.name; // jenemy xiaolu.getName(); // xiaolu
为了便于理解各对象之间的关系,咱们将其图形化展现:
|------------------------1---------------------------| v | |-----------------| |---------------------| | | Person | ----->| Person.prototype | | |-----------------| | |---------------------| | | prototype |·|------------- | constructor |·|-- |-----------------| | |---------------------| | name | String | | | getName | Function | |-----------------| | |---------------------| | age | String | 2 |-----------------| | |---------------------------| | | |-----------------| | |-------------------| | | jenemy | | | xiaolu | | |-----------------| | |-------------------| | | [[Prototype]] |·|------------| | [[prototype]] |·|-| |-----------------| |-------------------| | name | 'jenemy' | | name | 'xiaolu' | |-----------------| |-------------------| | age | 25 | | age | 25 | |-----------------| |-------------------|
而后咱们再用代码来验证一下图形所展现的对象之间的关系
// 验证线路1 Person.prototype.constructor === Person; // true // 验证线路2 Person.prototype.isPrototypeOf(jenemy); // true Object.getPrototypeOf(xiaolu) === Person.prototype; // true jenemy.__proto__ === Person.prototype; // true // 因为jenemy是由new Person()后获得的实例化对象,所以有 jenemy.constructor === Person; // true
这里的Object.isPrototypeOf()
方法用于检查传入的对象是不是传入对象的原型。而Object.getPrototypeOf()
方法返回指定对象的原型(也就是该对象的内部属性[[Prototype]]的值)。
上面咱们有使用__proto__
来获取对象的原型,但并非全部的JavaScript环境都支持经过它来获取对象的原型,所以官方给出了一个标准解决方案就是使用Object.getPrototypeOf()
方法。另外须要注意的是,拥有null原型的对象没有这个特殊的__proto__
属性。
function Person(name, age) { this.name = name; this.age = age; } var jenemy = new Person('jenemy', 25); var empty = Object.create(null); '__proto__' in jenemy; // true '__proto__' in empty; // false Object.getPrototypeOf(empty); // null
因为Object.getPrototypeOf()
方法是ES5中提供的方法,对于那些没有提供ES5 API的环境,也能够利用__proto__
属性来实现Object.getPrototypeOf()
函数。
if (typeof Object.getPrototypeOf === 'undefined') { Object.getPrototypeOf = function(obj) { var t = typoef obj; if (!obj || (t!== 'object' && t!== 'function')) { throw new TypeError('not an object'); } return obj.__proto__; } }
在建立原型属性时,咱们一样可使用对象字面量语法:
function Person(name, age) { this.name = name; this.age = age; } Person.prototype = { getName: function() { return this.name; } } Person.prototype.constructor === Person; // false Person.proottype.constructor; // Object
而后,咱们发现这里Person.prototype.constructor
并无指向建立它的构造函数,而是指向了Object
,缘由在于咱们重写了Person.prototype
对象,致使Person.prototype
指向了Object
,面Object.prototype.constructor
原本就指向'Object'。解决办法是手机将Person.prototype.constructor
指向Person
。
Person.prototype = { constructor: Person, getName: function() { return this.name; } } Person.prototype.constructor === Person; // true
对于一个对象的属性遍历,最早想到的就是使用in
操做符在for-in
循环中使用。经过in
操做符能够访问实例中和原型中的可枚举的属性。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.prop1 = 1; var jenemy = new Person('jenemy', 25); jenemy.addr = 'shanghai'; 'name' in jenemy; // true 'prop1' in jenemy; // true 'addr' in jenemy; // true
另外一个就是使用ES5提供的Object.keys()
方法,它会返回一个由给定对象的全部可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in
循环遍历该对象时返回的顺序一致(二者的主要区别是for-in
会枚举原型链中的属性)。
Object.keys(jenemy); // ["name", "age", "addr"]
最后一个是使用ES5提供的Object.getOwnPropertyNames()
方法,它会返回对象全部的实例属性,包括可枚举的和不可枚举的属性。
Object.getOwnPropertyNames(jenemy); // ["0", "1", "2", "length"]
这里length
属性是Object对象的一个不可枚举的属性,所以会被输出。
在JavaScript中咱们能够任意修改对象的属性和方法,甚至能够修改内置原型方法。固然,咱们不是不建议修改内置对象方法和属性,这样会致使依赖该方法或者属性的其它调用者发生没法预期的结果。
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。也就是说,它会阻止咱们访问原型中的那个属性,但不会修改那个属性。固然,使用delete
操做符能够彻底删除实例属性,从而从新访问原型中的属性。
使用hasOwnProperty()
方法能够检测一个属性是存在于实例中,仍是存在于原型中。只有属性存在于实例中时才会返回true。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.getName = function() { return this.name; } var jenemy = new Person('jenemy', 25); jenemy.getName(); // jenemy jenemy.hasOwnProperty('getName'); // false // 自定义实例属性 jenemy.getName = function() { return '个人名字叫' + this.name + '我会屏蔽掉原型中的属性值。'; } jenemy.getName(); // 个人名字叫jenemy我会屏蔽掉原型中的属性值。 jenemy.hasOwnProperty('getName'); // true delete jenemy.getName; jenemy.getName(); // jenemy jenemy.hasOwnProperty('getName'); // false
-《JavaScript高级程序设计》(第3版)
-《JavaScript面向对象精要》
-《Effective JavaScript》