一、instanceof
1.1 基本用法
1 | //特例: |
1.2 底层实现
1、instanceof的原理,并用代码实现
知识点:如果A沿着原型链能找到B.prototype,那么A instanceof B
为true
解法:遍历A的原型链,如果找到B.proptype,返回true,否则false
1 | //方法一: |
知识点: 如果在A对象上没有找到x属性,那么会沿着原型链找x属性
解法: 明确foo和F变量的原型链,沿着原型链找a属性和b属性
1 | let foo = {} |
二、ES5继承
2.1 示例代码
2.1.1 基本概念
为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如jslint将可以帮你检测到漏写的new
。
1、当对象没有某个属性的时候,便会顺着原型链去找。
1 | function Person(name, age){ |
2、Student()方法举例
如果不写new
,这就是一个普通函数,它返回undefined
。
但是,如果写了new
,它就变成了一个构造函数,它绑定的this
指向新创建的对象,并默认返回this
,也就是说,不需要在最后写return this
。
1 | function Student(name) { |
1 | 函数实现Student的方法: |
1 | class关键字来编写Student: |
2.1.2 属性遮蔽
1 | // 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的: |
2.2 prototype
给其它对象提供共享属性的对象
2.3 __ proto __
1、__proto__和constructor是对象独有的。2、prototype属性是函数独有的;
ECMAScript 规范描述prototype
是一个隐式引用,但之前的一些浏览器,已经私自实现了__proto__
这个属性,使得可以通过obj.__proto__
这个显式的属性访问,访问到被定义为隐式属性的prototype
。
ECMAScript 规范说 prototype 应当是一个隐式引用:
- 通过
Object.getPrototypeOf(obj)
间接访问指定对象的prototype
对象。 - 通过
Object.setPrototypeOf(obj, anotherObj)
间接设置指定对象的 prototype 对象。 - 部分浏览器提前开了
__proto__
的口子,使得可以通过obj.__proto__
直接访问原型,通过obj.__proto__ = anotherObj
直接设置原型。 - ECMAScript 2015 规范只好向事实低头,将
__proto__
属性纳入了规范的一部分。
2.4 constructor
constructor属性也是对象所独有的,它是一个对象指向一个函数,这个函数就是该对象的构造函数
必须有constructor()
方法,如果没有显式定义,一个空的constructor()
方法会被默认添加。
三、ES6继承
3.1 Class
1 | class Point { |
由于类的方法都定义在prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。Object.assign()
方法可以很方便地一次向类添加多个方法。
1 | class Point { |
3.2 toString()方法
1 | 类的内部所有定义的方法,都是不可枚举的(non-enumerable) |
1 | ES5 的写法,toString()方法就是可枚举的。 |
3.3 constructor()方法
1 | constructor()方法默认返回实例对象(即this),完全可以指定返回另外一个对象 |
静态方法包含this关键字,这个this指的是类,而不是实例。
1 | class Foo { |
3.4 super 关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
- 第一种情况,
super
作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。 - 第二种情况,
super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。1
2
3
4
5
6
7class A {}
class B extends A {
constructor() {
super();
}
}super
虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super
内部的this指的是B的实例。
因此super()
相当于:A.prototype.constructor.call(this)
。
super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super
调用的。
1 | ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。 |
由于this指向子类实例,所以如果通过super
对某个属性赋值,这时super
就是this,赋值的属性会变成子类实例的属性。
1 | class A { |
3.5 Class继承
Class
作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链。
- 子类的
__proto__
属性,表示构造函数的继承,总是指向父类。 - 子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。1
2
3
4
5
6class A {}
class B extends A {}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
1 | //继承 |
1 | class Animal { |
四、继承方式
4.1 原型链继承
缺点:属性被共用;无法传参
1 | function Child(){} |
4.2 构造函数继承
缺点:方法会重新创建
1 | function Child(name){ |
4.3 组合继承
结合上述两种,较为常用
1 | function Child(){ |
4.4 寄生式继承
缺点:方法会重新创建
1 | function createObj (o) { |
4.5 寄生组合式继承
缺点:调用两次父构造函数
1 | function Child(name){ |
五、ES5继承与ES6继承的区别
- ES5:先创建子类的实例对象this,再将父类的属性/方法添加上去
Parent.call(this)
- ES6:先创建父类实例this,再用子类的构造函数修改this
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this)
)。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super
方法),然后再用子类的构造函数修改this。class
的职责是充当创建 object 的模板, 通常来说,data 数据是由 instance
承载,而 methods 行为/方法则在 class
里。
也就是说,基于 class 的继承,继承的是行为和结构,但没有继承数据。
而基于 prototype
的继承,可以继承数据、结构和行为三者。
1 | 如果子类没有定义constructor方法,这个方法会被默认添加, |
六、new
6.1 构造函数创建对象
红宝书:
使用 new
操作符调用构造函数,实际上会经历一下4个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
6.2 实现new方法
- 创建一个空对象
- 从参数中删除第一个元素并返回,第一个参数(就是构造函数),剩下就是参数
- 链接到原型
- 调用构造函数,把this绑定到新对象上
- 返回构造函数调用的结果,或者新对象
new 手写版本一
1 | createNew(Person, {name: 'Tom', age:20}) |
new 手写版本二
1 | const createInstance = (Constructor, ...args) => { |
6.3 箭头函数的this指向
- 箭头函数不绑定this,箭头函数中的this相当于普通变量。
- 箭头函数的this寻值行为与普通变量相同,在作用域中逐级寻找。
- 箭头函数的this无法通过
bind,call,apply
来直接修改。 - 改变作用域中this的指向可以改变箭头函数的this
- eg.
function closure(){()=>{//code }}
,在此例中,我们通过改变封包环境closure.bind(another)()
,来改变箭头函数this的指向。
七、题目
Proxy
和Object.defineproperty
的区别- 写出结果:代码来源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27function Foo() {
getName = function() {
alert(1);
};
return this;
}
Foo.getName = function() {
alert(2);
};
Foo.prototype.getName = function() {
alert(3);
};
var getName = function() {
alert(4);
};
function getName() {
alert(5);
}
//请写出以下输出结果:
Foo.getName();// 2
getName();// 4
Foo().getName();// 1
getName();//1
new Foo.getName();// 2
new Foo().getName();// 3
new new Foo().getName();// 3