js的面向对象
1.面向对象语言都有一个标志,那就是类的概念。通过类可以创建任意多个具有相同属性的方法的对象。但是在ECMAScript中
没有相关类的概念。但是我们可以通过其他方式来模拟面向对象中的类。
-工厂模式:不细说
-构造函数模式:比如像ECMAScript中的Array,Object,Date等都是通过构造函数来创建的。
-动态原型模式
-寄生构造函数模式
-稳妥构造函数模式
<script type="text/javascript" charset="utf-8"> var obj = new Object(); obj.name = 'zs'; obj.sex = 'male'; obj.sayName = function() { alert('my name is zs'); }; // 类的概念 // 第一种形式 工厂模型 通过这个方法用来创建实例 function createPerson(name, sex, age) { var obj = new Object(); obj.name = name; obj.sex = sex; obj.age = age; obj.sayName = function(){ alert(this.name); }; return obj; } var p1 = createPerson(ls, male, 24); // 第二种形式,构造函数式, 函数的第一个字母大写,约定俗成的表示为一个类的模板 function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; this.sayName = function() { alert(this.name); }; } // 构造一个对象,一般使用new 关键字,并且传递参数 执行模板代码 返回对象 var p2 = new Person('ww', 'famale', 20); alert(p2.constructor == Person); // 返回结果为true alert(p2 instanceof Person); // 返回结果为true alert(p1 instanceof Object); // 返回结果为true // 创建对象的方式 // 1.当做构造函数去使用 即new的方式 var p3 = new Person('w1', 'male', 10); // 2.作为普通函数去调用 //在全局环境里定义属性并复制 直接定义在window上 var p4 = Person('w2', 'famale', 30); // 3.在另一个对象的作用域中调用 var o = new Object(); Person.call(o, '11', 'male', 30); </script>
2.原型
-我们创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途包含可以由特定类型
的所有实例共享的属性和方法。
-原型对象实际就是一个构造函数的实例对象,与普通的实例对象没有什么本质上的区别,js中每一个对象都有一个原型对象。不过
它比较特殊,该对象所包含的所有属性和方法能够供构造函数的所有实例共享,也就是其他编程语言所说的继承,而js通过原型对象来
继承,简称原型继承。静态原型继承:Object.prototype.[method field];
-简单实现Array中的push,pop方法。
<script type="text/javascript" charset="utf-8"> // 原型 prototype // 构造函数方式 function Person(name, age){ this.name = name; this.age = age; this.sayName = function() { alert('i am name'); }; } function sayName() { alert(this.name); } var p1 = new Person('zs', 20); var p2 = new Person('ls', 21); alert(p1.sayName == p2.sayName); //返回结果为false alert(p1.name == p2.name); //false p1.sayName(); alert(p1.sayName == p2.sayName); // 将Person中的sayName方法去掉之后,返回结果为true // 但是定义过多的全局变量 显然是不好的,于是我们采用下面的方法。 // 创建每一个函数的时候 都有一个Prototype属性,这个属性其实是一个指针。而这个指针总是指向一个对象。 // (这个对象的用途就是将特定的属性和方法 包含在内,起到一个所有实例所共享的作用) function Person1() { } var obj = Person1.prototype; alert(obj.constructor); // 返回值为function Person(){} obj.name = 'zs'; obj.age = 20; obj.sayName = function() { alert(this.name); }; var p3 = new Person(); var p4 = new Person(); alert(p3.name); //zs alert(p4.name); //zs alert(p3.age); // 20 alert(p4.age); // 20 // 构造函数 原型对象 实例对象 // 构造函数.prototype = 原型对象 // 原型对象.constructor = 构造函数 // 实例对象.prototype = 原型对象 obj.isPrototypeOf(p3); // 结果为true </script>
3.原型的详细方法
<script type="text/javascript" charset="utf-8"> // isPrototypeOf() 判断原型的方法 // Object.getPrototypeOf();根据实例对象获得原型对象 function Person() { } Person.prototype.name = 'zs'; Person.prototype.age = 20; Person.prototype.sayName = function() { alert('我是原型对象的方法'); }; var p1 = new Person(); var prototypeObj = Object.getPrototypeOf(p1); // 得到原型对象 var p2 = new Person(); p2.name = 'w5'; // 每次代码读取一个对象的属性的时候,会先进行一次搜索,搜索实例对象。如果没有,再去实例所对应的原型对象里搜索。 alert(p2.name); // 返回值为w5 // 如果非要获得原型对象中的属性,可以将实例中的属性删除掉。 delete p2.name; // 判断一个对象是原型属性还是实例属性 var p3 = new Person(); p3.hasOwnproperty('name'); //返回false 因为是原型属性 // in操作符:for-in // in操作符是判断属性是否存在于实例对象和原型对象中。如果有一个存在,则返回true,如果都不存在,才返回false var p5 = new Person(); alert('name' in p5); // 返回true 证明在p5对象中有name属性 var p6 = new Person(); p6.name = 'w3'; alert('name' in p6); //返回true // 判断一个属性是否存在原型中。第一个参数是当前对象,第二个参数是要判断的属性 function hasPrototypeProperty(object, name) { return !object.hasOwnProperty('name') && name in object; } // ECMA5新特性 Object.keys // 可以将对象中所有的属性找到,并返回一个数组 var m1 = new Person(); m1.name = 'zs'; m1.age = 20; var attribute = Object.keys(m1); alert(arrtibute); // 返回name和age var attribute = Object.keys(Person.prototype); //返回 name age sayName // ECMA5中 constructor属性 不可以被枚举 // Object.getOwnPropertyNames 方法的作用是 枚举出对象的全部属性,无论该属性能否被枚举 alert(Object.getOwnPropertyNames(m1));// constructor name age sayName </script>
4.模拟each方法
<script type="text/javascript" charset="utf-8"> // Array each方法 // ECMA5中,forEach方法。 var arr = [1,2,3,4,5]; //只适合遍历一维数组 arr.forEach(function(item, index, array) { alert(item); }); // 自己实现一个Array的Each方法,可以遍历多维数组 var arr1 = [1,2,3,[4,[5,[6]]]]; Array.prototype.each = function(fn){ try{ // 1.目的,遍历数组的每一项 // 计数器,记录当前遍历的元素位置 this.i || (this.i = 0); if (this.length > 0 && fn.constructor == Function) { // 循环遍历 while(this.i < this.length){ // 获取数组的每一项元素 var e = this[this.i]; // 如果当前元素获取到了,并且是一个数组 if (e && e.constructor == Array) { // 递归 e.each(fn); } else { fn.call(e, [e]); } this.i++; } // 释放内存,垃圾回收 this.i = null; } }catch(ex){ // do something } return this; }; arr1.each(function(item){ alert(item); }); </script>
5.简单原型的使用
直接通过对象字面来重写整个对象(会改变原型对象的构造器)。
ECMA5中的Object.defineProperty()方法可以为原型对象重新加入构造器。
原型的动态性(注意原型和创建实例的先后顺序)
<script type="text/javascript" charset="utf-8"> // 简单原型 function Person() { } Person.prototype = { name : 'zs', age : 18, job : 'doctor', say: function(){ alert('aa'); } }; var p1 = new Person(); alert(p1.name); p1.say(); alert(Person.prototype.constructor); //不加constructor : Person这句话,Object的构造函数,加上之后可以枚举(这样不对) // ECMA5给远行对象重新设置构造器方法 // 3个参数 参数1:重设构造器的对象 参数2:设置什么属性 参数3:option配置项 Object.defineProperty(Person.prototype, 'constructor', { enumerable : false, value : Person }); // 原型动态特性 function A() { } var a = new A(); A.prototype.say = function(){ alert('bbb'); }; a.say();//返回bbb 没有问题 function B() { } var b = new B(); B.prototype = { name : 'zs', age : 18, job : 'doctor', say: function(){ alert('aa'); } }; b.say(); // say is not a function 必须把var b = new B(); 拿到后面去写(实例对象必须在原型对象之后创建) </script>
6.原型对象存在的问题
-原型对象虽然可以对所有实例的属性和方法共享,但是他的局限性也是很明显的。正因为共享的特性,也导致了原型存在
的最大问题。
-我们一般组合使用构造函数式和原型模式,在实际开发中,这种模式也是应用的最稳广泛。
-动态原型模式:就是把信息都封装到函数中,这样体现了封装的概念。
-稳妥构造函数式:所谓稳妥模式就是没有公共属性,而且其他方法也不引用this对象。稳妥模式最适合在安全的环境中
使用。如果你的程序对于安全性要求很高,那么非常适合这种模式。
<script type="text/javascript" charset="utf-8"> // 原型概念:原型对象里的所有属性和方法,被所有构造函数实例化出来的对象所共享 function Person(){ } Person.prototype = { constructor:Person, name:'zs', age:20, job:'doctor', friends:['ls', 'ww'], sayName:function(){ alert('my name!'); } }; var p1 = new Person(); var p2 = new Person(); p1.friends.push('zl'); // p1增加friend之后,由于共享性,p2的friend也增加了,这样会存在问题。 alert(p1.friends); alert(p2.friends); // 组合使用原型和构造函数式(开发常用定义类的方式) function Person1(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); } }; // 动态原型模式:(让代码 所有属性和方法都封装到一起) function Person2(name, age, friends, job) { this.name = name; this.age = age; this.friends = friends; this.job = job; if(typeof sayName != 'function'){ Person.prototype.sayName = function() { alert(this.name); }; } } // 稳妥构造函数式:durable object(稳妥对象) // 1.没有公共属性 // 2.不能使用this function Person3(name, age, job) { // 创建一个要返回的对象 var obj = new Object(); var name = name; obj.sayName = function(){ alert(name); }; return obj; } </script>
7.继承
- 我们都知道构造函数,原型和实例的关系,如果我们让原型对象等于另一个类的实例,此时的原型对象将
包含一个指向另一个原型的指针,相应的另一个原型也包含着一个指向另一个构造函数的指针。
-原型链:利用原型让一个引用类型来继承另一个引用类型的属性和方法。
-简单继承
-类继承(模板继承或者借用构造函数继承)
-混合使用继承
<script type="text/javascript" charset="utf-8"> // js中如何实现继承 采用原型链的概念 // 构造函数 原型对象 实例对象 // 构造函数 .prototype = 原型对象 // 原型对象.constructor = 构造函数模板 // 原型对象.isprototypeOf(实例对象) 判断实例对象原型是否是该原型对象 // 如果我们让原型对象等于另一个类的实例 function Super(name) { this.name = name; } Super.prototype = { constructor : Sup, sayName : function() { alert(this.name); } }; function Sub(age){ this.age = age; } // 子类原型对象 等于父类的实例 // 子类的原型对象的构造器 变成了父类的构造器 Sub.prototype = new Sup(); </script>
<script type="text/javascript" charset="utf-8"> function Person(name, age){ this.name = name; this.age = age; } Person.prototype.id = 10; function Boy(sex){ this.sex = sex; } Boy.prototype = new Person('zs'); var b = new Boy(); alert(b.name); alert(b.id); // 类继承,只继承模板,不继承原型(借用构造函数方式继承) function Person1(name, age){ this.name = name; this.age = age; } Person1.prototype.id = 10; function Boy1(name, age, sex) { // call apply Person1.call(this, name, age); this.sex = sex; } var b1 = new Boy1('zs', 20, 'male'); alert(b1.name); alert(b1.id); // undefined 父类的原型对象并没有继承,这就是类继承的缺点 // 混合继承 通过原型继承 借用构造函数继承 = 混合继承 // 类继承,只继承模板,不继承原型(借用构造函数方式继承) function Person2(name, age){ this.name = name; this.age = age; } Person2.prototype.id = 10; Person2.prototype.sayName = function(){alert(this.name);}; function Boy2(name, age, sex) { // call apply Person2.call(this, name, age); this.sex = sex; } // 不传参数 ,父类的实例和子类的原型对象的关联 // 目的只是为了继承父类的原型对象 Boy2.prototype = new Person2(); </script>
8.模拟extjs底层继承方式
<script type="text/javascript" charset="utf-8"> // 混合继承:原型继承和借用构造函数继承 function Person(name, age){ this.name = name; this.age = age; } Person.prototype = { constructor : Person, sayHello : function(){alert('hello world');} }; function Boy(name, age, sex) { Person.call(this, name, age); // 也可以写成Boy.superclass.constructor.call(this, name, age); this.sex = sex; } Boy.prototype = new Person();//这里其实也继承了父类的模板,而且继承了父类的原型对象。 // 混合继承 做了3件事, 继承了两次父类模板,继承了一次原型对象。 // 混合模板的缺点,如果父类的属性特别多,这样很不方便 // 继承一次父类模板,继承一次父类原型对象,这样是最好的,我们自己写一个extend方法 function extend(sub, super){ // 目的:只继承父类的原型对象 // 1.用一个空函数进行中转 var F = new Function(); // 2.空函数的原型对象和父类的原型对象转换 f.prototype = super.prototype; // 3.将空函数的原型对象,给子类的原型对象 sub.prototype = new F(); // 4.还原子类的构造器 sub.prototype.constructor = sub; // 5.保存父类的原型对象 原因:方便解耦 方便获得父类的原型对象 sub.superclass = sup.prototype;// 利用子类的静态属性,存储父类的原型对象 // 判断父类的原型对象的构造器 if (sup.prototype.constructor == Object.prototype.constructor) { sup.prototype.constructor = sup; } } // 给子类加一个原型对象方法 Boy.prototype.sayHello = function(){ alert('hello sub!!'); }; b.sayHello(); //返回值为hello sub // 为了想要使用父类的sayHello方法 Boy.superclass.sayHello.call(b); // 这样就没问题了 </script>