在JavaScript中,对象其实就是一组键值对的组合。
1、字面量对象(Object.Literals)
这是JS中创建对象的最简单、最常见的方法之一,只需要在花括号内定义属性及其值,如下所示:
let student = {name: 'Ross', rollno: 1};
// 或用Object构造
let person = new Object();
console.log(person); // {}
person.name = 'lisa';
person.age = 21;
这种方式会创建大量重复代码。
2、构造函数创建(Constructor Functions)
在构造函数之前,先来说说工厂模式:为了解决对象字面量在创建多个相似对象时,会产生大量重复代码的问题,于是有了工厂模式。
function createPerson (name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function () {
console.log(this.name);
};
return o;
}
var person1 = new createPerson('zhang3', 29);
var person2 = new createPerson('li4', 2);
但是工厂模式也有不足,无法解决对象识别的问题,创建的所有实例都是 Object 类型,不知道是具体谁谁谁的实例。
构造函数创建的方式更多是用来在JS中实现继承、多态、封装等特性。构造函数是通过this为对象添加属性的。
比如,我们需要创建具有相同属性/结构集的多个实例,this关键字是指一个对象,该对象是执行当前代码位的任何对象。将new 关键字与函数名一起使用,将创建一个空对象,并且在该函数内部使用的this关键字将保留对该对象的引用。
function Animal (name) {
this.name = name;
this.say = function () {
console.log(this.name)
}
}
let cat = new Animal('Tom'); // Tom
let dog = new Animal('John'); // John
let lion = Animal('amy'); // undefined
//因为这里没有加new,所有属性都附加到了Windows对象。无法判断它是谁的实例,只能判断它是对象。
构造函数也有缺陷,就是其中的每个方法比如say(),在每次实例化时都会自动重新创建一遍,产生不同的作用域链,因此即使是同名函数也是不相等的,这样会造成资源浪费。比如:
let a1 = new Animal('zz')
let a2 = new Animal('ww');
console.log(a1.say === a2.say); // false
所以就有了原型模式,使用原型模式的好处就是可以让所有对象实例共享它所包含的属性和方法。
function Person () {
}
Person.prototype.name = 'zz';
Person.prototype.sayName = function () {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // zz
var person2 = new Person();
person2.sayName(); // zz
console.log(person1.sayName === person2.sayName); // true
这里将sayName()方法和所有的属性都直接添加到了Person的prototype属性中,构造函数就成了空函数,但是也能调用构造函数创建新对象,新对象的属性和方法是所有实例共享的,也就是person1和person2访问都是同一组属性和同一个sayName()函数。但是原型模式也有缺点,当其中包含引用类型值属性时会出现问题,如下:
function Person(){
}
Person.prototype = {
constructor: Person,
name: 'zzx',
age: '22',
job: 'Programmer',
friends: ['wc', 'rt'],
sayName: function(){
console.log(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('lol');
console.log(person1.friends); //[ 'wc', 'rt', 'lol' ]
console.log(person2.friends); //[ 'wc', 'rt', 'lol' ]
由于数组存在于Person.prototype中,当向数组中添加了一个字符串时,所有的实例都会共享这个数组。
组合使用构造函数和原型模式:是目前最常见的创建自定义类型对象的方式。构造函数用于定义实例属性,而原型模式用于定义方法和共享的属性。通过构造函数传递参数,这样每个实例都能拥有自己的属性值,同时实例还能共享函数的引用,最大限度节省了内存空间。如下:
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructore: Person,
sayName: function () {
console.log(this.name)
}
}
let person1 = new Person('king', 11);
let person2 = new Person('kkkk', 12);
console.log(person1.name); // king
console.log(person2.name); // kkkk
console.log(person1.sayName); // king
// 改变一个实例的属性值
person2.name = 'jing';
// 不影响另一个实例的属性值
console.log(person1.name); // king
console.log(person2.name); // jing
动态原型模式: 就是将原型对象放在构造函数内部,通过变量进行控制,只在第一次生成实例的时候进行原型的设置。相当于懒汉模式,只在生成实例时设置原型对象,其功能与构造函数和原型模式额混合模式是相同而。这是只有在sayName()不存在的情况下,才会将它添加到原型中。
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if (typeof this.sayName != "function") {
Person.prototype.sayName = function () {
console.log(this.name);
};
}
}
var person1 = new Person('zzx', 22, 'Programmer');
person1.sayName(); // zzx
3、Object.create()
ES5的新方法,我们可以使用Object.create()语法创建一个新对象,新对象的原型就是调用create方法时传入的第一个参数,第二个参数为添加的可枚举属性(自身属性)。如下:
// 例1
var a = {a: 1};
var b = Object.create(a); // b 的原型就是 a
console.log(b); // {}
b.__proto__ === a; // true
// 例2
// 把ross对象的属性挂到ross对象的原型上
var ross = Object.create(Object.prototype, {
name: {
value: 'ross',
enumerable: true,
writable: true,
configurable: true
},
rollno: {
value: 1,
enumerable: true,
writable: true,
configurable: true
}
});
对于每个属性,我们都将值、可枚举、可写和可配置的属性设置为true,使用对象文字或构造函数时,这自动为我们完成。
优点: 支持当前所有非微软版本或者 IE9 以上版本的浏览器。允许一次性地直接设置 proto 属性,以便浏览器能更好地优化对象。同时允许通过 Object.create(null)来创建一个没有原型的对象。
缺点:不支持 IE8 以下的版本;这个慢对象初始化在使用第二个参数的时候有可能成为一个性能黑洞,因为每个对象的描述符属性都有自己的描述对象。当以对象的格式处理成百上千的对象描述的时候,可能会造成严重的性能问题。
4、class创建
class关键字是ES6新引入的一个特性,它其实是基于原型和原型链实现的一个语法糖。
class Animal {
constructor(name) {
this.name = name
}
}
let cat = new Animal('Tom');
Class 类中的constructor方法就相当于ES5中的构造函数,其实类中的所有方法都定义在了prototype上,prototype对象的constructor属性也指向class类本身,被所有实例共享。不同的是,class类只能通过new操作符调用,不能像ES5 的构造函数一样,当成普通函数调用。
constructor方法是类的默认方法,所有的类都有constructor方法,如果constructor方法没有被显式定义,js会自动添加一个空的。