js中的类
基础知识
class在js中是构造函数的语法糖,其底层设计模式依然为原型和继承
声明定义
可以使用类声明和赋值表达式定义类,推荐使用类声明来定义类
//类声明
class User {
}
console.log(new Article());
let Article = class {
};
console.log(new User());
类方法间不需要逗号
class User {
show() {}
get() {
console.log("get method");
}
}
const hd = new User();
hd.get();
类方法是直接写到原型上的,如在User类中写类方法show()相当于User.prototype.show = function(){}
构造函数
使用 constructor
构造函数传递参数,下例中show
为构造函数方法,getName
为原型方法
constructor
会在 new 时自动执行
下面的User类,实例化的user对象的属性是name和age,在他的原型上,也就是User.prototype中有getName方法
class User{
constructor(name, age){
this.name = name;
this.age = age;
}
getName(){
console.log(`名字是${this.name},年龄是${this.age}`);
}
}
let user = new User('kevin', 20);
user.getName();//名字是kevin,年龄是20
console.log(user);
构造函数用于传递对象的初始参数,但不是必须定义的。
- 子构造器中调用完
super
后才可以使用this
。 - 如果需要调用父类构造方法,必须写
constructor
,因为super
方法只能写在constructor
里面。 - 如果你不写
constructor
,那么代码执行上会给你增加一个默认的constructor
。
关于最后一条,默认构造函数,基类默认的 constructor
是:
constructor() {} // 就是啥都不干
派生类默认的 constructor
是:
constructor(...args) {
super(...args) // 用派生类实例的参数调用下基类构造函数
}
可以说,如果你的类不需要特殊处理参数(与默认的构造函数一致),那么可以省略 constructor
原理分析
类其实是函数,class实际上就是构造函数的语法糖。
class User {
}
console.log(typeof User); //function
属性定义
每次new对象都是独立的属性。
class User{
constructor(name, age){
this.name = name;
this.age = age;
}
getName(){
console.log(`名字是${this.name},年龄是${this.age}`);
}
}
new User('kk', 22).getName();//名字是kk,年龄是22
new User('jj', 20).getName();//名字是jj,年龄是20
函数差异
class
是使用函数声明类的语法糖,但也有些区别
class
中定义的方法(放在类的实例对象的原型上),不能被遍历。
可以看到定义的方法的特征中的enumerable
属性为false
。
class User{
constructor(name, age){
this.name = name;
this.age = age;
}
getName(){
console.log(`名字是${this.name},年龄是${this.age}`);
}
}
console.log(
JSON.stringify(
Object.getOwnPropertyDescriptor(User.prototype, 'getName'),
null,
2
)
);
// {
// "writable": true,
// "enumerable": false,
// "configurable": true
// }
let user = new User('kevin', 20);
for (const key in user) {
console.log(key);//换行输出name age 没有getName方法,因为特征为不能遍历
}
严格模式
class
默认使用strict
严格模式执行
class User {
constructor(name) {
this.name = name;
}
show() {
function test() {
//严格模式下输出 undefined
console.log(this);
}
test();
}
}
let xj = new User("kk");
xj.show();//undefined
静态访问
静态属性
静态属性即为类设置属性,而不是为生成的对象设置,下面是原理实现
function User() {}
User.site = "dd";
console.dir(User);
const hd = new User();
console.log(hd.site); //undefiend
console.log(User.site); //dd
console.log(hd);
在 class
中为属性添加 static
关键字即声明为静态属性
- 可以把为所有对象使用的值定义为静态属性
class Request{
static Host = 'localhost';
requestApi(api){
return `${api}-${Request.Host}`
}
}
let request = new Request();
console.log(request.requestApi('www.baidu.com/'));//www.baidu.com/-localhost
静态方法
指通过类访问不能使用对象访问的方法,比如系统的Math.round()
就是静态方法
- 一般来讲方法不需要对象属性参与计算就可以定义为静态方法
下面是静态方法实现原理
function User() {
this.show = function() {
return "this is a object function";
};
}
User.show = function() {
return "welcome to houdunren";
};
const xj = new User();
console.dir(xj.show()); //this is a object function
console.dir(User.show()); //welcome to houdunren
在 class
内声明的方法前使用 static
定义的方法即是静态方法
class Request{
static Host = 'localhost';
static requestApi(api){
return `${api}-${Request.Host}`
}
}
console.log(Request.requestApi('www.baidu.com/'));//www.baidu.com/-localhost
下面使用静态方法在课程类中的使用
const data = [
{name: 'js',click: 55, price: 77},
{name: 'ts',click: 35, price: 127},
{name: 'node',click: 45, price: 88},
];
class Lesson{
constructor(data){
this.model = data;
}
get price(){
return this.model.price;
}
static totalPrice(data){//获取总价
return data.reduce((t,c) => {
return t + c.price
}, 0);
}
static maxPrice(data){//获取最大价格
return Math.max(...data.map(item => item.price));
}
}
console.log(Lesson.totalPrice(data));//292
console.log(Lesson.maxPrice(data));//127
访问器
使用访问器可以对对象的属性进行访问控制,下面是使用访问器对私有属性进行管理。
语法介绍
- 使用访问器可以管控属性,有效的防止属性随意修改
- 访问器就是在函数前加上
get/set
修饰,操作属性时不需要加函数的扩号,直接用函数名
class User{
constructor(name){
this.data = {name};
}
get name(){
return this.data.name;
}
set name(value){
if(value.trim === '') throw new Error('名字错误');
this.data.name = value;
}
}
let user = new User('kevin');
console.log(user.name);//kevin
user.name = 'tom'
console.log(user.name);//tom
访问控制
设置对象的私有属性有多种方式,包括后面章节介绍的模块封装。
public
public
指不受保护的属性,在类的内部与外部都可以访问到
protected
protected是受保护的属性修释,不允许外部直接操作,但可以继承后在类内部访问,有以下几种方式定义
命名保护
将属性定义为以 _
开始,来告诉使用者这是一个私有属性,请不要在外部使用。
- 外部修改私有属性时可以使用访问器
setter
操作 - 但这只是提示,就像吸烟时烟盒上的吸烟有害健康,但还是可以抽的
Symbol
下面使用 Symbol
定义私有访问属性,即在外部通过查看对象结构无法获取的属性
const protecteds = Symbol();
class Common {
constructor() {
this[protecteds] = {};
this[protecteds].host = "https://baidu.com";
}
set host(url) {
if (!/^https?:/i.test(url)) {
throw new Error("错误");
}
this[protecteds].host = url;
}
get host() {
return this[protecteds].host;
}
}
class User extends Common {
constructor(name) {
super();
this[protecteds].name = name;
}
get name() {
return this[protecteds].name;
}
}
let hd = new User("dd");
hd.host = "https://www.gg.com";
console.log(hd.name);//dd
WeakMap
WeakMap 是一组键/值对的集,也可以利用WeakMap
类型特性定义私有属性
private
private
指私有属性,只在当前类可以访问到(只能在声明私有属性的类的函数体中使用),并且不允许继承使用
- 为属性或方法名前加
#
为声明为私有属性 - 私有属性只能在声明的类中使用
下面声明私有属性 #host
与私有方法 check
用于检测用户名
class User{
//private
#host = 'www.baidu.com';
constructor(name){
this.name = name;
}
#check = ()=>{
return `private func`;
}
func(){
return `${this.#host} && ${this.#check()}`;
}
}
class Admin extends User{
constructor(age){
super();
this.age = age;
}
}
let user = new User('kevin');
console.log(user);//User {name: 'kevin', #host: 'www.baidu.com', #check: ƒ}
console.log(user.func());//www.baidu.com && private func
详解继承
在构造函数中实现继承
属性继承
属性继承的原型如下
function User(name) {
this.name = name;
}
function Admin(name) {
User.call(this, name);
}
let hd = new Admin("dd");
console.log(hd);//Admin {name: 'dd'}
方法继承
调用父类方法时传递this
。
let user = {
name: "user",
show() {
return this.name;
}
};
let admin = {
__proto__: user,
name: "admin",
show() {
return this.__proto__.show.call(this);
}
};
console.log(admin.show());
但如果是多层继承时,会出现新的问题
- 因为始终传递的是当前对象
this
,造成从this
原型循环调用
let common = {
show() {
console.log("common.init");
}
};
let user = {
__proto__: common,
name: "user",
show() {
return this.__proto__.show.call(this);
}
};
let admin = {
__proto__: user,
name: "admin",
get() {
return this.__proto__.show.call(this);
}
};
console.log(admin.get());
为了解决以上问题 js
提供了 super
关键字
- 使用
super
调用时,在所有继承中this
始终为调用对象 super
是用来查找当前对象的原型,而不像上面使用this
查找原型造成死循环- 也就是说把查询原型方法的事情交给了
super
,this
只是单纯的调用对象在各个继承中使用
super的使用
为什么super要在this前调用:因为当前类属性的优先级是要大于父类的,如果有重名属性,super放在后面就会把当前类的属性覆盖掉,所以编译器就直接规定要把super写在前面
在使用extends关键字继承后,在constructor
中使用this关键字之前必须先使用super
来继承属性,不然会报错。括号里面的值为想要继承的属性(当然也可以为空),在构造函数中也需要设置相应的形参,不然继承的类在new对象时不能传递相应的参数。
在继承方法时,也要使用super
关键字。具体方式为super.func()
func为想要继承的方法名。
class User{
constructor(name, age){
this.name = name;
this.age = age;
}
show(){
return `show User`;
}
}
class Admin extends User{
constructor(title,...args){
super(...args);
this.title = title;
}
gg(){
return super.show();
}
}
let admin = new Admin('js','kevin',20);
console.log(admin);
console.log(admin.gg());//show User
super
只能在类或对象的方法中使用,而不能在函数中使用
方法的重写
在子类中声明与父类同名的方法,这样使用子类的实例对象调用方法时就会用子类的方法,而不是父类(覆盖掉了)
在父类的基础上进行调整或扩展其功能,可以使用super
关键字
class User{
constructor(name, age){
this.name = name;
this.age = age;
}
show(){
return `show User`;
}
}
class Admin extends User{
constructor(title,...args){
super(...args);
this.title = title;
}
show(){
return `${super.show()}变成Admin`;
}
}
let admin = new Admin('js','kevin',20);
console.log(admin);
console.log(admin.show());//show User变成Admin
instanceOf 和 isPrototypeOf
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
object instanceof constructor
参数
object
某个实例对象
constructor
某个构造函数
isPrototypeOf()
方法用于测试一个对象是否存在于另一个对象的原型链上。
prototypeObj.isPrototypeOf(object)
参数
object
在该对象的原型链上搜寻
class User{}
class Admin extends User{}
let admin = new Admin();
console.log(admin instanceof User);//true
console.log(User.prototype.isPrototypeOf(admin));//true
继承内置类
我们可以声明一个继承于内置类的类,可以在这个类上添加功能
class Arr extends Array{
add(value){
this.push(value);
}
remove(value){
let removeIndex = this.findIndex(item => item !== value);
this.splice(removeIndex,1);
}
}
let arr = new Arr(1,2,3,4,5);
arr.add(6);
console.log(arr);//Arr(6) [1, 2, 3, 4, 5, 6]
arr.remove(5)
console.log(arr);//Arr(5) [2, 3, 4, 5, 6]
mixin
JS
不能实现多继承,如果要使用多个类的方法时可以使用mixin
混合模式来完成。
mixin
类是一个包含许多供其它类使用的方法的类mixin
类不用来继承做为其它类的父类
我们可以通过Object.assign()方法来将需要的对象的方法复制到需要的类中,进而模仿多继承
let ss = {
show(){
console.log('showshow');
}
};
let gg = {
gege(){
console.log('gg');
}
}
class User{}
Object.assign(User.prototype, ss);
Object.assign(User.prototype, gg);
let user = new User();
user.show();//showshow
user.gege();//gg