function Person(name, age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
var person1 = new Person("小明", 29, "工程师")
var person2 = new Person("小红", 20, "测试工程师")
new 操作叫符,经历了4个过程。
- 创建一个新的对象
- 将构造函数的作用域赋值给新的对象(因此this 就指向了这个对象)
- 指向这个构造函数中的代码
- 返回了新的对象 person1 和 person2 分别保存着一个 Person 的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向 Person,如下所示:
console.log(person1.constructor === Person) // true
console.log(person2.constructor === Person) // true
对象的 constructor 属性最初是用来标识对象类型的。但是,提到检测对象类型的话,还是instanceof 更可靠些。我们在这个例子中创建的对象,即是 Object 实例,也是 Person 实例。因为所有的对象都继承自 Object。
以这种方式定义的构造函数是定义在 Global 对象(在浏览中是window 对象)中的。 下面是在浏览器的控制台做的测试。
1、将构造函数当做函数
构造函数与其他函数的唯一区别就是调用它们的方式不同。任何函数,只要通过new操作符来调用,那它就可以作为构造函数,如果不通过new操作符来调用,就跟普通函数一样。例如: 定义的Person 可以通过下面这种方式调用。
// 当做构造函数使用
var person1 = new Person("小明", 29, "工程师");
person1.sayName(); // "小明"
// 作为普通函数使用
Person("小红", 20, "测试工程师");
window.sayName(); // "小红"
// 在另一个对象的作用域中使用
var o = new Object();
Person.call(0, "小红", 20, "测试工程师");
o.sayName(); // "小红"
不适用new操作符调用Person(),属性和方法都会被添加到window对象了。 这是因为,在全局作用域中调用一个函数是,this 的指向总是 Global(浏览器是window对象)。因此,在调用完函数之后,可以通过window对象来调用sayName()方法。
2、构造函数的问题
构造函数的主要问题,就是每个方法都要在每个实例上创建一遍。在当前的例子中, person1 和person2 都有一个名为 sayName() 的方法,但是两个方法不是同一个Function 的实例。ECMAScript 中的函数是对象,因此每定义一个函数,就是实例化了一个对象。 从逻辑的角度讲,此时的构造函数也可以这样定义:
function Person(name, age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("console.log(this.name)") // 与声明函数在逻辑上是等价的
}
因此,不同实例上的同名函数是不相等的。然而,创建两个完成同样任务的Function 实例的确是没有必要的。况且有this 对象在,根本不用再执行代码之前就把函数绑定在特定的对象上。因此,可以通过把函数定义转义到构造函数外部来解决这个问题。
function Person(name, age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName
}
function sayName() {
console.log(this.name);
}
我们将sayName函数转移到构造函数外面,相当于是一个全局的函数。 person1 和 person2 共享了全局作用域中的同一个函数。这样确实解决了两个函数做同一件事情的问题。但是,全局作用域中的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。更让人无法接受的是,如果我们需要定义很多的方法,那么就有很多个全局函数,我们自定义的引用类型就丝毫没有封装性可言了。
原型模式
function Person() {
Person.prototype.name = "小明";
Person.prototype.age = 29;
Person.prototype.job = "工程师";
Person.prototype.sayName = function() {
console.log(this.name)
}
}
var person1 = new Person();
person1.sayName(); // 小明
var person2 = new Person();
person2.sayName(); // 小明
console.log(person1.sayName === person2.sayName); //true
1、理解原型对象
创建一个新的函数,就会为这个函数创建一个 prototype 的属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在的函数的指针。比如 Person.prototype.constructor 指向 Person。通过这个构造函数,我们还可以继续为原型对象添加其他的属性和方法。
创建了自定义构造函数后,其原型对象默认只会取得constructor属性。至于其他的方法,都是从Object继承来的。调用构造函数创建一个新实例后,该实例包含一个[[Prototype]]。真正的实现是在每个对象上支持一个属性__proto__。这个连接存在于实例于构造函数的原型对象之间,而不是实例于构造函数之间。下面是红宝书的图。
虽然我们的实例不包含属性和方法,但我们却可以调用person1.sayName()。这是通过查找对象的属性的过程来实现的。
虽然所有的实现都无法访问[[Prototype]],可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系。
console.log(Person.prototype.isPrototypeOf(person1)); // true
console.log(Person.prototype.isPrototypeOf(person2)); // true
console.log(Object.getPrototypeOf(person1) === Person.prototype) //true
console.log(Object.getPrototypeOf(person1).name) //小明
当我们读取某个属性的时候,会执行一次搜索。搜索从对象实例本身开始。如果在该实例中找到了具有给定名字的属性,则返回。没有的话,则继续搜索指针指向的原型对象。
虽然可以通过对象实例访问保存在原型中的值,但是不能通过对象实例重写原型中的值,如果存在于原型实例中一个同属性名称,我们就在该实例中创建该属性,该属性会屏蔽原型中的值。
2、原型与in操作符
有两种方式使用in操作符:单独使用和在for-in循环中使用。在单独使用时,in操作符会通过对象是否能够访问属性时返回true,无论属性在对象中还是原型中。hasOwnPrototype只有在属性存在于对象中才会返回true。hasPrototypeProperty()只有属性在原型中才会返回true
3、更简单的原型写法
function Person {
}
Person.prototype = {
name: "小明",
age: 29,
job: "工程师",
sayName: function() {
console.log(this.name);
}
}
注意:,这里的constructor 属性不在指向Person了。我们这里的写法,本质上是完全重写的prototype对象,因此constructor属性也变成了新的对象的constructor属性(指向了Object构造函数),不在执行Person 构造函数。可以特意将constructor设置为特定的值。
function Person {
}
Person.prototype = {
constructor: Person,
name: "小明",
age: 29,
job: "工程师",
sayName: function() {
console.log(this.name);
}
}
注意:,我们使用这种方式设置的constructor属性会导致它的[[Enumerable]]特性被设置为true,默认情况下,原生的constructor属性是不可枚举的。想要设置不可以枚举的话:
function Person {
}
Person.prototype = {
name: "小明",
age: 29,
job: "工程师",
sayName: function() {
console.log(this.name);
}
}
//重设构造函数,只使用与ECMAScript5 兼容的浏览器
Object.defineProperty(Person.pertotype, "constructor", {
enumerablea: false,
value: Person
})