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="小王" ...
随机推荐
- WCF终结点配置
错误信息:已有针对 IP 终结点 127.0.0.1:8235 的侦听器.如果有其他应用程序已在侦听此终结点,或者,如果在服务主机中具有多个服务终结点,这些终结点具有相同的 IP 终结点但绑定配置不兼 ...
- border-image(转载)
本文转自:http://www.zhangxinxu.com/wordpress/2010/01/css3-border-image%E8%AF%A6%E8%A7%A3%E3%80%81%E5%BA% ...
- 为什么TCP连接不可靠
原文链接:http://watter1985.iteye.com/blog/1924977 原文在此 这篇文章是关于TCP网络编程的一个不起眼的小问题.几乎人人都并不太明白这个问题是怎么回事.曾经我以 ...
- SPARQL1.1 101 Language and Jena support
1 introduction definition cited from SPARQL 1.1 Overview: SPARQL 1.1 is a set of specifications that ...
- java中的static关键词
以下来自:http://www.cnblogs.com/codc-5117/archive/2011/12/04/2275298.html Static基本规则: (1)一个类 ...
- java 压缩文件 传入文件数组,压缩文件,在指定路径下生成指定文件名的压缩文件
/** * 传入文件数组,压缩文件,在指定路径下生成指定文件名的压缩文件 * * @param files * 文件数组 * @param strZipName * 压缩文件路径及文件名 * @thr ...
- VNC-Server安装及配置
一.什么是VNC? VNC (Virtual Network Computer)是虚拟网络计算机的缩写.VNC 是一款优秀的远程控制工具软件,由著名的 AT&T 的欧洲研究实验室开发的.VNC ...
- AndroidStudio 中的坑Error:(1, 0) Plugin is too old, please update to a more recent version, or set ANDROID_DAILY_OVERRID
将 build.gradle 中 的 classpath改为2.0.+ dependencies { classpath 'com.android.tools.build:gradle:2.0.+'然 ...
- Kafka/Metaq设计思想学习笔记 转
转载自: http://my.oschina.net/geecoodeer/blog/194829 本文没有特意区分它们之间的区别,仅仅是列出其中笔者认为好的设计思想,供后续设计参考. 目前笔者并没有 ...
- 函数调用方式__stdcall、__cdel
函数调用方式关系对比如下: 关键字 调用规则 参数传递方向 返回 参数寄存器 堆栈的清除 __cdecl C语言 从右向左 EAX 无 调用者 __stdcall Win32标准 从右向左 EAX ...