js基础之对象

271 阅读6分钟

创建对象

Object 构造函数或对象字面量都可以用来创建但个对象,缺点是:使用同一个接口创建很多对象,会产生大量的重复代码。

1. 工厂模式

这种模式抽象了创建具体对象的过程。

function createPerson(name, age, job) {
    var o = new Object()
    o.name = name
    o.age = age
    o.job = job
    o.sayName = function () {
        alert(this.name)
    }
    return o
}

工厂模式虽然可以创建多个相似的对象,但是却没有解决对象识别的问题(即怎么知道一个对象的类型)。

2. 构造函数模式

构造函数可用来创建特定类型的对象。

function Person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
    this.sayName = function () {
        alert(this.name)
    }
}

与工厂模式的不同:

  • 没有显式的创建对象;
  • 直接将属性和方法赋给了 this 对象;
  • 没有 return 语句。

构造函数始终都应该以一个大写字母开头。 创建 Person 的新实例,必须使用 new 操作符。调用构造函数其实会经历以下4个步骤:

(1)创建一个新对象;

(2)将构造函数的作用域赋值给新对象(因此this指向了这个新对象);

(3)执行构造函数中的代码;

(4)返回新对象。

构造函数胜过工厂函数之处:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。

带来的问题:每个方法都要在实例上重新创建一遍。

解决方案:可以把函数定义转移到构造函数外部。

function Person(name, age, job) {
    this.name = name
    this.age = age
    this.job = job
    this.sayName = sayName
}
function sayName () {
    alert(this.name)
}

带来的新问题:在全局作用域中定义的函数实际上只能被某个对象调用,让全局作用域有点名不副实,如果对象有多个方法,那就需要定义多个全局函数,那我们自定义引用类型就没有封装性可言了。

原型模式可以解决这些问题。

3. 原型模式

我们创建的每个函数都有一个 prototype 属性,这个属性是个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。 好处:让所有对象实例共享它所包含的属性和方法。

function Person() {
}
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.sayName = function() {
    alert(this.name)
}

var person1 = new Person();
person1.sayname(); // 'Nicholas'

var person2 = new Person();
person2.sayname(); // 'Nicholas'

alert(person1.sayname === person1.sayname) // true
  1. 只要创建了新函数,就会为该函数创建一个 prototype 属性;
  2. prototype 属性指向函数的原型对象;
  3. 所有的原型对象都会自动获得一个 constructor 属性,这个属性包含一个指向 prototype 属性所在函数的指针;
  4. 调用构造函数创建一个新实例后,该实例的内部包含一个指针,指向构造函数的原型对象([[Prototype]]),脚本中没有标准的方式访问 [[Prototype]],但 Firefox、 Safari 和 Chrome 在每个对象上都只吃一个属性 _proto_ 指向构造函数的原型对象,与构造函数没有直接关系。

Person.prototype.constructor 指向 Person, person1._proto_ 指向 Person.prototype

上面的代码每添加一个属性和方法都要敲一遍 Person.prototype , 为了减少不必要的输入,我们可以使用对象字面量来重写原型对象:

function Person(){
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    sayName : function () {
        alert(this.name);
    }
};

Person.prototype 设置为了一个以对象字面量形式创建的新对象,但是 constructor 属性不再指向 Person。每创建一个函数,都会创建它的 Prototype 对象,这个对象会自动获得 constructor 属性。我们这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性,即 Object 构造函数,而不再指向 Person 函数。

const friend = new Person()
friend instance of Object // true
friend instance of Person // true
friend.constructor == Object // true
friend.constructor == Person // false

isPrototypeOf()

可以通过 isPrototypeOf() 方法来确定某个对象的原型是否是某个实例的 [[Prototype]]。

Person.prototype.isPrototypeOf(person1) // true
Person.prototype.isPrototypeOf(person2) // true

Object.getPrototypeOf()

ES5 新增此方法,这个方法返回 [[Prototype]] 的值。

Object.getPrototypeOf(person1) === Person.prototype // true

hasOwnProperty()-从Object继承来的

检测给定属性是否存在于对象实例中。

person1.hasOwnProperty('name') // false

person1.name = 'Greg'
person1.hasOwnProperty('name') // true

person2.hasOwnProperty('name') // false

delete person1.name
person1.hasOwnProperty('name') // false

in 操作符

只要通过对象能够访问到的属性,就返回 true。

判断属性是否存在于原型中

function hasPropertyProperty(object, name) {
    return !object.hasOwnProperty(name) && (name in object);
}

for-in 循环

返回的是所有能够通过对象访问的、可枚举的属性。

Object.keys()

这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
const keys = Object.keys(Person.prototype);
alert(keys);       // "name,age,job,sayName"

Object.getOwnPropertyNames()

获得对象的所有实例属性,无论它是否可枚举。

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);    //"constructor,name,age,job,sayName"

结果中包含了不可枚举的 constructor 属性。Object.keys()和 Object.getOwnPropertyNames()方法都可以用来替代 for-in 循环。

4. 组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。

另外,这种混合模式还支持向构造函数传递参数。

function Person(name, age, job){
    this.name = name; 3 this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}

5. 动态原型模式

把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。

换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name, age, job){
    //属性
    this.name = name;
    this.age = age;
    this.job = job;
    //方法
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
        }; 
    }
}

只在 sayName() 方法不存在的情况下,才会将它添加到原型中。

这段代码只会在初次调用构造函数时才会执行。

此后,原型已经完成初始化,不需要再做什么修改了。不过这里对原型所做的修改,能够立即在所有实例中得到反映。

6. 寄生构造函数模式

创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但 从表面上看,这个函数又很像是典型的构造函数。

function Person(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };
    return o; 
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实 是一模一样的。

7. 稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。

稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup 程序)改动时使用。

稳妥构造函数类似寄生构造函数的模式,但有两点不同: 一是新创建对象的实例方法不引用 this; 二是不使用 new 操作符调用构造函数。

function Person(name, age, job){
    //创建要返回的对象
    var o = new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.sayName = function(){
        alert(name);
    };
    //返回对象
     return o; 
}

var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

继承