《JavaScript高级程序设计》第六章【面向对象的程序设计】 包括对象、创建对象、继承
- 一、理解对象
- 二、创建对象
- 1. 工厂模式
- 2. 构造函数模式
- 3. 原型模式
- 4. 组合使用构造函数模式和原型模式【使用最广泛】
- 5. 动态原型模式
- 6. 寄生构造函数模式
- 7. 稳妥构造函数模式
- 三、继承
- 1. 原型链
- 2. 借用构造函数
- 3. 组合继承【最常用】
- 4. 原型式继承
- 5. 寄生式继承
- 6. 寄生组合式继承
一、理解对象
ECMAScript中有两种属性:数据属性和访问器属性。
二、创建对象
1. 工厂模式
使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这种方法后来被构造函数模式所取代。
2. 构造函数模式
可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。但是它的每个成员都无法得到复用,包括函数。
但是这样说好像也不准确——如果是通过一个指针指向构造函数外部的函数的话,应该算是复用?
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = sayName; //一个指向函数的指针,所以所有的实例共享同一函数
this.sayAge = function(){
console.log(this.age);
}
}
function sayName(){
console.log(this.name);
}
var person1 = new Person('Jack');
var person2 = new Person('Amy');
person1.sayName();
person2.sayName();
console.log(person1.sayName === person2.sayName); //true
console.log(person1.sayAge == person2.sayAge); //false
但是这种方法:1)sayName函数在全局作用域中定义,但实际只被某个对象调用,名不副实
2)没有封装性
3. 原型模式
使用构造函数的prototype属性来指定那些应该共享的属性和方法。
4. 组合使用构造函数模式和原型模式【使用最广泛】
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的熟悉。且这种模式还支持构造函数传递参数
function Person(name){
this.name = name;
}
Person.prototype = {
constructor : Person, //因为这里通过对象字面量重写了整个原型对象,但constructor会指向Object。所以要特意设置
sayName : function(){
console.log(this.name);
}
}
var person1 = new Person('Nick');
var person2 = new Person('Amy');
console.log(person1.sayName == person2.sayName); //true
5. 动态原型模式
function Person(name){
this.name = name;
if(typeof this.sayName != "function"){ //这段代码只会在在初次调用构造函数时才会执行
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
var person = new Person('Jack');
可以使用instanceof操作符来确定实例类型
注意:使用动态函数模型时,不能使用对象字面量重写原型。
在已经创建了实例的情况下重写原型,就会切断现有实例和新原型之间的关系。
举个栗子:
function Person(name){
this.name = name;
if(typeof this.sayName != "function"){ //这段代码只会在在初次调用构造函数时才会执行
Person.prototype.sayName = function(){
console.log(this.name);
}
}
}
var person = new Person('Jack');
Person.prototype = {
sayHi : function(){
console.log("hi");
}
}
person.sayName(); //Jack
//person.sayHi(); //Uncaught TypeError: person.sayHi is not a function var person2 = new Person('Amy');
person2.sayHi(); //hi
person2.sayName(); //Amy
打印出person和person2:
这里我的理解是:调用构造函数会为实例增加一个指向最初原型的[[prototype]]指针,而把原型修改为另一个对象,则会切断构造函数与最初原型之间的关系。因为实例中的指针只会指向原型,而不指向构造函数,此时修改的是构造函数的原型,而之前创建的实例仍然指向之前的原型对象。故其仍然用于sayName()函数。然而,为什么后来创建的实例person2也会拥有sayName()函数呢?不是之前的构建已经切断了吗?这里我猜测原因是因为在创建实例person2时,if逻辑检测到Person中没有sayName()函数,于是又增加了这样一个函数。为了验证猜想,我在if逻辑里打印一句话,这样只要进入循环就会打印出来这句话,果然打印了两次,猜想得证。
6. 寄生构造函数模式
基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
常用于为对象创建构造函数。
一个栗子:创建一个具有额外方法的特殊数组
function SpecialArray(){
//创建数组
var values = new Array(); //添加值
values.push.apply(values, arguments); //这里比较疑惑的是为什么要用apply,既然values是一个数组,那么直接调用push不可以吗?
//values.push(arguments); //[object Arguments] //添加方法
values.toPipedString = function(){
return this.join("|");
}; //返回数组
return values;
}
var color = new SpecialArray("red", "blue", "green");
console.log(color.toPipedString()); //red|blue|green
上述代码中遇到了一个问题:如第6.7行所示,为什么要用apply而不能直接使values.push(arguments)呢?
——换成直接使用push后输出是[object Arguments]。然后查了下发现arguments果然是一个对象,那为什么apply中可以直接用?猜想应当是apply内部实现对arguments进行了解析。
arguments并不是一个数组,而是一个伪数组,具有length属性. 这里也可以直接用[].push来代替.
关于寄生构造函数模式,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没什么不同。为此,不能依赖instanceof操作符来确定对象类型。
所以,可以用其他,不要用这个方法。
7. 稳妥构造函数模式
稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象适用在一些安全的环境中(禁止this和now),或在防止数据被其他应用程序改动时使用。
function Person(name){
var o = new Object(); //在这里定义私有变量和函数 o.sayName = function(){
console.log(name);
}; return o;
}
var person = Person("nick");
person.sayName(); //nick
这种方法与上一个方法的区别在于:新创建对象的实例方法不引用this,不使用new操作符调用构造函数。
这种方法除了调用sayName()函数外,没有别的办法可以访问到其数据成员,所以具有安全性。
instanceof操作符对这种方法也无效。
三、继承
两种继承方式:接口继承和实现继承。ECMAScript只支持实现继承,且其实现继承主要是依靠原型链来实现的。
1. 原型链
原型链实现继承的基本思想:用原型链让一个引用类型继承另一个引用类型的属性和方法。
构造函数,原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,如果让原型对象等于另一个对象的实例,则原型对象将包含一个指向另一个原型的指针,另一个原型中也包含一个指向另一个构造函数的指针。(这都是什么鬼!)
实现原型链模式:
function Parent(){
this.name = "parent";
}
Parent.prototype.getName = function(){
return this.name;
}
function Child(){
this.subName = 'child';
}
//继承了Parent
Child.prototype = new Parent();
Child.prototype.getSubName = function(){
return this.subName;
};
var child = new Child();
console.log(child.getName()); //parent
console.log(child.getSubName()); //child //两种确定原型和实例的关系的方法
//1)instanceof
console.log(child instanceof Object); //true
console.log(child instanceof Parent); //true
console.log(child instanceof Child); //true //isPrototypeOf()
console.log(Object.prototype.isPrototypeOf(child)); //true
console.log(Parent.prototype.isPrototypeOf(child)); //true
console.log(Child.prototype.isPrototypeOf(child)); //true
子类型有时候需要重写超类中的某个方法,或者需要添加超类中不存在的某个方法,给原型添加方法的代码一定要放到替换原型的语句之后。否则,子类的方法会被覆盖。因为原型指针指向了父类的原型。
function Parent(name){
this.name = name;
}
Parent.prototype.sayName = function(){
console.log("Parent: my name is " + this.name);
}
function Child(name,age){
this.name = name;
}
Child.prototype.sayName = function(){
console.log("Child: my name is "+ this.name);
};
Child.prototype = new Parent();
var child = new Child('Amy'); child.sayName(); //Parent: my name is Amy
原型链继承的问题:原先的实例属性也会变成原型属性。且不能向构造函数传递参数
2. 借用构造函数
apply()和call()
3. 组合继承【最常用】
使用原型链实现对原型属性和方法的继承,而通过构造函数实现对实例属性的继承。
instanceof和isPrototype()也能够识别基于组合继承创建的对象
function Parent(name){
this.name = name;
}
Parent.prototype.sayName = function(){
console.log(this.name);
}
function Child(name,age){
Parent.call(this,name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.sayAge = function(){
console.log(this.age);
}
var child = new Child('Jack',27);
child.sayName(); //Jack
child.sayAge(); //
4. 原型式继承
思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。如下:
function object(o){
function F(){} //创建一个临时的构造函数
F.prototype = o; //将传入的对象作为这个构造函数的原型
return new F(); //返回这个临时类型的一个实例
}
这种方法要求必须有一个对象可以作为另一个对象的基础。
ECMAScript5中新增了一个函数实现了原型式继承:Object.create()。
Object.create()函数接收两个参数:一个用于新对象原型,一个为新对象定义额外属性的对象。
这种方法也可能会使引用类型值的属性共享
var Person = {
name: "Nick",
friends: [1,2,3,4]
};
var anotherPerson = Object.create(Person);
anotherPerson.name = "Joe";
anotherPerson.friends.push(5); var yetAnotherPerson = Object.create(Person);
yetAnotherPerson.name = "Amy";
yetAnotherPerson.friends.push(6); console.log(Person.friends); //[1,2,3,4,5,6] var person2 = Object.create(Person, {
name: {
value: "Lee"
}
});
console.log(Person.name); //Nick
console.log(person2.name); //Lee
console.log(person2); //name:Nick在其原型之中。原型链会先找实例属性
5. 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,然后返回对象
function createAnothor(o){
var clone = Object.create(o);
clone.sayHi = function(){
console.log("hi");
};
return clone;
}
var person = {
name : "Nick",
}
var anotherPerson = createAnothor(person);
anotherPerson.sayHi();
这种模式用于主要考虑对象不是自定义类型和构造函数的情况下。
这种方法来为对象添加函数,也不能做到函数复用。
6. 寄生组合式继承
之前提到的组合函数的不足在于:无论什么情况下,都会调用两次超类型构造函数。一次是在创建子类原型的时候,一次是在子类构造函数内部。
所谓寄生式组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function inheritPrototype(Child, Parent){
var prototype = Object.create(Parent.prototype); //创建对象
prototype.constructor = Child; //增强对象
Child.prototype = prototype; //指定对象
}
function Parent(name){
this.name = name;
}
Parent.prototype.sayName = function(){
console.log(this.name);
};
function Child(name,age){
Parent.call(this,name);
this.age = age;
}
inheritPrototype(Child,Parent);
Child.prototype.sayAge = function(){
console.log(this.age);
}
var child = new Child('Jack',29);
child.sayName();
child.sayAge();
console.log(child); //这时,child的__proto__中就不会有从父类继承来的name和age属性了
用寄生组合式继承打印出来的child:
很干净。而如果用组合继承的话:
可以看到其原型中有一个多余的name属性。
《JavaScript高级程序设计》第六章【面向对象的程序设计】 包括对象、创建对象、继承的更多相关文章
- JavaScript高级程序设计-第六章面向对象的程序设计
创建对象主要的两种形式,创建Object实例和创建对象字面量 对象包含属性和方法 数据 .属性有四个特性,特性是为了描述属性行为的,他们是: Configurable(可配置的)是否能删除或是否能修改 ...
- JAVASCRIPT高程笔记-------第六章 面向对象的程序设计
理解对象的概念 js中的对象与其他 编程语言中的类不一样 ECMAscript 没有类的概念 ECMA-262 把对象定义为 “无序属性的集合,其属性可以包含基本值,对象或者函数” ...
- 读书笔记 - js高级程序设计 - 第六章 面向对象的程序设计
EcmaScript有两种属性 数据属性 和 访问器属性 数据属性有4个特性 Configurable Enumerable Writable Value 前三个值的默认值都为false ...
- JavaScript高级程序设计 第六章 面向对象程序设计
面向对象程序设计 ECMA-262将对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”严格来讲,这就相当于说对象是一组没有特定顺序的值.对象的每个属性和方法都有一个名字,而每个名字都 ...
- javascript高级程序设计第3版——第6章 面向对象的程序设计
第六章——面向对象的程序设计 这一章主要讲述了:面向对象的语言由于没有类/接口情况下工作的几种模式以及面向对象语言的继承: 模式:工厂模式,构造函数模式,原型模式 继承:原型式继承,寄生式继承,以及寄 ...
- Python第六章 面向对象
第六章 面向对象 1.面向对象初了解 面向对象的优点: 1.对相似功能的函数,同一个业务下的函数进行归类,分类 2.类是一个公共的模板,对象就是从具体的模板中实例化出来的,得到对象就得到一 ...
- js高级程序设计(六)面向对象
ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”严格来讲,这就相当于说对象是一组没有特定顺序的值.对象的每个属性或方法都有一个名字,而每个名字都映射到一个值.正 ...
- 精读《javascript高级程序设计》笔记三——面向对象的程序设计
重点来了,我认为这一章值得好好地反复地看.看第一遍 还是懵懵懂懂,现在看第二遍,终于能看出点意思了. 创建对象 工厂模式 function createPerson(name, age, job){ ...
- 【笔记】javascript权威指南-第六章-对象
对象 //本书是指:javascript权威指南 //以下内容摘记时间为:2013.7.28 对象的定义: 1.对象是一种复合值:将很多值(原始值或者对象)聚合在一起,可以通过名字访问这些值. ...
- JavaScript高级程序设计学习笔记第六章--面向对象程序设计
1.ECMAScript没有类的概念,ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”,有点类似于散列表 2.ECMAScript 中有两种属性:数据属性和访问 ...
随机推荐
- 删除链表中的重复元素:不留&留一个&删除一个
不留: [抄题]: 给定一个排序链表,删除所有重复的元素只留下原链表中没有重复的元素. [思维问题]: 给出 1->2->3->3->4->4->5->nul ...
- 移动端bug之解决方式
1.Android中元素被点击时产生的边框: * { -webkit-tap-highlight-color: rgba(250,250,250,0); /*更改点击事件的焦点色*/} 2.去除移 ...
- Python.__getattr__Vs__getattribute__
__getattr__ Vs __getattribute__ class Fish(object): def __getattr__(self, key): if key == 'color': p ...
- gis笔记 wms wfs等OGC标准
WFS 和WMS的区别 WFS是基于地理要素级别的数据共享和数据操作,WFS规范定义了若干基于地理要素(Feature)级别的数据操作接口,并以 HTTP 作为分布式计算平台.通过 WFS服务,客户端 ...
- python while语句写法
count=5 while count>0: print 'i love python' count=count-1 else: print 'over'
- c++11多线程学习笔记之三 condition_variable使用
从windows角度来说,condition_variable类似event. 阻塞等待出发,不过condition_variable可以批量出发. 代码如下: // 1111111.cpp : 定义 ...
- boost 学习(1)
智能指针的学习 中文教程网站 http://zh.highscore.de/cpp/boost/ 不过代码可能 由于BOOST 版本不同需要稍作修改 scoped_ptr 离开作用域则自动调用类析构函 ...
- sd卡不能格式化
可能是读卡器坏了,还真遇到过,花了一下午,各种尝试,最后发现只是读卡器坏了.
- MYSQL 问题小总结
mysql 问题小总结 1.MySQL远程连接ERROR 2003(HY000):Can't connect to MySQL server on ‘ip’(111)的问题 通常是mysql配置文件中 ...
- crontab误删除
命令如下: cat /var/log/cron* | grep -i "`which cron`" > ./all_temp cat ./all_temp | grep -v ...