面向对象OOP

从零开始面向对象的程序设计


构建函数和对象


有些人认为 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 的 protoproto ,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 proto 就是 window.Object.prototype. 所以 doSomeInstancing 的 protoproto (也就是 doSomething.prototype 的 proto (也就是 Object.prototype)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的 protoprotoproto 里面查找. 然而这有一个问题: doSomeInstancing 的 protoprotoproto 不存在. 最后, 原型链上面的所有的 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';
}

对象成员总结

  1. 定义在构造器函数中,用于给予对象实例的属性和方法
function Person () {
	this.x = x;
}
var myInstance = new Person();
  1. 直接在构造函数上定义,仅在构造函数上可用的属性和方法,称静态属性和静态方法
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);

待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值