---实例对象的三种方式
---原型的概念,为什么需要原型
---原型常用方法
---实现自己的原型扩展方法
---简单原型对象
---原型对象的常用开发模式(组合、寄生、动态模型、稳妥对象)
---深入继承的解析(javascript 继承实现的关键解析)
---常用三种继承模式(原型继承、借用构造函数继承、混合继承)
---模拟extjs4.1底层继承代码
-----------------学 习 目 标---------------
---了解面向对象程序的特点
---学会js模拟面向对象的方式
---理解prototype
---理解继承
------------5.1 面 向 对 象 程 序 设 计 ------------------
***面向对象的语言有一个标志,那就是他们都有类的概念,通过类
可以创建任意多个具有相同属性和方法的对象。但是在ECMAScript
中没有类的概念,但是我们可以通过其他方式来模拟面向对象的类
--工厂模式:工厂模式是软件工厂领域中一种广为人知的设计模式
--构造函数模式:比如像 ECMAScript 中的 Array 、 Object
Date 等,都是通过构造函数来创建的
--动态原型模式:
--寄生构造函数模式:
--稳妥构造函数模式:
eg:
var obj = new Object(); //或者使用 var obj = {};
obj.name = "z3";
obj.sex = "男";
obj.sayName = function (){alert("我是张三!")};
----------类的概念--------------
第一种形式:工厂模式
function createPerson(name , sex , age){
var obj = new Object();
obj.name = name;
obj.sex = sex;
obj.age = age;
obj.sayName = funciton(){
alert(this.name);
}
return obj;
}
var p1 = createPerson("z3","男","20");
第二种形式:构造函数式
//函数的第一个字母大写,是类的模板(规范!!)
function Person(name ,age , sex){
this.name = name;
this.age = age;
this.sex = sex;
this.sayName = function(){
alert(this.name);
}
}
//构造一个对象: new关键字 、 传递参数、执行模板代码、返回对象
var p1 = new Person("xiao1" , 20 , "男");
---------------创建对象的方式----------------
1.当做构造函数去使用
var p1 = new Person("xiao1" , 20 , "男");
2.作为普通的函数去调用
var p2 = Person("小2" ,25 , "男");//Person类的调用者是 window ,即 Person 类中的 this 就是 window
3.在另一个对象的作用域中调用
var obj = new Object();
//使用 call 、apply
Person.call(obj , "xiao4" , 12 , "女");
alert(obj.name); //会输出 xiao4
---------------5.2 原 型 ------------------------
**我们创建的每一个函数都有一个prototype 属性,这个属性是一个指针,指向一个对象,
而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
**原型对象实际就是一个构造函数的实例对象,与普通的实例对象没有什么本质上的区别,
js中每一个对象都有一个原型对象。不过他比较特殊,该对象所包含的所有属性和方法
能够供构造函数的所有实力共享,这就是其他语言所说的继承,而javascript通过原型
对象来实现继承,简称原型继承。静态原型继承:Object.prototype.[method field];
**简单实现 Array 中的push 、pop 方法
----------------
原型:prototype 关键字
function Person(){
}
var obj = Person.prototype; //获取函数的原型对象 ,并且赋给 obj
//alert(obj.constructor); //该句返回的是 构造器function Person(){ }里面的所有内容
//下面是给 对象 obj 设置属性,obj里面的属性都是可以共享的
obj.name = "z3";
obj.age = 20;
obj.sayName = function(){ alert(this.name);};
-----------上面是定义 共享属性的过程------
下面进行调用:
var p1 = new Person();
var p2 = new Person();
注释:上面写得 两个对象 p1 、p2 中的属性值是一样的,方法也是用的同一个方法。
补充:搞清楚:构造函数 、 原型对象、 实例对象
构造函数.prototype = 原型对象
原型对象.constructor = 构造函数
判断obj 是否是实例对象 p1的原型:obj.idPrototypeOf(p1);
--------------Object.getPrototypeOf()---------
---Object.getPrototypeOf(): 根据实例对象获得原型对象
function Person(){
}
Person.prototype.name = "z3";
Person.prototype.age= 20;
Person.prototype.sayName = function(){alert("我是原型!");};
var p1 = new Person();
alert(p1.name); //返回z3
//根据实例对象获得原型对象(prototypeObj)
var prototypeObj = Object.getPrototypeOf(p1);
---------------------
/*每次代码读取一个对象的属性的时候:首先会进行一次搜索,搜索实例对象里
name 属性,看看有没有;如果没有,再去p2的实例所对应的原型对象里去搜索
name 属性,如果有就返回;如果没有的话,就返回undefined
*/
var p2 = new Person();
p2.name = "w5";
alert(p2.name); //输出 w5 ( 如果就想获得原型对象的 name 属性,可以在此句前面加上 “delete p2.name;”)
-----------判断一个对象属性:是属于原型属性还是属于实例属性----------
利用方法:hasOwnProperty("")------->可以准确的判断出是属于原型的还是实例的
var p3 = new Person();
alert(p3.name); //将会输出一个值
alert(p3.hasOwnProperty("name")); //这句话是为了判断:name 属性是否属于p3,这里返回的是 false ,
说明:name 属性是属于原型的
---------in 操作符---------------
//in 操作符:判断属性是否存在于 实例对象 和 原型对象中
补充:只能判断是否有,不能准确的判断出是 属于实例的还是原型的
var p1 = new Person();
alert("name " in p1);
var p2 = new Person();
----------模拟方法:就是判断一个属性:是否存在原型中---------------
在原型对象中,是否存在这个属性{ 第一个参数:当前对象 , 第二个参数:要判断的属性 }
function hasPrototypeProperty(object , name){
return !object.hasOwnProperty(name) && name in object;
}
-----------ECMA5 新特性 :Object.keys(实例对象);括号里传入实例对象,获得的是实例对象的所有属性名称---------------------
//Object.keys(实例对象) :拿到当前对象里的所有keys,返回一个数组。
var p1 = new Person();
p1.name = "z3";
p1.age = 20;
var attributes = Object.keys(p1); //将p1实例对象的所有属性放在 attributes 变量(是一个数组)中
alert(attributes);
----------ECMA5 constructor 属性: 该属性是不能被枚举的-----------
//constructor 属性:该属性的 [eable = false],默认是不能枚举的。
//Object.getOwnPropertyNames 获得对象所有的属性(包括“自己定义的”和“系统默认的”):不管该内部属性能否被枚举
var attribute3 = Object.getOwnPropertyNames(Person.prototype);
-------------------5.3 原型实例练习-------------------
***原型的另外一个作用就是扩展对象中的属性和方法的
--模拟Array 中 each 循环方法
// ECMA5 : forEach循环遍历数组的每一项(只适合遍历一维数组)
var arr = [1,2,3,4,5]; //数组
arr.forEach(function(item , index ,array){
alert(item);
});
//自己实现一个 Array 的each 方法,能遍历多维数组
var arr = [1,2,3,[4,[5,[6]]]];
Array.prototype.each = function(fn){
try{
//1.目的:遍历数组的每一项
this.i || (this.i = 0); //计数器,记录当前遍历的元素位置
//2.严谨的判断什么时候去走 each 核心方法
//当数组的长度大于0 的时候 && 传递的参数必须为函数
if(this.length > 0 && fn.constructor == Function){
//循环遍历数组的每一项
while(this.i < this.length){ //while 循环的范围
//获取数组的每一项
var e = this[this.i];
//如果当前元素获取到了,并且当前元素是一个数组
if(e && e.constructor == Array){
//直接做递归操作
e.each(fn);
}else{
//如果不是数组(那就是一个单个元素)
//下面两行代码的目的就是为了把数组的当前元素传递给fn 函数,并且
让函数执行
var obj = true;
fn.apply(obj , [e]);
}
this.i++;
}
this.i = null;//释放内存,垃圾回收机制回收变量
}
}catch(ex){
}
return this;
}
arr.each(function(item){
alert(item);
});
---------------------5.4 简 单 原 型--------------
**之前我们已经学过了原型如何使用,那么现在我们介绍一种
简单原型的使用方式:即直接通过对象字面量来重写整个原型
对象(这种方式会改变原型对象的构造器)
eg:
function Person(){
}
Person.prototype = {
constructor : Person , //必须得表示原型对象的构造器
name : "z3",
age :20,
job : "程序员",
say: function (){
alert("我是原型的函数!!");
}
};
调用:
var p1 = new Person();
alert(p1.name);
**ECMA5 中的 Object.defineProperty()方法可以为原型对象
重新加入构造器
//浏览器的要求:火狐4以上的版本、ie8 以上
//3个参数。参数1:重设构造器的对象
参数2:设置什么属性
参数3:options配置项
Object.defineProperty(Person.prototype , "constructor" , {
enumerable : false ,
value :Person
});
**原型的动态性(注意原型和创建实例的前后顺序)
注意:简单原型使用的顺序(实例对象必须在原型对象之后创建)
--------------5.5 原 型 对 象 存 在 的 问 题------------
**原型对象虽然可以对所有实例的属性和方法共享,但是它的局限性
也是很明显的,正是因为共享的特性,也导致原型存在的最大问题。
**我们一般组合使用“构造函数式”和“原型模式”,在实际开发中,这种
模式也是应用的最为广泛。
**动态原型模式:就是把信息都封装到函数中,这样体现了封装的概念。
**稳妥构造函数式:所谓稳妥模式就是没有公共属性,而且其他方法也不引用
this 对象,稳妥模式最适合在安全的环境中使用。如果你的程序对于安全性
要求很高,那儿非常适合这种模式。
-----------组 合 使 用 原 型 和 构 造 函 数 式------------
形式一的代码:
function Person(name , age , friends , job){
this.name = name;
this.age = age;
this.friends = friends;
this.job = job;
}
设置原型:
Person.prototype = {
constructor : Person,
sayName : function (){
alert(this.name);
}
};
调用:
var p1 = new Person("z3" , 20 , ["王五","赵六"] ,"技术总监");
var p2 = new Person("李四" , 25 , ["王五","赵六","赵7"] ,"boss");
alert(p1.friends);
p1.sayName();
alert(p2.friends);
p2.sayName();
----------------------动 态 原 型 模 式------------------------
function Person(name , age , friends , job){
this.name = name;
this.age = age;
this.friends = friends;
this.job = job;
//动态原型方法
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
}
}
}
-------------------稳 妥 构 造 函 数 式-------------------------
稳妥对象: durable object -------->适用于非常安全的环境中
定义:
function Person(name , age , job){
//创建一个要返回的对象
var obj = new Object();
//可以定义一些私有的变量和函数
var sex = "男";
var saySex = function(){
};
//添加一个对外的方法
obj.sayName = function(){
alert(name);
};
return obj;
}
调用:
var p1 = new Person("张三" , 20 , "技术员");
p1.sayName();
---------------------5.6 继 承---------------------------
**我们都知道构造函数、原型和实例直接的关系。如果我们让原型对象等于
另一个类型的实例,结果会怎么样呢? 显然此时的原型对象将包含一个指向
另一个原型的指针,相应的另一个原型中也包含一个指向另一个构造函数的指针。
**原型链:利用原型让一个引用类型继承另外一个引用类型的属性和方法
**简单继承(原型继承)
**类继承(模板继承或借用构造函数继承)
**混合使用继承实现完整的继承
-----------js中怎么去实现继承:采用原型链的概念-----------
//构造函数 原型对象 实例对象
//构造函数.prototype = 原型对象
原型对象.constructor = 构造函数的模板
原型对象.isPrototypeOf(实例对象) --》判断实例对象的原型是不是当前对象
构造函数 实例对象(类和实例)
//父类的构造函数 Sup
function Sup(name){
this.name = name;
}
//父类的原型对象
Sup.prototype = {
constructor : Sup;
sayName : function (){
alert(thid.name);
}
}
//子类的构造函数 sub
function Sub(age){
this.age = age;
}
//如果让子类的原型对象等于父类的实例,结果会怎么样呢?(实现了js的继承)
//显然此时的原型对象将包含一个指向另一个原型的指针
//相应的另一个原型中也包含一个指向另一个构造函数的指针。
//通过下面的代码:子类的原型对象的构造器变成了父类的构造器
Sub.prototype = new Sup();
--------------原 型 继 承-----------------
//父类
function Person(name , age){
this.name = name;
this.age = age;
}
//父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(sex){
this.sex = sex;
}
//继承已经实现了
Boy.prototype = new Person("z3" , 20);
《补充:原型继承的特点:既继承了父类的模板,又继承了父类的原型对象》
------------类 继 承-----------------
** 类继承的特点:只继承模板,不继承原型对象(借用构造函数的方式继承)
//父类
function Person(name ,age ){
this.name = name;
this.age = age;
}
//父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(name , age ,sex){
//call apply
Person.call(this , name , age); //利用 call 进行绑定
this.sex = sex;
}
var b = new Boy("张三" , 20 , "男");
-------------原型继承+ 借用构造函数式继承 = 混合继承---------------
//父类
function Person(name ,age ){
this.name = name;
this.age = age;
}
//父类的原型对象属性
Person.prototype.id = 10;
Person.prototype.sayName = function (){alert(this.name);};
//子类
function Boy(name , age ,sex){
//call apply
Person.call(this , name , age); //利用 call 进行绑定(借用构造函数式继承)
this.sex = sex;
}
Boy.prototype = new Person(); //原型继承(只剩下“父类的实例”和“父类的原型对象” 的关系了)----》继承父类的原型对象
var b = new Boy("张三" , 20 , "男");
***该模式做了三件事(混合继承的缺点):继承了父类的2次模板,继承了一次父类的原型对象。
-------------------5.8 继 承 示 例--------------------------
**经典继承方法实现(模拟extjs底层继承机制)
就做两件事:继承一次父类的模板,继承一次父类的原型对象
//只继承一次父类的原型对象
定义:
function extend(sub , sup){
//实现只继承父类的原型对象
//1.创建一个空函数:用一个空函数进行中转
var F = new Function();
F.prototype = sup.prototype;//实现空函数的原型对象和超类的原型对象转换
sub.prototype = new F(); //原型继承
sub.prototype.constructor = sub; //还原子类的构造器
//保存一下父类的原型对象:一方面是方便解耦
另一方面可以方便获得父类的原型对象
sub.superClass = sup.prototype;//自定义一个子类的静态属性,接收父类的原型对象
//判断父类的原型对象的构造器(加保险)
if(sup.prototype.constructor == Object.prototype.constructor){
sup.prototype.constructor = sup;//手动还原父类原型对象的构造器
}
}
//实现重写(其实就是 子类中重新写了父类中的方法)
eg:
Boy.prototype.sayHello = function(){
alert("hi javascript!");
}