目录
ES5:
1.对象和函数的原型
1.1认识对象的原型
每一个对象都有自己的原型。
如何获取我们的原型?
疑问:这个原型有什么用???
当我们通过[[get]]方式获取一个属性对应的value时,
1)它会优先在自己的对象中查找,如果找到直接返回
2)如果没有找到,那么会在原型对象中查找
可以看到,如果对象中不存在但是原型链中存在的话,返回值不会是undefined。如果都没有则返回undefined。
那么我们尝试通过对原型添加属性,再通过对象获取,可以看到原型里面添加了这个属性,也能获取到该属性。
1.2函数的原型
1.将函数看作一个普通对象(隐式原型)
2.将函数看成一个函数,其具备prototype(注意:对象是没有prototype!!)(显式原型)
prototype:只有函数才具备显式原型
回顾new操作new Person()
1.创建一个空对象
var obj = {}
2.将this指向该对象
this = obj
2.5.将函数的显式原型赋值给这个对象作为隐式原型(可作为第二步/第三步)
obj.__proto__ = Person.prototype
3.执行函数中的代码
4.将这个对象返回
1.3 将方法放到原型上
当我们创建一个构造函数的时候,如果创建对象,调用构造函数里面的方法时,每创建一个对象调用相应的方法,就会创建一个新的方法,其实这是没有必要当,当我们创建非常多的对象时,这样就会造成空间资源的浪费,是非常没有必要的。
由上面两张图可以看出来:每个对象调用构造函数里面的方法时,都会创建新的方法。所以我们可以用prototype来把方法创建在原型上。
如下:
当把方法放在原型上面,调用的方法都是同一个方法。
但是,为什么能够调用呢?
stu1的隐式原型就是Student的显式原型;
怎么查找自己的原型?(stu1举例)
*现在自己身上查找,没有找到;
*去原型上查找。
stu2的隐式原型也是Student的显示原型。
所以:当我们多个对象拥有共同的值时,可以将其放在构造函数的显式原型上,由构造函数创建出来的所有对象,都会共享这些属性。
1.4总结:
1.什么是函数的显式原型?
区分和对象原型的区别。
2.函数原型的作用?
再通过new操作创建对象时,将这个显式原型赋值给创建出来的对象的隐式原型。
3.案例Person,将所有的函数定义放在显式原型上,所有的对象都可以共享这些方法。
1.5思考:
属性为什么不放在原型上?
每个对象都有自己独有的属性。
2.显式原型的属性
2.1默认属性
事实上,原型对象都有一个属性:constructor
默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象。
打印后发现原型的数据类型是:object
刷新后发现,其中有constructor(构造器)属性。
首先提取出Person的prototype属性,对属性内的constructor进行打印,发现constructor指向了Person函数对象。所以这之间存在循环引用的。
实例对象举例:
可以看到,constructor属性都是同一个。
内存指向:
验证如下:
2.2重写原型对象
从上图可以看到有个缺点:
在未重写原型时,我们发现原型里面并不能枚举出来constructor属性,但是在重写原型后,是可以枚举出来constructor属性的,这就与浏览器的默认方法不一致,那么如何解决这个问题?
从上图可以看到:仍然添加了constructor属性,但是枚举时不显示。
3.面向对象的特性——继承
面向对象:一种编程方式。
面向对象有三大特性:封装、继承、多态
- 封装:我们将属性和方法封装到一个类中,可以称之为封装的过程;
例如:
var name = 'lu'; var age = 26; function running () {} function info = { name: 'lu', age: 26, running: function() {} } // 如果有很多类似的东西,我们也可以把其放到类里,这也叫封装
- 继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态前提(纯面向对象中);
- 多态:不同的对象在执行时表现出不同的形态;
那么继承能做什么呢?
- 继承可以帮助我们将重复的代码和逻辑抽取到父类中,子类只需要直接继承过来使用即可;
- 在很多编程语言中,继承也是多态的前提;
向上面这样,我们的学生和老师类都有很多共同的属性和方法,这个时候,只需要把共同的属性和方法放到父类里面,子类只需要继承。
所以这个时候讲解一下原型链的概念。
查找顺序:
1.obj上查找
2.obj.__proto__上查找
3.obj.__proto__.__proto__ -> null上查找(undefined)
对现有代码进行改造:
假设前两个原型添加message属性:
所以得到了如下结论:
这样一个链式查找过程就叫做:原型链。
所以继承类似,当我们在子类中查找不到时,就可以去父类中查找,所以就可以把所有的子类中相同的属性都放到父类中,以此类推。
原型链实现继承
什么地方是原型的尽头呢?
当我们一直打印原型__proto__属性会发现在最后返回null。
事实上,这个原型就是我们最顶层的原型了。
从Object上直接创建出来的对象原型都是null。
那么我们思考,该原型有什么特殊的吗?
1.该对象有原型属性,但是其原型属性已经指向null,已经是顶层原型了;
2.该对象上有很多默认的属性和方法;
我们先不使用继承创建一个Person类和一个Student类。
(下图的Student的s应该大写,后面改了,下面这张图懒得改了)
一个错误示例:
首先考虑方法的继承:
原型链的弊端:
1.我们通过直接打印对象是看不到这个属性的;
2.这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题;
3.不能给Person传递参数(让每个stu有自己的属性),因为这个对象是一次性创建的,没办法定制化;
考虑属性的继承:
组合继承:
方法继承+属性继承。
组合继承存在什么问题?
1.无论在什么情况下,都会调用两次父类构造函数;
- 一次在创建子类原型的时候;
- 一次在子类构造函数内部(就是每次创建子类实例的时候);
2.所有子类实例事实上都会拥有两份父类属性
- 一份在当前的实例自己里面(也就是Person本身),另一份在子类对应的原型对象中(也就是person.__proto__里面);
- 默认访问实例本身;
原型式继承的渊源
那么我们如何解决这个问题?
满足条件:
1.必须创建出来一个对象
2.这个对象的隐式原型必须指向父类的显式原型
3.将这个对象赋值给子类的显式原型
在真实的开发中,一般都会对其进行封装成函数,并且因为创建对象里面不存在constructor属性,也会给其添加该属性。