构造函数和普通函数

相同点:

定义的方式,都是通过function来定义

执行时参数传递方式一样

都会形成私有上下文,都有私有变量

不同点:

构造函数执行通过new来执行,在执行时浏览器会在当前上下文中创建一个实例对象,并且会让函数中的this指向到这个实例对象中,而普通函数中的this指向为window。

函数如过没有返回值或返回值为基本类型值,则返回undefined或你指定的返回值;如果是构造函数,它没有返回值,则返回当前的实例对象,如果有返回值,返回值是基本类型则也是返回当前对象,当返回的是引用类型,则返回你所指定的类型。

构造函数:箭头函数没有this,不能当构造函数,箭头函数没有prototype;ES6简写没有prototype;

原型与原型链

箭头函数没有prototype原型属性;ES6简写没有prototype;

每一个函数数据类型都自带一个prototype原型属性,属性值是一个对象,并且对象中自带一个属性为:constructor,属性值是当前构造函数本身。

每一个对象数据类型都自带一个__proto__原型链属性,属性值是所属类的prototype原型对象。

prototype也是一个对象,每一个类new出来的实例对象从prototype原型对象中继承属性;对象中包含 __proto__指向类的原型对象,原型中包含constructor属性,指向构造函数。

原型链:在对象的私有属性中查找结果,如果私有中存在就是用私有的(优先级最高),如果不存在,则基于__proto__找所属类prototype上的属性,再找prototype的 __proto__上的属性,直到Object.prototype为止,报错。原型链查找。

image-1

获取对象的原型方式:

1
2
3
4
5
6
function Fn(){};
Fn.prototype.a = function (){};
const o = new Fn;
console.log(Fn.prototype);
console.log(o.__proto__);
console.log(Object.getPrototypeOf(o));

Object.create():会创建一个空对象,基于传入的对象来创建,

获取与设置原型
1
2
3
4
5
6
7
8
9
// 设置一个指定的对象的原型
Object.setPrototypeOf(对象父对象);
// 指定对象的原型
Object.getPrototypeOf(对象)
let child = {};
let parent = { id: 100 };
// child.__proto__ = parent;//IE不兼容
// 设置child.__proto__的原型指向到parent
Object.setPrototypeOf(child, parent);
原型重定向

原型重定向会引发constructor丢失,手动添加constructor属性,是可以通过for in枚举出来的,默认这个属性是不可以的。

设置对象属性不可枚举

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 给对象添加一个属性,并且给属性添加一些描述, 不可枚举
Object.defineProperty(Fn.prototype, 'constructor', {
    // 指定值
    value: Fn,
    // 不可枚举
    enumerable: false
})
// 如果你要用prototype重定向,推荐写法
Object.assign(Fn.prototype, {
    getA() {
        console.log('getA')
    },
    getB() {
        console.log('getB')
    }
})
原型检测

instanceof 检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,只要能找到则为真,检查对象是否属于某个类;使用isPrototypeOf检测一个对象是否是另一个对象的原型链中;

在ES6以后,先查找当前对象是否有Symbol,通过 Symbol.hasInstance方法,把要比较的对象放到此方法中,返回true/false。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let arr = [1, 2, 3]
console.log(myInstanceof(Array, arr))
// 封装,只适用于es6之后
function myInstanceof(FC, obj) {
    // 在类中查找是否存在 Symbol.hasInstance方法
    let ins = FC[Symbol.hasInstance]
    if (ins && typeof ins === 'function') {
        // 调用
        return ins.call(FC, obj)
    }
}
原型扩展方法

可以实现链式写法、限定调取方法的类型,必须是指定类型的实例

继承

封装:类也是一个函数,把实现一个功能的代码进行封装,以此实现“低耦合高内聚”。

多态:重写: 子类重写父类上的方法(伴随着继承运行的); 重载: 相同的方法,由于参数或者返回值不同,具备了不同的功能(js中不具备严格意义上的重载)。

继承: 子类继承父类中的方法和属性。

方式

原型继承 (让子类的原型 = 父类实例)、call继承(只能继承父类中私有的,不能继承父类中公共的)、寄生组合式继承(call继承 + 原型继承)、混合继承(间接实现多继承)

原型继承:把父类中的私有方法,都在子类中变成公有的,让子类的prototype = 父类。

call/apply继承:把父类当普通函数执行,所以prototype丢失,但是让this指向还要指向到当前的对象中。