JavaScript面向对象的程序设计——“建立对象”的注重要点

建立对象

Object 组织函数或对象字面量都可以用来建立单个对象。但这个要领的瑕玷异常显著:统一个接口建立很可耐多对象会发生大批的反复代码。为了处理这个题目,人们最先运用工场形式的一种变体。

工场形式(摒弃,不引荐)

这个形式没有处理对象辨认的题目(即如何晓得一个对象的范例)。如:

详细的建立单个对象:

var person = {};
person.name = "Oliver";
person.age = 18;
person.sayName = function(){
    return this.Name;
};

改变成工场形式:

function createPerson(name,age){
    var obj = {};
    obj.name = name;
    obj.age = age;
    obj.sayName = function(){
        return this.name
    };
    return obj; //注重这里要返回obj 对象,如许才能把obj 对象传给createPerson 变量。
}

var newPerson = createPerson("Oliver",18);

组织函数形式

组织函数可以建立特定范例的对象。所以,可以建立自定义的组织函数,从而定义自定义对象范例的属性和要领。如:

function Person(name,age){ //注重大小写,组织函数应当把第一个字幕大写化
    this.name = name;
    this.age = age;
    this.sayName = function (){
        return this.name;
    };
}

var newPerson = new Person("Oliver",18);

document.write(newPerson.name); //Oliver
document.write(newPerson.age); //18
document.write(newPerson.sayName()); //Oliver

确切相称轻易

这里一定要记着,组织函数都应当以一个大写字母开首,用来辨别其他的函数

又如:

function Person(name,age){ //注重大小写,组织函数应当把第一个字幕大写化
    this.name = name;
    this.age = age;
    this.sayName = function (){
        return this.name;
    };
}

var person1 = new Person("Oliver",18);
var person2 = new Person("Troy",24);

document.write(person1.constructor == Person); //true
document.write(person2.constructor == Person); //true

这里的person1 和person2 离别保存着Person 的一个差别的实例。两个对象都有一个constructor(组织函数)属性,该属性指向Person。

在上面这个例子中,person1 和person2 等于Object 的实例,同时也是Person 的实例。可以经由过程instanceof 操作符来考证:

console.log((person1 instanceof Object) && (person2 instanceof Person)); //true

以这类体式格局建立组织函数是定义在Global 中的(window 对象)

将组织函数当作函数

任何函数,只需经由过程new 操作符来挪用,那它就可以坐位组织函数;而任何函数,假如不经由过程new 操作符来挪用,那它就跟平常函数没区分。如下面这个组织函数:

function Car(name,color,sound){
    this.name = name;
    this.color = color;
    this.sound = function(){
        return sound;
    };
    console.log(this.name + " " + this.color + " " + this.sound());
}

假如当作组织函数来运用:

var benz = new Car("C200","White","Boom Boom"); //C200 White Boom Boom

假如作为平常函数来挪用:

Car("Benz","White","Boom!"); //Benz White Boom!
console.log(window.name + window.color + window.sound()); //BenzWhiteBoom!

假如在另一个对象的作用域中挪用:

var cars = {};
Car.call(cars,"Benz","White","Boom Boom!");
document.write(cars.sound()); //Boom Boom!

组织函数的题目

题目是每一个要领都要在每一个实例是从新建立一遍。可用经由过程把内部的函数转移到外部来处理这些题目。如:

function Car(name,color){
    this.name = name;
    this.color = color;
    this.show = show;
}
function show(){
    console.log(this.name + this.color);
}
var benz = new Car("Benz","white");
benz.show(); //Benzwhite

但这个题目是完整没有了封装性可言。不过可以经由过程原型形式来处理。

原型形式

function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.sayName(); //Oliver
var person2 = new Person();
person2.sayName(); //Oliver;
console.log(person1.sayName == person2.sayName); //true

与组织函数差别的是,新对象的这些属性和要领是由一切实例同享的。这里两个新的person 接见的都是统一组属性和统一个sayName() 函数。

明白原型对象

以上面的Person 为例,Person 组织函数内里存在一个prototype 属性,这个属性指向原型对象Person Prototype,该Person Prototype 内里包括了constructor 属性,该属性又指向组织函数Person。组织函数的实例包括了一个[[Prototype]]的内部属性,该内部属性则指向Person Prototype。如:

