从零开始面向对象的程序设计
构建函数和对象
有些人认为 JavaScript 不是真正的面向对象的语言,比如它没有像许多面向对象的语言一样有用于创建class类的声明。JavaScript 用一种称为构建函数的特殊函数来定义对象和它们的特征
构建函数提供了创建您所需对象(实例)的有效方法,将对象的数据和特征函数按需联结至相应对象
// 例:利用普通函数定义一个人
function createNewPerson (name) {
var obj = {};
obj.name = name;
obj.greeting = function () {
console.log(this.name);
}
return obj;
}
// 调用
var salva = createNewPerson('salva');
salva.name;
salva.greeting();
上面的代码运行良好,但是有点冗长,如果我们知道如何创建一个对象,就没有必要创建一个新的空对象并且返回它
// 例 通过构造函数创建一个人
function Person (name) {
this.name = name;
this.greeting = function () {
console.log(this.name);
}
}
// 调用
var person1 = new Person('Bob');
var person2 = new Person('Sarah');
person1.name;
person1.greeting();
person2.name;
person2.greeting();
控制台有两个对象,没一个保存在不同的命名空间里,当您访问它们的属性时,您需要使用person1或者person2调用它们,尽管它们有相同的name属性和greeting方法,但是它们都是各自独立的,不会冲突
注:一个构造函数通常大写字母开头
值得注意的是每次当我们调用构造函数时,我们都会重新定义一遍 greeting(),这不是个理想的方法。为了避免这样,我们可以在原型里定义函数,接下来我们会讲到
创建最终的构造函数
function Person (first, last, age, gender, interests) {
this.name = {
'first': first,
'last': last
};
this.age = age;
this.gender = gender;
this.bio = function () {
console.log(this.name.first + ' ' + this.name.last + ' is ' + this.age + 'years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
};
this.greeting = function () {
console.log(this.name.first);
};
};
// 调用
var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
// 访问
person1['age'];
person1.interests[1];
person1.bio();
创建对象的其他方式
使用Object()构造函数
/*
Object()构造函数
*/
// 创建新对象
var person1 = new Object();
// 给新对象添加属性和方法
person1.name = 'Chris';
person1['age'] = 38;
person1.greeting = function () {
console.log(this.name);
}
/*
还可以将对象文本传递给Object() 构造函数作为参数, 以便用属性/方法填充它
var person1 = new Object({
name: 'Chris',
age: 38,
greeting: function () {
console.log(this.name);
}
})
*/
使用create()方法
// 根据上面的person1创建新对象person2
var person2 = Object.create(person1); // 缺点:有兼容问题
// 调用
person2.name;
person2.greeting();
对象原型
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。
使用Javascript中的原型
在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)
function doSomething () {};
console.log(doSomething.prototype);
控制台打印如下:
{
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
现在,我们可以添加一些属性到 doSomething 的原型上面,如下所示.
function doSomething () {};
doSomething.prototype.foo = 'bar';
console.log(doSomething.prototype);
控制台打印如下:
{
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 doSomething 的实例,就可以在这个对象上面添加一些属性
function doSomething () {};
doSomething.prototype.foo = 'bar';
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = 'some value';
console.log(doSomeInstancing);
控制台打印如下:
{
prop: "some value",
__proto__: {
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
}
就像上面看到的, doSomeInstancing 的 proto 属性就是doSomething.prototype. 但是这又有什么用呢? 好吧,当你访问 doSomeInstancing 的一个属性, 浏览器首先查找 doSomeInstancing 是否有这个属性. 如果 doSomeInstancing 没有这个属性, 然后浏览器就会在 doSomeInstancing 的 proto 中查找这个属性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 proto 有这个属性, 那么 doSomeInstancing 的 proto 上的这个属性就会被使用. 否则, 如果 doSomeInstancing 的 proto 没有这个属性, 浏览器就会去查找 doSomeInstancing 的 proto 的 proto ,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 proto 就是 window.Object.prototype. 所以 doSomeInstancing 的 proto 的 proto (也就是 doSomething.prototype 的 proto (也就是 Object.prototype)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的 proto 的 proto 的 proto 里面查找. 然而这有一个问题: doSomeInstancing 的 proto 的 proto 的 proto 不存在. 最后, 原型链上面的所有的 proto 都被找完了, 浏览器所有已经声明了的 proto 上都不存在这个属性,然后就得出结论,这个属性是 undefined.
constructor 属性
每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。
修改原型
// 构造器及其属性定义
function Test (a, b, c, d) {
// 属性定义
// 定义第一个方法
Test.prototype.x = function () { ... }
// 定义第二个方法
Test.prototype.y = function () { ... }
}
JavaScript中的继承
继承的对象函数并不是通过复制而来,而是通过原型链继承(通常被称为 原型式继承 —— prototypal inheritance)
// 定义Person构造器函数
function Person (first, last, age, gender, intersts) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
// 把方法定义在原型上面
Person.prototype.greeting = function () {
console.log(this.name.first);
}
// 定义Teacher构造器函数
function Teacher (first, last, age, gender, interests, subject) {
/*
call 函数
允许您调用一个在这个文件里别处定义的函数
第一个参数:重新指定您调用的函数里所有“this”指向的对象
后面的参数:指明了所有目标函数运行时接受的参数
*/
// 改变this指向,继承Person里面的属性
Person.call(this, first, last, age, gender, interests);
// Teacher 独有的属性
this.subject = subject;
}
从无参构造函数继承
如果您继承的构造函数不从传入的参数中获取其属性值,则不需要在call()中为其指定其他参数
function Brick() {
this.width = 10;
this.height = 20;
}
function BlueGlassBrick () {
// 改变this指向,继承width和height属性
Brick.call(this);
this.opacity = 0.5;
this.color = 'blue';
}
对象成员总结
- 定义在构造器函数中,用于给予对象实例的属性和方法
function Person () {
this.x = x;
}
var myInstance = new Person();
- 直接在构造函数上定义,仅在构造函数上可用的属性和方法,称静态属性和静态方法
function Person () {};
Person.x = x;
3.定义在构造函数原型上,由所有实例和对象类继承的属性和方法
function Person () {};
Person.prototype.x = x; // 在原型上定义属性
Person.prototype.y(); // 在原型上定义方法
ES6 class类
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
greeting () {
console.log(this.name.first);
}
}
// 调用
let han = new Person('Han', 'Solo', 25, 'male', ['Sumggling']);
han.greeting();
通过extends继承person,添加自有属性
class Teacher extends Person {
constructor(first, last, age, gender, interests,subject, grade) {
super(first, last, age, gender, interests);
this.subject = subject;
this.grade = grade;
}
}
// 调用
let snape = new Teacher('Serverus', 'Snape', 58, ['Potions'], 'Dark arts', 5);
snape.greeting();
snape.age
snape.subject;
Getters and Setters
class Teacher extends Person {
constructor(first, last, age, gender, interests, subject, grade) {
super(first, last, age, gender, interests);
this._subject = subject;
this.grade = grade;
}
get subject () {
return this._subject;
}
set subject (newSubject) {
this._subject = newSubject;
}
}
// 调用
let snape = new Teacher('Serverus', 'Snape', 58, ['Potions'], 'Dark arts', 5);
console.log(snape.subject);
snape.subject = 'Balloon animals';
console.log(snape.subject);