《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 中有两种属性:数据属性和访问 ...
随机推荐
- 二叉树的层次遍历 · Binary Tree Level Order Traversal
[抄题]: 给出一棵二叉树,返回其节点值的层次遍历(逐层从左往右访问) [思维问题]: [一句话思路]: 用queue存每一层 [输入量]:空: 正常情况:特大:特小:程序里处理到的特殊情况:异常情况 ...
- TextBox 宽度无效
TextBox 宽度设置 Width="550px" 在IE10.1011下无效 改为用 style="width:550px; height:200px;" ...
- 测试SQL
create database testDB create table users( id int primary key identity(1,1), uname nvarchar(20 ...
- [leetcode]403. Frog Jump青蛙过河
A frog is crossing a river. The river is divided into x units and at each unit there may or may not ...
- PostgreSQL+pgpool-II复制方案
目录 PostgreSQL+pgpool-II复制方案 1. Pgpool-II介绍 2. pgpool-II安装 2.1 安装pgpool-II yum源,并安装pgpool-II 2.2 添加Pg ...
- c语言指针数组和结构体的指针
指向数组的指针,先初始化一个数组,使用传统方式遍历 void main() { ] = { ,,,, }; ; i < ; i++) { printf("%d,%x\n", ...
- springmvc与struts2的不同
1.springmv的入口是一个servlet,即前端控制器.而struts2入口是一个fliter过滤器. 2.springmvc是基于开发方法(一个url对应一个方法,通过注解的方式进行访问),请 ...
- requestFullscreen()事件全屏不好使怎么解决
标明:我在360和火狐中全屏requestFullscreen()事件不好使: 解释:我后来发现我的页面是在iframe框架中使用的并且没有设置allowfullscreen="true&q ...
- 绘制3D的js库
有哪些值得推荐的绘制3D的js库? 4 个回答 默认排序 草皮子 HTML5/GAME 4 人赞同了该回答 只用过three.js,所以推荐这个.不清楚你打算用来做什么,总的来说,得看你的运 ...
- 在windows系统下安装oracle 11g
oracle 11g 安装在windows server 2012 系统下. 最近,需要配置数据库,要求在windows操作系统下,安装oracle 11g 数据库,因为以前没有安装过,所以成功后, ...