function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.sayName(); //Oliver

var person2 = new Person();
person2.sayName(); //Oliver;

console.log(Person.prototype);
/*
age: 18
constructor: function Person() {}
name: "Oliver"
sayName: function () {
__proto__: Object
*/
console.log(Person.prototype.constructor);
//function Person() {}
console.log(Object.getPrototypeOf(person1));
/*
age: 18
constructor: function Person() {}
name: "Oliver"
sayName: function () {
__proto__: Object
*/

关于组织函数、原型属性以及实例之间的关联,拜见《js高等程序设计》一书中第6.2.3 章节。

两个要领:isPrototypeOf()Object.getProtytypeOf()(ECMAScript 5)。前者是用来肯定[[Prototype]];后者是用来返回[[Prototype]]值。如:

console.log(Person.prototype.isPrototypeOf(person1)); //true
console.log(Object.getPrototypeOf(person1).name); //Oliver

console.log(Object.getPrototypeOf(person1));
/*
age: 18
constructor: function Person() {}
name: "Oliver"
sayName: function () {
__proto__: Object
*/

为对象增加一个属性时,这个属性会屏障原型对象中的同名属性,然则并不会修正谁人属性。假如运用delete 删除这个属性,就可以从新接见原型中的属性。如:

function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.sayName(); //Oliver 原型中的Name

person1.name = "Troy";
person1.sayName(); //Troy 实例中的Name

delete person1.name;
person1.sayName(); //Oliver 原型中的Name

每次读取某个对象的某个属性,都邑从实例自身最先搜刮,假如没有找到给定名字的属性,则会在原型对象中再次搜刮。

要领hasOwnProperty()检测属性假如在对象实例中时,返回true。如:

console.log(person1.hasOwnProperty("age")); //false age属性来自于原型
console.log(person1.hasOwnProperty("name")); //true name属性来自于实例

原型与in 操作符

两种要领运用in 操作符:零丁运用和for-in 轮回中运用。

零丁运用时,in 返回true 申明该属性存在于实例或原型中。如:

function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.name = "Troy";
person1.sayName(); //Troy 实例中的Name

console.log("name" in person1); //true name属性在实例或原型中
console.log(person1.hasOwnProperty("name")); //true name属性在实例中
//上面两个就申明name属性一定在实例中

又如:


function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.name = "Troy";
person1.sayName(); //Troy 实例中的Name

var person2 = new Person();

console.log("name" in person1); //true name属性在实例或原型中
console.log(person1.hasOwnProperty("name")); //true name属性在实例中
//上面两个就申明name属性一定在实例中

console.log("name" in person2); //true
console.log(person2.hasOwnProperty("name")); //false
//上面两个申明name属性一定在原型中

自定义一个函数hasPrototypeProperty(object,name);即同时运用上面两个要领来肯定属性究竟是不是是存在于实例中。如:

function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.name = "Troy";
person1.sayName(); //Troy 实例中的Name

var person2 = new Person();

function hasPrototypeProperty(object,name){
    console.log((name in object) && !(object.hasOwnProperty(name)))
}

hasPrototypeProperty(person2,"name"); //true name属性是在原型中
hasPrototypeProperty(person1,"name"); //false name属性是在实例中

Object.defineProperty()要领定义的属性:

function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
Object.defineProperty(person1, "age", {
    value: 18
})

console.log(person1.hasOwnProperty("age")); //true age属性是实例属性

关于for-in、[[enumerable]]、defineProperty、hasOwnProperty 的例子:

var person1 = {
    age: 18
};
Object.defineProperty(person1, "name", {
    value: "Oliver",
    enumerable: true
})
for(var x in person1){
    console.log(x);
}
console.log(person1.hasOwnProperty("name")); //true

又如:

function Person(age){
    this.age = age;
}
var person1 = new Person(18);
Object.defineProperty(person1, "name", {
    value: "Oliver",
    enumerable: false
})
for(var x in person1){
    console.log(x); //用defineProperty 定义的name 属性是实例属性,这里不会罗列到
}
console.log(person1.hasOwnProperty("name")); //true

又如:

function Person(){};
Person.prototype.age = 18;
var person1 = new Person();
Object.defineProperty(person1, "name", {
    value: "Oliver",
    enumerable: false
})
for(x in person1){
    console.log(x); //这里依然不回罗列到自定义的name 实例属性
}

