私有属性和方法:仅仅函数内使用
公有属性和方法:函数可以使用(push、shift)
静态属性和方法:定义在构造函数上的属性和方法,不用实例调用(Promise.all()、Promise.race()、Object.assign()、Array.from())
构造函数(ES6之前定义)
很好区分:
在函数内用var定义的就是私有的;
在函数内用this承接的,或者设置在构造函数原型对象上比如Person.prototype.xxx就是公有;
原型对象的函数和变量不会在实例对象上,但是实例对象可以访问和调用它们。
区分定义在实例对象和原型对象上的属性和方法
for...in...:自身和原型
Object.keys():自身
Object.getOwnPropertyNames():自身
原型链查找:当访问一个对象的属性/方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。
class
- class基本概念
- 当使用class的时候,它会默认调用constructor这个函数,来接收一些参数,并构造出一个新的实例对象(this)并将它返回。
- 如果没有定义constructor,会隐式生成构造函数;
- class中几种定义属性的区别::
- 在constructor中var一个变量,它只存在于constructor这个构造函数中
- 在constructor中使用this定义的属性和方法会被定义到实例上
- 在class中使用=来定义一个属性和方法,效果与第二点相同,会被定义到实例上
- 在class中直接定义一个方法,会被添加到原型对象prototype上
- 在class中使用了static修饰符定义的属性和方法被认为是静态的,被添加到类本身,不会添加到实例上
- other:
- class本质虽然是个函数,但是并不会像函数一样提升至作用域最顶层
- 如遇class中箭头函数等题目请参照构造函数来处理
在构造函数中如果使用了箭头函数的话,this指向的就是这个实例对象。
class Cat {
constructor () {
this.name = 'guaiguai'
var type = 'constructor'
}
type = 'class'
getType = () => {
console.log(this.type)
console.log(type)
}
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.getType()
console.log(guaiguai)
转化为构造函数
function Cat () {
this.type = 'class'
this.getType = () => {
console.log(this.type) //this指向的是这个实例对象,所以输出class
console.log(type)
}}
Cat.prototype.constructor = function () {
this.name = 'guaiguai'
var type = 'constructor'
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.constructor()
guaiguai.getType()
console.log(guaiguai)
'class'
'window'
Cat {type: "class", name: "guaiguai", getType: ƒ}
- 使用class生成的实例对象,也会有沿着原型链查找的功能
下面正式谈论继承
对象封装、继承、多态
原型链
继承
原型链继承
Son.prototype = new Person()
优点:继承了父类的模板和父类的原型对象
缺点:
- 如果要给子类的原型上新增属性和方法,就必须放在Child.prototype = new Parent()这样的语句后面
- 无法实现多继承(因为已经指定了原型对象了)
- 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响(这点从修改child1.colors可以看出来)
- 创建子类时,无法向父类构造函数传参数(这点从child1.name可以看出来)
构造继承
Person.call(this, arg)
function Person(){
}
function Son(){
}
优点:
解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数(见题目3.3)
缺点:
- 构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法(见题目3.4)
- 实例并不是父类的实例,只是子类的实例(见题目3.5)
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
组合继承
Person.call(this, arg)
Son.prototype = new Person()
实现方式:
- 使用原型链继承来保证子类能继承到父类原型中的属性和方法
- 使用构造继承来保证子类能继承到父类的实例属性和方法
优点:
- 可以继承父类实例属性和方法,也能够继承父类原型属性和方法
- 弥补了原型链继承中引用属性共享的问题
- 可传参,可复用
缺点:
- 使用组合继承时,父类构造函数会被调用两次
- 并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。
constructor总结:
- constructor它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象。
- 它并不会影响任何JS内部属性,
只是用来标示一下某个实例是由哪个构造函数产生的而已。 - 如果我们使用了原型链继承或者组合继承无意间修改了constructor的指向,那么出于编程习惯,我们最好将它修改为正确的构造函数。
。
寄生组合继承
Child.prototype = Object.create(Parent.prototype) Child.prototype.constructor = Child
有没有一种办法直接跳过父类实例上的属性,直接继承父类实例上的属性?也就是说我们需要一干净的实例对象,作为子类的原型。
干净就想到Object.create(proto, propertiesObject)
- 参数一,需要指定的原型对象(作用:指定你需要创建的这个对象的原型对象是谁)
- 参数二,可选参数,给新对象自身添加新属性以及描述器
怎么说呢?
就好比,我们使用var parent1 = new Parent()创建了一个对象parent1,那parent1.__proto__就是Parent.prototype。
使用var obj = new Object()创建了一个对象obj,那obj.__proto__就是Object.prototype。
而这个Object.create()屌了,它现在能指定你新建对象的__proto__。
这正不是我们想要的吗?我们现在只想要一个干净并且能链接到父类原型链上的对象。 Child.prototype = Object.create(Parent.prototype) ,这样就只会创建一份父类属性,调用一次父类构造函数。
寄生组合继承算是ES6之前一种比较完美的继承方式吧。
它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。
所以它拥有了上述所有继承方式的优点:
- 只调用了一次父类构造函数,只创建了一份父类属性
- 子类可以用到父类原型链上的属性和方法
- 能够正常的使用instanceOf和isPrototypeOf方法
class继承
class继承的效果和之前我们介绍过的寄生组合继承方式一样。
关键内容:extends和super
class Child extends Parent {}
// 等同于
class Child extends Parent {
constructor (...args) {
super(...args)
}
}
ES6中的继承:
- 主要是依赖extends关键字来实现继承,且继承的效果类似于寄生组合继承
- 使用了extends实现继承不一定要constructor和super,因为没有的话会默认产生并调用它们
- extends后面接着的目标不一定是class,只要是个有prototype属性的函数就可以了
super相关:
- 在实现继承时,如果子类中有constructor函数,
必须得在constructor中调用一下super函数(不能少super),因为它就是用来产生实例this的。 - super有两种调用方式:
当成函数调用和当成对象来调用。
函数调用:
- super当成函数调用时,
代表父类的构造函数,但返回的是子类的实例,也就是此时super内部的this指向子类。在子类的constructor中super()就相当于是Parent.constructor.call(this)。 - 子类constructor中如果要使用
this的话就必须放到super()之后,super当成函数调用时只能在子类的construtor中使用。
super当成对象调用:
- 普通函数(constructor函数、原型对象上函数、实例对象上函数)中super对象指向
父类的原型对象 - 静态函数中super对象指向
父类 - 且通过super调用父类的方法时,super会绑定子类的this,就相当于是Parent.prototype.fn.call(this)。
class Parent {
constructor (name) {
this.name = name
}
getName () {
console.log(this.name)
}
}
Parent.prototype.getSex = function () {
console.log('boy')
}
Parent.getColors = function () {
console.log(['white'])
}
class Child extends Parent {
constructor (name) {
super(name)
super.getName()
}
instanceFn () {
super.getSex() //super代表Parent.prototype,也就是父类原型对象
}
static staticFn () {
super.getColors() //super代表Parent,也就是父类对象
}
}
var child1 = new Child('child1')
child1.instanceFn()
Child.staticFn()
console.log(child1)
'child1'
'boy'
['white']
Child{ name: 'child1' }
new.target: 指向当前正在执行的那个函数,你可以理解为new后面的那个函数
ES5继承和ES6继承的区别:
- 在ES5中的继承(例如构造继承、寄生组合继承) ,实质上是先创造子类的实例对象this,然后再将父类的属性和方法添加到this上(使用的是Parent.call(this))。
- 而在ES6中却不是这样的,它实质是先创造父类的实例对象this(也就是使用super()),然后再
用子类的构造函数去修改this。