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="小王" ...
随机推荐
- 多线程 or 多进程?[转]
在Unix上编程采用多线程还是多进程的争执由来已久,这种争执最常见到在C/S通讯中服务端并发技术的选型上,比如WEB服务器技术中,Apache是 采用多进程的(perfork模式,每客户连接对应一个进 ...
- Git 一次性 pull push 所有的分支
/********************************************************************************* * Git 一次性 pull pu ...
- centos6.4_安装Python3.5.2之问题
一.安装centos6.4虚拟机 这个就不用我详细介绍了,网上安装教程一大把了哈,自己百度安装应该没啥问题了 二.下载python安装包 官网下载python3.5.2安装包:https://www. ...
- javascript_获取浏览器属性
navigator.appName:浏览器名称: navigator.appVersion:浏览器版本: navigator.language:浏览器设置的语言: navigator.platform ...
- [java] java解析txt文件
/** * 读取txt文件内容封装为map返回 * @param filePath * @return */ public static String readTxt(String filePath) ...
- 电子面单纸打印时固定高度18cm,到底是多少px
点评:A4纸竖向打印,html网页页面的宽度设置成多少?这个问题是我们大家所疑惑的,于是网上搜集整理下,希望可以帮助你们 最近开发项目时遇到了网页打印的问题,这是问题之二,打印宽度设置 在公制长度单位 ...
- 如何创建 CSS
如何插入样式表 当读到一个样式表时,浏览器会根据它来格式化 HTML 文档.插入样式表的方法有三种: 外部样式表 当样式需要应用于很多页面时,外部样式表将是理想的选择.在使用外部样式表的情况下,你可以 ...
- python学习-day20、装饰器【图片缺失可看】印象笔记博客备份
前言: 装饰器用于装饰某些函数或者方法,或者类.可以在函数执行之前或者执行之后,执行一些自定义的操作. 1.定义:装饰器就是一个函数,为新定义的函数.把原函数嵌套到新函数里面.以后就可以在执行新函数的 ...
- WEB-INF目录下的文件访问权限(待解决)
对于Tomcat服务器而言,WEB-INF目录下的文件是不能通过在浏览器中直接输入地址的方式来访问. 原因:还不清楚
- javascript性能优化总结一(转载人家)
一直在学习javascript,也有看过<犀利开发Jquery内核详解与实践>,对这本书的评价只有两个字犀利,可能是对javascript理解的还不够透彻异或是自己太笨,更多的是自己不擅于 ...