然则:

function Person(){};
Person.prototype.age = 18;
Person.prototype.name = "Oliver";
var person1 = new Person();
Object.defineProperty(person1, "name", {
    enumerable: false
})
for(x in person1){
    console.log(x); //这里则返回罗列到自定义的name 原型属性
}

原型属性的[[enumerable]]设置为false,挪用for-in 依然可以被罗列到。

别的,Object.keys()要领可以返回一切可罗列属性的字符串数组:

function Person(){};
Person.prototype.age = 18;
Person.prototype.name = "Oliver";

var person1 = new Person();
Object.defineProperty(person1, "sound", {
    value: "miao~",
    enumerable: true //可罗列
});
Object.defineProperty(person1, "sound2", {
    value: "wang~",
    enumerable: false //不可罗列
});

console.log(Object.keys(Person.prototype)); //["age", "name"]
console.log(Object.keys(person1)); //["sound"]

Object.getOwnPropertyName()要领,则可以返回不管能否罗列的一切实例属性:

function Person(){};
Person.prototype.age = 18;
Person.prototype.name = "Oliver";

var person1 = new Person();
Object.defineProperty(person1, "sound", {
    value: "miao~",
    enumerable: true //可罗列
});
Object.defineProperty(person1, "sound2", {
    value: "wang~",
    enumerable: false //不可罗列
});

console.log(Object.keys(Person.prototype)); //["age", "name"]
console.log(Object.keys(person1)); //["sound"]
console.log(Object.getOwnPropertyNames(Person.prototype)); //["constructor", "age", "name"]
console.log(Object.getOwnPropertyNames(person1)); //["sound","sound2"]

更简朴的原型语法

即字面量要领:


