JavaScript高级程序设计笔记之面向对象
说起面向对象,大部分程序员首先会想到 类 。通过类可以创建许多具有共同属性以及方法的实例或者说对象。但是JavaScript并没有类的概念,而且在JavaScript中几乎一切皆对象,问题来了,JavaScript中如何面向对象?
JavaScript中将对象定义为:一组无序的 键值对的集合,属性以及方法的名称就是键,键的值可以是任何类型(字符串,数字,函数……)
在JavaScript中,所有对象继承自Object,所有对象继承自Object,所有对象继承自Object!
创建
1 简单创建对象:
var o = new Object();
o.name = 'mncu';
o.age = 120;
o.sayName = function(){
alert(this.name);
};
o.sayName(); // 'mncu' // 字面量创建 与上面的代码等价
var o = {
name:'mncu',
age : 120,
sayName:function(){
alert(this.name);
} };
o.sayName(); //'mncu'
使用上面的方法虽然可以创建对象,但缺点也很明显,假如再次创建一个具有相同属性以及方法的对象,还得把代码复制修改一遍,会产生大量的重复代码。
2 工厂模式
这种模式抽象了创建对象的具体过程
function createperson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
};
return o; }
var p1 = createperson('mncu',120);
var p2 = createperson('linghuchong',120);
p1.sayName(); //'mncu'
p2.sayName(); //'linghuchong'
console.log(typeof p1); //object
p1 instanceof createperson // false ,无法判断类型
但这种方式有一个缺点:无法判断某个对象是什么类型。
3 构造函数模式
function Person(name,age){ // 按照惯例,构造函数的首字母要大写
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
};
}
var p1 = new Person('mncu',120); // 必须使用new关键字创建对象
var p2 = new Person('linghuchong',120);
p1.sayName(); //'mncu'
alert(p1 instanceof Person); // true,可判断类型
构造函数模式解决了工厂模式中不能判断对象类型的问题。
在使用构造函数模式时要注意:
--必须使用new关键字创建对象! new 关键字相当于做了以下几步:
1 创建一个新对象
2 将构造函数的作用域赋值给这个新对象(因此this就指向了这个新对象)
3 执行构造函数的代码(为这个新对象添加属性)
4 返回新对象
--使用构造函数创建的对象都有一个constructor属性,该属性可以标识对象类型,但一般我们还是常用instanceof来判断对象类型, 因为constructor属性仅返回构造函数类型。
p1.constructor === Person //true
p1.constructor === Object //false
p1 instanceof Object // true
p1 instanceof Person //true
--构造函数实际上和普通函数并无多大区别,因为其不存在特殊的语法,任何函数,只要通过new调用,那么他就可以作为构造函数。
var p3 = Person('dongfangbubai',120); // 不使用new时,函数的作用域会是全局作用域,this就会指向window对象。
p3.sayName() //报错
sayName() // 'dongfangbubai'
--构造函数也存在问题,每个方法都要在实例上创建一遍。也就是说p1和p2的sayName()方法虽然作用相同,但这两个方法并不是同一个函数。
p1.sayName == p2.sayName // false
--无new创建:
function Person(name,age){
//alert(this);
if(!(this instanceof Person)){
return new Person(name,age);
}
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
}
}
var p1 = Person('hah',20);
console.log(p1.name)
4 原型模式
原型模式解决了构造函数模式中同功能的方法的代码无法重用的问题。
我们创建的每个函数都有一个名为prototype的属性,这个属性是一个指针,指向一个对象,这个对象被称为原型对象。原型对象有一个名叫constructor的属性,这个属性是一个指针,指向构造函数。默认情况下,所有函数的原型都是Object的实例。
使用原型模式创建对象:
function Person(){
Person.prototype.name = 'noOne';
Person.prototype.age = 'noAge';
Person.prototype.sayName = function(){
return this.name;
}; }
var p1 = new Person();
p1.name; // 'noOne'
var p2 = new Person()
p2.name; //'noOne'
在本例中,构造函数创建的对象p1的name属性是如何展现的?
首先p1会查找自身有没有name属性,如果有的话,就返回自身的name属性的值,如果没有的话,则查找原型对象中有没有name属性,若原型对象中有name属性,则返回其值,否则,就报错。
p1.name = 'mncu';
p1.name; // 'mncu'
p2.name; //'noOne'
我们可以通过hasOwnProperty()方法检测一个属性是存在于具体的对象中,还是存在于该对象的原型对象中
p1.hasOwnProperty('name') //true
p2.hasOwnProperty('name') //false
我们也可以通过 in 关键字来判断一个属性是否存在于具体的对象或者该对象的原型对象中
'name' in p1 // true
'name' in p2 // true
原型对象的简写格式:
function Person(){ }
Person.prototype={ // 将原型对象重写,但重写后原型对象的constructor就不会指向Person了(指向Object)。所以我们一般会添加:constructor:Person
constructor:Person,
name:'noOne',
age:'noAge',
sayName:function(){
alert(this.name);
}
};
原型模式存在的问题:
function Person(){ }
Person.prototype={
constructor:Person,
name:'noOne',
age:'noAge',
brothers : ['xiaoming'],
sayName:function(){
alert(this.name);
}
};
var p1 = new Person();
var p2 = new Person();
p1.brothers.push('xiaohong');
console.log(p2.brothers) // ['xiaoming','xiaohong']
当我们改变 值为引用类型的对象的属性 时,这个改变的结果会被其他对象共享。
5 将构造函数模式和原型模式结合
构造函数模式的属性没毛病。缺点是:无法共享方法
原型模式的方法没毛病。缺点是:当原形对象的属性的值为引用类型时,对其进行修改会反映到所有实例中
那我们就将两者的结合,对象的属性使用构造函数模式创建,方法则使用原型模式创建
这种方式是最为常见的一种面向对象编程模式
function Person(name,age){
this.name = name;
this.age = age; }
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
};
var p1 = new Person('mncu',120);
p1.name; // 'mncu'
继承
1 原型链
JavaScript中引入了原型链的概念,具体思想: 子构造函数的原型对象初始化为父构造函数的实例,孙构造函数的原型对象初始化为子构造函数的实例…… ,这样子对象就可以通过原型链一级一级向上查找,访问父构造函数中的属性以及方法。
构建原型链:
function Animal(name){
this.name = name || 'null';
this.action = ['move','wow'];
}
function Cat(){
this.wow = function miao(){
alert('miao');
};
}
function Dog(){
this.wow = function(){
alert('wang')
};
}
Cat.prototype = new Animal();
Dog.prototype = new Animal();
var c1 = new Cat();
var d1 = new Dog();
console.log(c1.action); // ['move','wow']
d1.action.push('run');
var c2 = new Cat();
var d2 = new Dog();
console.log(d1.action); //["move", "wow", "run"]
console.log(c1.action); //["move", "wow"]
console.log(c2.action); //["move", "wow"]
console.log(d2.action); //["move", "wow", "run"]
原型链继承的问题:
function SuperObject(){
this.colors = ['red','blue'];
}
function SubObject(){ }
SubObject.prototype = new SuperObject(); var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors) // ["red", "blue", "yellow"]
当我们改变 值为引用类型的原型对象的属性 时,这个改变的结果会被所有子对象共享。这个缺点某些时候相当致命,所以我们很少使用这种方法来继承
2 借用构造函数继承
function SuperObject(){
this.colors = ['red','blue'];
this.sayBye= function(){
console.log('Bye')
}
}
function SubObject(){
SuperObject.call(this); // 在子类中调用父类的构造方法,实际上子类和父类已经没有上下级关系了
} var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors); //['red','blue']
console.log(instance2 instanceof SuperObject); // false
console.log(instance1.sayBye === instance2.sayBye) // false
这个方法虽然弥补了原型链的缺点,但是又暴露出了新的缺点:
1 子类和父类没有上下级关系,instance2 instanceof SuperObject 结果是false
2 父类中的方法在每个子类中都会生成一遍,父类中的方法没有被复用。
3 组合继承
组合继承就是将原型链继承和借用构造方法继承组合,发挥两者之长。
function SuperObject(){
this.colors = ['red','blue']; } SuperObject.prototype.sayBye= function(){
console.log('bye')
}; function SubObject(){
// 引用父类型的属性,又调用了一次父函数
SuperObject.call(this);
}
// 继承父类型的方法,调用了一次父函数
SubObject.prototype = new SuperObject(); var instance1 = new SubObject();
instance1.colors.push('yellow');
var instance2 = new SubObject();
console.log(instance2.colors); //['red','blue']
console.log(instance2 instanceof SuperObject); // true
console.log(instance1.sayBye === instance2.sayBye); // true
4 寄生组合式继承----道格拉斯方法
虽然组合继承没啥大缺点,但是爱搞事情的有强迫症的程序猿们觉得,组合继承会调用两次父类型函数(在上面的代码中标注了),不够完美。于是道格拉斯就提出了寄生组合继承。
思路是构造一个中间函数,将中间函数的prototype指向父函数的原型对象,将子函数的prototype指向中间函数,并将中间函数的constructor属性指向子函数。
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
function Student(props) {
this.name = props.name || 'Unnamed';
} Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
} function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
} // 实现原型继承链:
inherits(PrimaryStudent, Student); // 绑定其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
这个方法只调用了一次父构造函数,并因此避免了在父函数的原型对象上创建不必要的、多余的属性。
开发人员都表示:这种方法是最理想的继承方式(我没看出来,只觉得这是最烧脑的继承方式,看来我离开发人员还有一定距离。。。)
JavaScript高级程序设计笔记之面向对象的更多相关文章
- javascript高级编程笔记05(面向对象)
面向对象设计 es中有两种属性:数据属性和访问器属性 数据属性: 数据属性包含一个数据值的位置,在这个位置可以读取和写入值,数据属性有4个描述其行为的特性 [[Configurable]]:表示能否通 ...
- javascript高级程序设计--笔记01
概述 JavaScript的实现包含三个部分: 1 核心(ECMAScript) 提供核心语言功能 2 文档对象模型(DOM) 一套提供了访问以及操作网页内容的API 3 浏览器对象模型( ...
- javascript事件小结(事件处理程序方式)--javascript高级程序设计笔记
1.事件流:描述的是从页面中接收事件的顺序. 2.事件冒泡:IE的事件流叫做事件冒泡,即事件开始从具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到不具体的节点(文档). 3.事件捕获 ...
- JavaScript高级程序设计笔记(一)
---恢复内容开始--- 前三章为基础知识,为了方便以后查看,所以比较啰嗦.这里对函数的基本操作没有记录. 1.JavaScript的实现 虽然 JavaScript 和 ECMAScript 通常都 ...
- JavaScript高级程序设计笔记 事件冒泡和事件捕获
1.事件冒泡 要理解事件冒泡,就得先知道事件流.事件流描述的是从页面接收事件的顺序,比如如下的代码: <body> <div> click me! </div> & ...
- <javascript高级程序设计>笔记
1.要讲一个值转换成其对应的Boolean类型 ,可以调用转型函数Boolean(). var message=“hello world!”; var messageAsBoolean=Boolean ...
- javaScript高级程序设计笔记 2
Undefinde Null Boolean Number String 基本类型 Object 引用类型 只有引用类型才能动态的添加属性 赋值基本类型和引用类型也不相同,复制的基本类型的 ...
- javaScript高级程序设计笔记 1
核心 ECMAScript 文档对象模型 DOM 浏览器对象模型 BOM 延迟脚本 defer typeof操作符 判断字符类型 返回 undefined boolean s ...
- Javascript高级程序设计笔记 <第五章> 引用类型
一.object类型 创建object实例的方式有两种: //第一种使用new操作符跟构造函数 var person= new Object(); person.name="小王" ...
随机推荐
- attention 机制
参考:modeling visual attention via selective tuning attention问题定义: 具体地, 1) the need for region of inte ...
- python数据结构与算法——快速排序
快速排序通过不断将数列分段,使得较小的数在左边的序列,较大的数在右边的序列,不断重复此过程实现排序效果.通过设置两个哨兵不断的找两个序列的较小数,较大数,并把左右的数据互换,实现对数据从粗到细的排序. ...
- css+div
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...
- |原创|unity 4.3 2D功能SpriteRenderer修改颜色的方法
4.3增加了不少2D功能,然后实在没有找到有人分享,在国外查资料研究一下午然后给个简单的教程 ===================================================== ...
- 一:解决VirtualBox只能安装32位系统的问题
发现自己的笔记本(Thinkpad E440)里的 VirtualBox 只能安装 32位 的系统,如下图所示: 经过一番查资料,发现这玩意需要到BIOS里设置一下,方可安装 64位 系统,操作如下: ...
- .NET Core On Mac 第一步,配置环境
话说.NET Core出来这么久了,也没搞搞,实在是羞愧难当啊.既然选择了开始那就不能半途而废啊..NET 跨平台了,那我就用Mac试试吧. 安装步骤一:安装Homebrew 其中会遇到权限不够的问题 ...
- 【java】 linux下利用nohup后台运行jar文件包程序
Linux 运行jar包命令如下: 方式一: java -jar XXX.jar 特点:当前ssh窗口被锁定,可按CTRL + C打断程序运行,或直接关闭窗口,程序退出 那如何让窗口不锁定? 方式二 ...
- 国内首家VR虚拟现实主题公园即将在北京推出
近期,美国“The VOID”.澳洲“Zero Latency”两大虚拟现实主题乐园让许多爱好者兴奋至极,门票据说都已经预约到明年2月!在如此巨大的商机面前,谁将抢到国内VR虚拟现实主题公园第一块蛋糕 ...
- NHibernate和 FluentNHibernate
NHibernate有两类配置文件,一个是数据库连接配置文件(一般写到程序配置文件里),另一个是数据表和实体的映射文件(实体类.hbm.xml) 开源的框架Fluent NHibernate,有了它, ...
- 根据 MySQL 状态优化 ---- 1. 慢查询
查看 MySQL 服务器运行的各种状态值: mysql> show global status: 1. 慢查询 mysql> show variables like '%slow%'; + ...