创建对象
对象是无序属性的集合,其属性可以包含基本值,对象或者函数,即由若干个“键值对”(key-value)构成。
创建对象有3种方法:1.对象字面量;2.构造函数;3.对象继承。
Object对象
JavaScript原生提供一个Object对象,所有其他对象都继承自这个对象。
Object本身也是一个构造函数,可以直接通过它来生成新对象。
通过new Object()的写法创建新对象,与字面量的写法o = {}是等价的。工厂模式
工厂模式就是我们提供一个模子,然后通过这个模子复制出我们需要的对象。
function createPerson(name, age) {
// 1.声明一个中间对象,该对象就是工厂模式的模子
var o = new Object();
// 2.添加属性和方法
o.name = name;
o.age = age;
o.getName = function() {
return this.name;
}
// 3.返回中间对象
return o;
}
// 创建对象实例
var p1 = createPerson('Leo', 26);
var p2 = createPerson('Tom', 20);
console.log(p1 instanceof Object); // true
console.log(p1 instanceof createPerson); // false
工厂模式缺点:无法识别对象实例的类型,因为构造函数都指向Object。
- 构造函数
JavaScript中使用构造函数作为对象的模板,专门用来生成对象。
构造函数特点:- 生成对象的时候,必需用new命令,调用构造函数。
- 函数体内部使用了this关键字,代表了所要生成的对象实例。
new 命令
new命令的作用就是执行构造函数,并返回一个实例对象。
new命令本身就可以指向构造函数,所有new后面的构造函数可以带括号,也可以不带。
如果没有使用new命令,构造函数就变成了普通函数,并不会生成实例对象。
new 命令的原理 - 创建一个新对象,作为将要返回的实例对象。
- 将这个新对象的原型,指向构造函数的prototype属性。
- 执行构造函数,将构造函数内部的this,指向这个新对象。
- 返回新对象。
在构造函数内部,this指的是这个新建的空对象,所有针对this的操作,都会发生在这个空对象上。
如果构造函数内部有return语句,而且后面跟一个对象,new命令会返回这个对象,否则返回this对象。
如果普通函数(内部没有this关键字)使用new命令,则会返回一个空对象。
var A = function () {
this.x = 1;
return {x: 2};
}
console.log( (new A()).x ); // 2
new 命令简化的内部流程:
// 创建构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = function () {
return this.name;
}
}
Person.prototype.getAge = function () {
return this.age;
}
// 模拟new命令
function New(func) {
// 将类数组arguments对象转换为数组
var args = [].slice.call(arguments);
// 取出构造函数
var func = args.shift();
// 创建新对象,该对象的原型指向继承构造函数的原型,继承构造函数的属性和方法
var obj = Object.create(func.prototype);
// result为构造函数执行的结果,通过apply将构造函数内的this指向修改为实例对象
var result = func.apply(obj, args);
// 当构造函数指定了返回的对象时,New的执行结果就返回该对象,否则返回实例对象
return (typeof result === 'object' && result != null) ? result : obj;
}
var p1 = New(Person, 'Tom', 20);
console.log(p1.getName()); // Tom
console.log(p1.getAge()); // 20
console.log(p1 instanceof Person); // true
// 其他的一些特殊处理,将var p1=New(Person,'Tom',20)等效于var p1 = new Person('Tom',20);
- 构造函数模式
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = function () {
console.log(this.name);
};
}
var p1 = new Person('Leo', 26);
var p2 = new Person('Tom', 20);
console.log(p1.getName === p2.getName); // false
构造函数模式的缺点:
每次创建实例都要创建一遍方法,这既没有必要,又浪费系统资源。
原型
每个函数都有一个prototype属性,该属性指向一个对象,这个对象就是原型对象。
构造函数的prototype属性指向实例对象的原型对象。
每个原型对象都有一个constructor属性,该属性默认指回原型对象的构造函数。
每个实例对象都可以通过__proto__
访问该实例对象的原型对象。
原型对象的作用就是定义所有实例对象共享的属性和方法。
实例对象可以看作从原型对象衍生出来的子对象,原型对象上的所有属性和方法,都能被实例对象共享。
实例对象本身没有某个属性或方法时,会到原型对象去寻找该属性或方法。
原型对象上的变动会立刻体现在所有实例对象上,原型上的属性和方法被所有实例共享。组合模式
组合使用构造函数和原型:
构造函数中通过this绑定的属性与方法称为私有变量与方法,它们被当前被某一个实例对象所独有。
原型中的属性和方法称之为共有属性与方法,它们可以被所有的实例对象共享。
// 私有属性和方法
function Person(name, age) {
this.name = name;
this.age = age;
this.getAge = function () {
console.log(this.age);
}
}
// 共有属性和方法
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
var p1 = new Person('Tom', 10);
var p2 = new Person('Leo', 26);
console.log(p1.getName === p2.getName); // true
组合模式的缺点:构造函数和原型没有封装在一起。
- 动态原型模式
封装在一个构造函数中,在构造函数中初始化原型。
function Person(name, age) {
this.name = name;
this.age = age;
// 第一调用构造函数初始化原型
if (typeof this.getName !== "function") {
Person.prototype.getName = function () {
console.log(this.name);
}
}
}
var person1 = new Person('Leo', 26); // 初次调用构造函数时,初始化原型
var person2 = new Person('Tom', 20); // 原型已初始化
person1.getName(); // Leo
person2.getName(); // Tom
注意:使用动态原型模式时,不能使用对象字面量重写原型,这样做会切断所有实例对象与新原型的联系。
重写原型对象会切断现有原型与任何之前已存在的实例对象直接的联系。
function Person(name, age) {
this.name = name;
this.age = age;
if (typeof this.getName !== "function") {
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
}
}
var p1 = new Person('Leo', 26);
var p2 = new Person('Tom', 20);
p2.getName(); // Tom
p1.getName(); // 报错,p1.getName不存在
console.log(Person.prototype === p1.__proto__); // false
console.log(Person.prototype === p2.__proto__); // true
console.log(p1.__proto__ === p2.__proto__); // false
// 第一次调用构造函数时,重写原型,添加原型方法getName
// 而此时实例p1的__proto__仍然指向以前的原型对,此原型没有getName方法,报错
// 第二次调用构造函数的时候,实例p2已继承原型方法getName
解决办法:
function Person(name, age) {
this.name = name;
this.age = age;
if (typeof this.getName !== "function") {
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
return new Person(name, age); //返回一个实例
}
}
var p1 = new Person('Leo', 26); // 第一次调用时执行if语句返回一个具有新原型的对象实例
var p2 = new Person('Tom', 20);
p2.getName(); // Tom
p1.getName(); // Leo
- 寄生构造函数模式
除了使用new操作符,并把封装的函数称为构造函数之外,这个模式和工厂模式一样。
function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.getName = function () {
console.log(this.name);
};
return o;
}
var p1 = new Person('Leo', 26); // 使用new操作符
console.log(p1 instanceof Person) // false 该模式不能识别构造函数
console.log(p1 instanceof Object) // true
这个模式可以在特殊情况下来为对象创建构造函数。
假如我们需要创建一个具有额外方法的特殊数组,由于不能直接修改Array构造函数,可以使用该模式。
function SpecialArray() {
// 创建新数组
var arr = new Array();
// 给数组添加值
arr.push.apply(arr, arguments);
// 给数组添加额外的方法
arr.toPipedString = function () {
return this.join('|');
}
// 返回该数组
return arr;
}
var prople = new SpecialArray('Leo', 'Tom', 'Bob');
console.log(prople.toPipedString()); // Leo|Tom|Bob
- 稳妥构造函数模式
稳妥对象,是指没有公共属性,而且其方法也不引用this的对象。
与寄生构造函数模式有两点不同:
新创建的实例方法不引用this;不使用new操作符调用构造函数。
稳妥构造函数模式也跟工厂模式一样,无法识别对象所属类型。
function person(name, age){
var o = new Object();
o.getName = function(){
console.log(name);
};
o.getAge = function(){
console.log(age);
};
return o;
}
var p1 = person('Leo', 26);
p1.getName(); // Leo
p1.getAge(); // 26
console.log(p1.name); // undefined,只能通过getName()方法访问name值
《JavaScript高级程序设计》
《JavaScript 标准参考教程》