浅谈对象
面向对象原型链继承这块,应该算是javascript中最难理解的部分了,小弟脑子比较难转弯,也是看了好久视频,博文,慢慢的才有了自己的理解,现在记录一下学习的内容和总结。首先第一节应该说说对象这个东西了,js中对象和其他语言还是有所不同的,现在切入正题,开始浅谈对象。
什么是对象
定义(ECMA-262):无序属性的集合,其属性可以包含基本值、对象或者函数。
通过定义可以看出来,对象是属性的集合,这些属性又会是一个基本值,一个函数或者又是一个新的对象。记住,函数也是对象,了解这点以后原型链会很好理解。
对象结构
大概解释一下这个图,var obj = {}; obj.x = 1; obj.y = 2;这段代码先定义了一个obj空对象,然后定义了两个属性x和y,每一个属性又都是一个键值对的对象。有writable,enumberable,configurable,value四个属性和一对get/set方法。一个对象又有三个默认的属性__proto__,__class__,__extensible__,其中__proto__属性是指向创建该对象的函数(方法)的prototype对象,如第二个obj的__proto__对象会指向fn的构造函数的prototype属性。好吧,有点绕,后续会绕出来的。。。
创建对象
1.new
首先,需要说明的是,对象其实都是通过函数创建的,如上图中的var obj=new fn();,再比如这种:var obj={},arr=[];,其实这两种创建对象的方法的实质分别为var obj=new Object(),arr = new Array();,所以对象都是通过函数创建的这个话就不足为奇了。
上个模块我们知道了函数会默认有prototype这个属性,如下:
这个prototype的属性值是个对象,即属性的集合,该属性一般情况下只有一个constructor属性,指向函数本身。
我们也可以在自定义函数的prototype中添加一些属性,看代码:
1 function Fn(){ 2 3 }; 4 Fn.prototype.x=1; 5 var f1 = new Fn();
这时,实例化的f1对象就可以访问Fn()的原型属性x的值了。也就是说,f1对象是从Fn函数new出来的,这样f1对象就可以调用Fn.prototype中的属性了。这是因为每个对象都有一个__proto__属性(chrome将这个属性暴露了出来,大家可以在chrome浏览器上看一下这个属性),这个属性引用了创建该对象的函数的prototype属性,可以得到f1.__proto__===Fn.prototype.__proto__会在下节详细说明,这个算是原型链继承的关键(一条看不见的链条连接了所有有血缘关系的家族~)。本节还是继续扯函数的prototype属性。
那么原型这块大体上可以用下图来表示:
一句话概括:实例化出来的对象的__proto__属性连接实例函数的prototype属性,这样实例化对象就可以通过看不见的那根链条访问prototype属性。那么问题来了:怎么判断一个对象的属性值是原型上的属性还是自身的属性,js为我们提供了这么一个方法:hasOwnProperty()和in操作符,如上,使用in操作符可以判断属性是否存在(包含原型链上属性),hasOwnProperty()方法也是判断属性是否存在(不包含原型属性)。
2.Object.create()
根据代码对比来理解一下:
1 function Person (name) { 2 this.name=name 3 }; 4 Person.prototype.sayName=function(){ 5 console.log(this.name) 6 }; 7 function Teacher(name){ 8 this.name=name 9 }; 10 Teacher.prototype = Person.prototype; 11 var teacher = new Teacher("James"); 12 teacher.sayName(); //James 13 14 Teacher.prototype.teach = function(course){ 15 console.log("I teach "+course) 16 } 17 teacher.teach("English"); //I teach English 18 19 var person = new Person("Lebro"); 20 person.teach("Chinese"); //I teach Chinese
如上第10行代码,将Person的原型直接赋值给Teacher的原型看似是没问题的,对象teacher会继承Person()的sayName()方法,但是给Teacher拓展方法时就会出现共享的问题,Teacher.prototype 会和 Person.prototype指向一处引用,这样拓展Teacher的原型方法就相当于给Person的原型添加了方法,显然不是我们想要的;
所以引入了Object.create()方法免除这个问题,实质还是对prototype属性的赋值:
1 function Person (name) { 2 this.name=name 3 }; 4 Person.prototype.sayName=function(){ 5 console.log(this.name) 6 }; 7 function Teacher(name){ 8 this.name=name 9 }; 10 Teacher.prototype = Object.create(Person.prototype); 11 var teacher = new Teacher("James"); 12 teacher.sayName(); //James 13 14 Teacher.prototype.teach = function(course){ 15 console.log("I teach "+course) 16 } 17 teacher.teach("English"); //I teach English 18 19 var person = new Person("Lebro"); 20 person.teach("Chinese"); //Uncaught TypeError: person.teach is not a function
如上代码,用Object.create()方法代替直接赋值就会避免这个问题,因此这个方法实际上就是先创建一个空对象,把参数Person.prototype的值有这个空对象进行传递给Teacher.prototype,这样就不会有原型共享的问题了,使用Object.create()方法时考虑到这一点就不会出错了(没有研究源码,只是本人暂时这么理解的,欢迎指正)。
第一节就先说这么多,没有按照常用思维来写,因为本次执笔只是本人的一次学习总结,是按照我从不懂到慢慢缕清整个思路的一个过程来写的,可能不会满足所有人的学习模式,而且最基础的一些对象方面的知识也没提到,因为能看到原型继承这块我觉得最基本的基础应该是具有的,所以,请尽情吐槽吧~~~下节说一下对象的属性操作,包括读写,删除,枚举,检测。下节再见了。