一、类
1、介绍
类:对一群具有相同特征的对象的集合的描述。
内置类:
每一种数据类型都有一个自己所属的内置类,String、Boolean、Symbol、BinInt、Object、Array、RegExp、Date、Function 等,类在实例化时需要使用NEW关键字,Symbol特殊,不能通过new关键字来实例化,直接使用即可 let sym=Symbol()
自定义类:
在es6之前,通过创建一个构造函数来定义一个类,此函数称为构造函数,在es6之后js提供了class关键词来定义一个自定义。
在es6之前,我们定义类都是通过function来完成。
规范:函数定义为一个类,则首字母大写。
2、构造函数和普通函数的区别
相同点:
- 定义的方式相同,都是通过function来定义
- 函数执行时传递参数的方式一样
- 都会形成私有上下文,也都存在私有变量
不同点:
- 构造函数执行通过new来执行,在执行时浏览器会在当前上下文中创建一个实例对象,并且会让函数中的this指向到这个实例对象,而普通函数中的this指向window
- 函数如果没有返回值或返回值为基本数据类型,则返回undefined或者你指定的返回值;如果是构造函数,他没有返回值,则返回当前的实例对象,如果有返回值,返回值是基本数据类型则也是返回当前对象,只有返回的是引用类型,才返回你所指定的类型。
注:构造函数中,尽量不要去写return
3、创建类
//首字母大写,当做构造函数,它就是一个类
function Myclass(id,name){
//当前对象的成员属性
this._id = id;
this._name = name;
}
//实例化类
let c = Myclass(100,'tom');
二、原型与原型链
1、原型、原型链属性
每一个函数数据类型(箭头函数除外),都自带一个prototype原型属性,属性值是一个对象(Function.prototype除外),并且原型对象中自带一个属性:constructor,属性值是当前构造函数本身。
每一个对象数据类型都自带一个属性__proto__,叫做原型链属性,属性值指向所属类的原型对象(prototype)。
- 可以将原型理解为对象的父亲,对象从所属类的原型对象来继承属性
- prototype也是一个对象
- 使用原型对象为多个对象共享属性或方法,这样可以解决通过构造函数创建对象时复制多个函数造成的内存占用问题
- 如果对象本身不存在属性或方法将到原型上查找
- 原型包含 constructor 属性,指向构造函数
- 对象包含 proto 属性指向类的原型对象
2、原型链
首先查找自己的私有属性,私有中存在则使用私有的,私有中不存在,则默认基于__proto__找所属类prototype上的,如果还没有,则基于prototype上的__proto__继续向上查找,直到找到Object.prototype为止,我们把这套查找机制,称之为原型链。
3、使用原型来共享类中的方法
函数既有prototype属性,也有__proto__属性。
function Myclass(id){
this.id = id;
}
//共享方法
Myclass.prototype.getId = function(){
return "我的id是" + this.id;
}
//通过构造函数new出来的对象的__proto__属性指向构造函数的prototype属性
let c = new Myclass(100);
console.log(c.getId());
三、获取与设置原型
设置指定对象的原型
语法:Object.setPrototypeOf(对象,父对象)
let child = {a:100};
let parent = {b:200,c:300};
// Object.setPrototypeOf(child,parent);
//__proto__有兼容问题,IE不兼容
child.__proto__ = parent; //相当于这个写法
console.log(child);
获取指定对象的原型
语法: Object.getPrototypeOf(对象)
let child = {a:100};
let parent = {b:200,c:300};
Object.setPrototypeOf(child,parent);
console.log(Object.getPrototypeOf(child));
四、原型重定向
当给构造函数的原型对象中添加属性方法较多时,只能一条一条的书写,比较麻烦,可以直接将原型对象指向一个新的对象,在这个新的对象中写好公共的属性和方法。
优点:把原型上为其实例提供的公共属性和方法,全部写在一起,提高了整体性和模块性。
缺点: 重定向后的原型对象中,缺乏了constructor 属性,需要手动添加 constructor
为解决上述缺点,使用原型重定向时的推荐写法:
Object.assign(Myclass.prototype,{})
五、原型检测
instanceof 检测构造函数的prototype 属性是否出现在某个实例对象的原型链上
es6之前原理:在原型链中去检测,只要能找到则为真;
es6+ 先查找当前类是否有Symbol.hasInstance方法,把要比较的对象放入此方法中,查找返回true/false。
// 封装,只适用于es6之后
function myInstanceof(FC, obj) {
// 在类中查找是否存在 Symbol.hasInstance方法
let ins = FC[Symbol.hasInstance]
if (ins && typeof ins === 'function') {
// 调用
return ins.call(FC, obj)
}
}
function A() {}
function B() {}
function C() {}
const c = new C();
B.prototype = c;
const b = new B();
A.prototype = b;
const a = new A();
console.dir(a instanceof A); //true
console.dir(a instanceof B); //true
console.dir(a instanceof C); //true
console.dir(b instanceof C); //true
console.dir(c instanceof A); //false
使用 isPrototypeOf 检测一个对象是否在另一个对象的原型链中
const a = {};
const b = {};
const c = {};
Object.setPrototypeOf(a, b);
Object.setPrototypeOf(b, c);
console.log(b.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(a)); //true
console.log(c.isPrototypeOf(b)); //true
六、原型扩展方法
内置类的原型上提供了很多内置方法,但是这些方法不一定完全满足业务需求,有时需要我们自己扩展一些方法。
优点:
- 调用方便
- 可以实现链式写法
- 限定调用方法的类型,必须是指定类型的实例
缺点:
- 自己扩展的方法,容易覆盖内置方法(建议方法前加前缀)
//给数组扩展一个去重的方法
Array.prototype.myUnique = function () {
let arr = [];
for (let i = 0; i < this.length; i++) {
if (this.indexOf(this[i]) === i) {
arr.push(this[i]);
}
}
return arr;
}
//给数组扩展一个取最大值的方法
Array.prototype.myMax = function () {
return Math.max.apply(null, this);
// return Math.max.call(null, ...this);
};
//给字符串扩展一个将query字符串转成对象的方法
String.prototype.parseQuery = function () {
let arr = this.split(/&/);
return arr.reduce((val, item) => {
let prop = item.split('=');
val[prop[0]] = prop[1];
return val;
}, {})
}
//给对象扩展一个把对象转成query字符串的方法
Object.prototype.parseStr = function(){
let str = '';
for(let [key,value] of Object.entries(this)){
str = str + "&" + key +"=" +value;
}
return str.slice(1);
}
七、继承
JS本身是基于面向对象开发的编程语言
- 封装:类也是一个函数,把实现一个功能的代码进行封装,以此实现“低耦合高内聚”
- 多态:
== 重写:子类重写父类上的方法(伴随着继承运行的)
== 重载:相同的方法,由于参数或者返回值不同,具备了不同的功能(js中不具备严格意义上的重载) - 继承:子类继承父类中的属性和方法
JS中的继承方案:
- 原型继承 (让子类的原型 = 父类的实例)
- call继承 (只能继承父类中私有的,不能继承父类中公共的)
- 寄生组合式继承 (call继承 + 原型继承)
1、原型继承
原型继承:让子类的原型中的__proto__指向父类的实例
原型继承的缺点:父类中的私有属性都变成了子类中共有的
function Child(x){
this.x=x;
};
function Parent(){
this.y=1000;
}
Child.prototype.getX=function(){
console.log(this.x);
}
Parent.prototype.getY=function(){
console.log(this.y);
}
//原型继承
Object.setPrototypeOf(Child.prototype,new Parent);
//Child.prototype.__proto__=new Parent();
let c = new Child(100);
console.log(c);
c.getX();
c.getY();
2、call继承
call继承,也就是借用构造函数继承。
在子类构造函数中,把父类当做普通方法执行,没有父类实例,没有办法继承父类原型上的属性方法。
function Parent(y){
this.y=y;
}
Parent.prototype.getY=function(){
console.log(this.y);
}
function Child(x,y){
//call和apply继承,它是把父类当做普通函数执行,所以prototype丢失
//把父类当做普通函数执行,但是让this的执行还是要指向到当前的对象中
Parent.call(this,y);
// Parent.apply(this,[y]);
this.x=x;
}
Child.prototype.getX=function(){
console.log(this.y);
}
let c = new Child(10,20);
console.log(c);
3、寄生组合式继承
先通过call继承把父类中的私有属性继承,在通过原型继承将父类原型中的属性和方法继承过来。
把父类原型中的方法继承过来,写法:
//写法1:
Child.prototype.__proto__ = Parent.prototype;
//写法二:推荐此写法
Object.setPrototypeOf(Child.prototype,Parent.prototype);
4、封装一个继承函数
function extend(ChildClass, ParentClass, ...args) {
let self = new ChildClass(...args)
// 执行父类
ParentClass.call(self, ...args)
Object.setPrototypeOf(ChildClass.prototype, ParentClass.prototype)
return self
}
const child = extend(Child, Parent, 1, 2, 3)
console.log(child)
5、混合继承
JS不能实现多继承,如果要是用多个类的方法时可以使用Object.assign给类的原型对象扩展其他类的方法。
function A(name) {
this.name = name;
}
A.prototype.show = function () {
console.log(this.name);
};
const B = {
total() {
console.log("统计下输出");
}
};
const C = {
ajax() {
console.log("发送请求");
}
};
function User(name) {
A.call(this, name);
}
User.prototype = Object.create(A.prototype)
User.prototype.constructor = User
Object.assign(User.prototype, B, C);
let u = new User("张三");
u.show();
u.total();
u.ajax();
八、实现类中方法多态
JS中不存在严格意义上的重载,我们可以使用一些方法实现类似的效果
class Person {
say(...args) {
if (args.length > 3) throw new Error('参数过多,最多3个');
this[`m${args.length}`](...args)
}
m0() {
}
m1(...args) {
console.log(args)
}
m2(...args) {
}
m3(...args) {
}
}
const p = new Person()
p.say(100)