前言:本文大体摘自:https://blog.csdn.net/sysuzhyupeng/article/details/54645430 这位CSDN博主写的十分的好,逻辑性很强。后面 “如何安全的扩展一个内置对象 ” 是我添加的。顺便把这位博主的 详解js中extend函数 和 call和apply上手分析 摘抄下来。
原型继承:利用原型中的成员可以被和其相关的对象共享这一特性,可以实现继承,这种实现继承的方式,就叫做原型继承.
原型继承是js的一种继承方式,原型继承是什么意思呢,其实就是说构造函数和子构造函数,或者说类和子类之间(当然js中不存在类),唯一的继承传递方式是通过原型,而不是其他语言直接通过extends(当然ES6的语法糖出现了extends)。所以你需要手写prototype。(封装手写prototype的方法请看我的另一篇文章详解js中extend函数) 我们先看一个简单的例子
具体看例子:
1 function Parent(){ 2 this.job = 'teacher'; 3 } 4 Parent.prototype.showJob = function(){ 5 alert(this.job); 6 } 7 var child = new Parent(); 8 child.showJob(); //'teacher'
很明显,这个例子中的child获得属性job和一个方法showJob,为什么会获得呢? 这时候来看看new Parent()到底做了什么
1 var obj = { }; //obj获得Parent的this引用 2 obj.job = 'teacher'; 3 obj.__proto__ = Parent.prototype; 4 var child = obj;
所以为什么child获得了属性job,是因为他执行了构造函数,child对象上获得了属性job。而为什么child获得了方法showJob, 是因为对象上有一个隐藏的原型__proto__ ,它指向了Parent.prototype。当我们在对象child上调用方法时,它首先检查对象自己是否具有这个方法,没有的话搜索自己的隐藏原型中有没有这个方法。
所以,原型继承是什么,它的本质是改变其他对象的__proto__,或者说让它丰富起来,以获得父构造函数或者祖先构造函数的方法,请看代码。
1 function Parent(){ 2 } 3 Parent.prototype.showJob = function(){} 4 function Child(){ 5 } 6 Child.prototype = new Parent(); 7 Child.prototype.constructor = Child; 8 var grandChild = new Child();
这时候grandChild获得了showJob方法,因为它的__proto__中有showJob方法,而为什么它的隐式原型中有这个方法呢,因为new Child()中grandChild.__proto__指向了Child的prototype, 至于为什么Child的prototype中有showJob方法,因为Child.prototype.__proto__等于Parent.prototype.
至于为什么有这么一句
1 Child.prototype.constructor = Child;
这是因为原本Child.prototype中有一个constructor属性指向Child本身,当执行Child.prototype = new Parent()的时候,Child.prototype.constructor指向了Parent,否则下一次new Child的时候,constructor的指向就会不正确,当然,这个在实际开发中即时漏掉也不会有大问题,因为我们很少会对constructor进行读写。
以上代码还有另一个问题,为什么我们要把showJob这个方法写在Parent.prototype上呢,如果写成如下
1 function Parent(){ 2 this.showJob = function(){} 3 } 4 function Child(){ 5 } 6 Child.prototype = new Parent();
当然这样写也可以,child.prototype对象上有了showJob方法,而不是child.prototype.__proto__,这对于我们原型链的继承并没有影响。然而这样写的方法一多,child.prototype对象上的方法就越多,如果new了多次的话,在内存上会比写在原型上多一些消耗。
那么在实际开发中,会怎么实现面向对象的原型继承呢。正常在我们拿到需求的时候,如果需求逻辑复杂,且在多个页面中有相似逻辑的时候,我们就会想到使用面向对象了,因为面向对象解决的就是逻辑的封装和复用。
假设页面A,页面B,页面C中存在相同逻辑,那么我们可以封装父构造函数对象
1 function Parent(){} 2 Parent.prototype = { 3 method1: function(){}, 4 method2: function(){}, 5 ... 6 } 7 function A(){ //页面A 8 this.A = A; 9 } 10 A.prototype = new Parent(); 11 A.prototype.otherMethod = function(){}; 12 13 var a = new A(); //使用对象 14 a.init...
首先将页面A,页面B,页面C中相同逻辑抽离,相同逻辑可以是同一个ajax请求返回数据,或者是数据格式化等等的相同操作。将这些方法在Parent.prototype中定义,至于A,B,C页面自己特有的方法,则在如A.prototype中定义。这样很好地了解决了我们的问题,逻辑清晰,代码复用性强。 如果在Parent方法参数中加入了回调callback,并且在callback中想调用子函数方法或者属性,可以参考我另一篇博文call和apply上手分析
hasOwnProperty
hasOwnProperty方法可以检测一个属性是存在于实例中,还是存在于原型中。
1 function Parent(){ 2 this.name = 'sysyzhyupeng'; 3 } 4 Parent.prototype.job = 'teacher'; 5 Parent.prototype.showJob = function(){ 6 } 7 var parent = new Parent(); 8 parent.hasOwnProperty('name'); // true 9 parent.hasOwnProperty('job'); // false 10 //方法也可以 11 parent.hasOwnProperty('showJob'); // false
in
in运算符和hasOwnProperty不同,只要存在在原型上或者对象上就返回true
1 function Parent(){ 2 this.name = 'sysyzhyupeng'; 3 } 4 Parent.prototype.job = 'teacher'; 5 Parent.prototype.showJob = function(){ 6 } 7 var parent = new Parent(); 8 'name' in parent; // true 9 'job' in parent // true 10 for(_key in parent){ 11 console.log(_key); // 'name', 'job', 'showJob' 12 }
在使用for-in循环时,返回的是所有能通过对象访问的且可枚举的属性。所有开发人正常定义的属性都是可枚举的,只有在IE8及更早版本除外。
Object.keys
ES5的Object.keys方法可以返回对象上的所有可枚举属性(注意只有对象上的,从原型上继承的没有)
如何安全的扩展一个内置对象
给内置对象新增成员和方法,叫做扩展内置对象。这种行为是不可取的。因为原生的内置对象本身就有一些方法和属性,在一个项目中,如果你替换了这些方法,别人就用不到了,容易造成代码崩溃,不宜维护。
如何安全的扩展一个内置对象呢? 就比如 内置对象Array, 我们既想用它本身的一些方法,又想给它添加一个sayHello()方法。这样到底怎么去做呢?
首先我们创建一个对象,让这个对象继承内置对象Array,这样Array数组里面的方法就可以在我们创建的对象中使用,然后在我们创建的对象添加我们自定义的方法。这样无论我们怎么修改自己创建的对象里面的方法,都不会影响到内置对象。
1 function MyArray() { //自己定义函数对象MyArray 2 //我自己定义的属性 3 this.name = "我是一个数组"; 4 this.sayHello = function () { 5 console.log("我的sayHello方法"); 6 } 7 } 8 9 var arr = new Array(); //内置对象Array创建的对象arr 10 11 MyArray.prototype = arr; // 替换原型对象 也就是原型对象继承数组,我的数组中就有了原生数组对象的所有的属性和方法 12 MyArray.prototype.hello =function () { 13 console.log("大家好,我是自定义对象MyArray的原型中的hello方法"); 14 } 15 16 var myArr = new MyArray(); //myArr这个对象就继承自arr 17 18 myArr.push(100); //原生内置对象Array自带的push的方法 19 myArr.push(2,3,4,5) 20 console.log(myArr); 21 myArr.sayHello(); 22 myArr.hello();