function Person(){};
Person.prototype = {
    name: "Oliver",
    age: 18,
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
console.log(Person.prototype.constructor); //不再指向Person()组织函数

function People(){};
People.prototype.name = "Troy";
People.prototype.age = 26;
People.prototype.sayName = function(){
    console.log(this.name);
};

var people1 = new People();
console.log(People.prototype.constructor); //这里则指向People()组织函数

上面第一种就是字面量要领。然则由此带来的题目是,他的原型对象中的constructor 属性将不再指向上个例子中的Person() 组织函数了。(实在我们本质上是重写了prototype对象)

假如constructor 值真的异常重要,则只须要把它设置回恰当的值就可以了:

function Person(){};
Person.prototype = {
    constructor: Person,
    name: "Oliver",
    age: 18,
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
console.log(Person.prototype.constructor); //从新指向Person()组织函数

function People(){};
People.prototype.name = "Troy";
People.prototype.age = 26;
People.prototype.sayName = function(){
    console.log(this.name);
};

var people1 = new People();
console.log(People.prototype.constructor); //这里则指向People()组织函数

但是用字面量的要领致使的题目依然没有结束,以上面这类体式格局重设constructor 属性会致使[[Enumerable]]特征被设置为true。因此在支撑ECMAScript 5 的js 引擎中可以用Object.defineProperty()要领把它修正为false:

function Person(){};
Person.prototype = {
    constructor: Person,
    name: "Oliver",
    age: 18,
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
console.log(Person.prototype.constructor);
for (var x in person1){
    console.log(x); //这里会涌现constructor,然则我们实际上不应当让他可以被罗列出
}

Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false
});

for (var x in person1){
    console.log(x); //这里就不会涌现constructor 了,然则这类要领只支撑ECMAScript 5的js 引擎
}

/*
[Log] function Person() {} (repetition.html, line 130)
[Log] constructor (repetition.html, line 132)
[Log] name (repetition.html, line 132)
[Log] age (repetition.html, line 132)
[Log] sayName (repetition.html, line 132)
[Log] name (repetition.html, line 140)
[Log] age (repetition.html, line 140)
[Log] sayName (repetition.html, line 140)
*/

原型的动态性

我们对原型对象所做的任何修正都能马上从实例上回响反映出来。由于实例与原型之间的链接只不过是一个指针而不是副本:

function Person(){};
var person = new Person(); //person在Person()组织函数修正之前建立的
Person.prototype.name = "Oliver";
console.log(person.name); //依然会涌现及时的变化

然则假如重写了prototype 则就差别了,由于实例的[[Prototype]]会指向原型对象,假如修正了本来的原型对象,则就是切断了组织函数与最初原型之间的联络:

function Person(){};
var person = new Person();

Person.prototype = { //这里重写了Person.prototype,属于新的Person.prototype
    constructor: Person,
    name: "Oliver"
}

console.log(person.name); //原型对象被修正了,指针依然指向旧的Person.prototype

从这里就可以很显著看出,Person.prototype={},实际上字面量要领就是重写了原型对象。假如是Person.prototype.name=”Oliver”,则并非重写而是修正,不会建立“新的原型对象。”

《js高等程序设计》一书中6.2.3 中的图6-3 很清晰的形貌了该道理

原生对象的原型

一切原生的援用范例(Object、Array、String等等)都在其组织函数的原型上定义了要领。同时,我们也可以给原生对象自定义要领:

var array = new Array();
Array.prototype.name = function(){
    console.log("Array")
};

array.push("hello ","there");
console.log(array);

array.name();

也可以修正:

var array = new Array();
Array.prototype.toString = function(){
    return("Array")
};

array.push("hello ","there");
console.log(array.toString());
//如许就抹去了toString()要领

猛烈不引荐修正和重写原生对象的原型

原型对象的题目

就是包括援用范例值的属性来讲,题目比较严重。详细体现在原型中的属性被实例同享:

function Person(){};
Person.prototype = {
    constructor: Person,
    name: "Oliver",
    age: 18,
    friends: ["Troy","Alice"]
}

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Ellen");
console.log(person1.friends); //["Troy", "Alice", "Ellen"]
console.log(person2.friends); //["Troy", "Alice", "Ellen"]
//二者完整相同,由于原型中的该属性被实例所同享,push()要领只是修正了person1.friends,没有重写

person1.friends = ["Troy", "Alice"];
console.log(person1.friends); //["Troy", "Alice"]
console.log(person2.friends); //["Troy", "Alice", "Ellen"]
//虽然可以经由过程重写和掩盖来处理该题目,然则依然异常贫苦

console.log(person1.hasOwnProperty("friends")); //true;
console.log(person2.hasOwnProperty("friends")); //false;
//这里就可以看到,重写能处理题目是由于重写致使它建立了实例属性"friends"

这里可以看出,假如我们的初志像如许只是想建立一个同享的数组,那末固然不会有什么题目;然则实例平常都邑有本身的属性,所以不应当零丁运用原型形式。而是组合运用组织函数形式和原型形式。

组合运用组织函数形式和原型形式

这是一种用来定义援用范例的一种默许形式:


function Person(name,age){
    this.name = name;
    this.age = age;
    this.friends = [];
} //独享的部份
Person.prototype = {
    constructor: Person,
    sayName: function(){
        return this.name;
    }
} //同享的部份
var person1 = new Person("Oliver",18);
var person2 = new Person("Troy",24);
person1.friends.push("Alice","Mark");
person2.friends.push("Mac");
console.log(person1.friends.toString());
console.log(person2.friends.toString());
/*
[Log] Alice,Mark (repetition.html, line 228)
[Log] Mac (repetition.html, line 229)
*/

动态原型形式

可以经由过程搜检某个应当存在的要领是不是有用,来决议是不是须要初始化原型:

function Person(name,age){
    this.name = name;
    this.age = age;
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            return (this.name);
        };
    }
}
var person = new Person("Oliver",18);
console.log(person.sayName()); //Oliver

实际上就是把下面代码封装在了组织函数中:

function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function(){
    return(this.name);
};
var person = new Person("Troy",24);
console.log(person.sayName()); //Troy

寄生组织函数形式

世纪撒好惆怅跟工场形式一样。发起在可以运用其他形式的情况下,不要运用该形式。

稳妥组织函数形式

稳妥对象,指的是没有大众属性,且其要领也不援用this 的对象如:

function Person(name,age){
    var obj = new Object();
    obj.sayName = function(){
        console.log(name);
    };
    return obj;
}
var person1 = Person("Oliver",18);
person1.sayName(); //Oliver
    原文作者:JS菌
    原文地址: https://segmentfault.com/a/1190000004034059
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