class User { constructor(name) { this.name = name; this.show = function() {}; } getName() { return this.name; } } const as = new User("阿顺"); console.log(as); console.log(User.prototype); console.log(as.hasOwnProperty("getName")); console.log(as.hasOwnProperty("name"));
构造函数用于传递对象的初始参数,但不是必须定义的,如果不设置系统会设置如下类型
**子构造器中调用完super 后才可以使用 this**。
至于 super 的概念会在后面讲到,(执行父类中的constructor)
1 2 3
constructor(...args) { super(...args); }
原理分析
之前也提到过,**class其实就是函数**
1 2 3
class User { } console.log(typeof User); //function
下面是与普通函数的对比,结构是完全一致的。
constructor 用于定义函构造数体代码
constructor 外部定义的方法,会追加到该 类(构造函数) 的原型上。
constructor 外部定义的属性,依然会分配到实例上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class User { constructor(name) { this.name = "name"; } getName() { return this.name; } }
//普通构造函数 function Ashun(name) { this.name = "name"; } Ashun.prototype.getName = function () { return this.name; };
console.dir(User); console.dir(Ashun);
属性定义
在calss类中,无论是在constructor 内、外部定义的属性,都会分配到实例上。
constructor 内部定义的属性,使用this声明
constructor 外部定义的属性,不使用关键字声明。
在class类中,在constructor 外部定义的方法,会自动添加到该类的原型上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class User { site = `Ashuntefannao.com`; //在constructor外部定义的属性,依然会被分配到实例上 constructor(name) { this.name = name; } show() { console.log(this.name); } }
console.log(User.prototype.site);//undefined console.log(User.prototype.show); let as = new User("阿顺"); console.log(as.name, as.site);//阿顺 Ashuntefannao.com console.table(as);
{ class User { site = `Ashuntefannao.com`; constructor(name) { this.name = name; } show() { console.log(this.name); } } let as = new User("阿顺"); for (let key in as) { console.log(key); //site name } }
对比普通构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13
{ function User(name) { this.site = "Ashuntefannao.com"; this.name = name; } User.prototype.show = function () { console.log(this.name); }; let as = new User("阿顺"); for (let key in as) { console.log(key); //site name show } }
严格模式
class 默认使用use strict 严格模式执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
{ class User { site = `Ashuntefannao.com`; constructor(name) { this.name = name; } show() { !(function () { console.log(this); //默认严格模式,打印undefined })(); } } let as = new User("阿顺"); as.show(); }
对比普通构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13
{ function User(name) { this.site = "Ashuntefannao.com"; this.name = name; } User.prototype.show = function () { !(function () { console.log(this); //非严格模式,this=》window })(); }; let as = new User("阿顺"); as.show(); }
静态访问
静态属性、方法:意为只能够被 该类本身 访问的属性和方法,使用类名来调用。
在class中使用static关键字进行声明
在普通构造函数中,向构造函数本身压入对应的属性和方法。(构造函数本身也是对象)
静态属性
静态属性即为类设置属性,而不是为实例对象设置,下面是原理实现
1 2 3 4 5 6 7
function User() {} User.site = "阿顺特烦恼"; console.dir(User);
const as = new User(); console.log(as.site); //undefiend console.log(User.site); //阿顺特烦恼
在 class 中为属性添加 static 关键字即声明为静态属性
可以把所有实例对象都要使用的值 定义为静态属性
1 2 3 4 5 6 7 8
class Request { static HOST = "https://www.Ashuntefannao.com"; query(api) { return Request.HOST + "/" + api; } } let request = new Request();
静态方法
指通过类访问不能使用对象访问的方法,比如系统的Math.round()就是静态方法
一般来讲方法不需要对象属性参与计算就可以定义为静态方法
下面是静态方法实现原理,向构造函数本身压入方法。
1 2 3 4 5 6 7 8 9 10 11
function User() { this.show = function() { return "this is a object function"; }; } User.show = function() { return "welcome to Ashuntefannao"; }; const as = new User(); console.dir(as.show()); //this is a object function console.dir(User.show()); //welcome to Ashuntefannao
在 class 内声明的方法前使用 static 定义的方法即是静态方法
1 2 3 4 5 6 7 8 9 10
class User { constructor(name) { this.name = name; } static create(name) { return new User(name); } } const as = User.create("阿顺"); console.log(as);
class User { constructor(name) { this.data = { name }; } get name() { return this.data.name; } set name(value) { if (value.trim() == "") throw new Error("invalid params"); this.data.name = value; } } let as = new User("阿顺"); as.name = "Ashun"; console.log(as.name);
访问控制
设置对象的私有属性有多种方式,包括后面章节介绍的模块封装。
public
public 指不受保护的属性,在类的内部与外部都可以访问到
1 2 3 4 5 6 7 8
class User { url = "Ashuntefannao.com"; constructor(name) { this.name = name; } } let as = new User("阿顺"); console.log(as.name, as.url);
class Common { _host = "https://Ashuntefannao.com"; set host(url) { if (!/^https:\/\//i.test(url)) { throw new Error("网址错误"); } this._host = url; } get host(){ return this._host } } class Article extends Common { lists() { return `${this._host}/article`; } } let article = new Article(); console.log(article.lists()); //https://Ashuntefannao.com/article article.host = "https://ASHUN.com"; console.log(article.lists()); //https://ASHUN.com/article
class User { //private #host = "https://Ashuntefannao.com"; constructor(name) { this.name = name ; this.#check(name); } set host(url) { if (!/^https?:/i.test(url)) { throw new Error("非常网址"); } this.#host = url; } get host() { return this.#host; } #check = () => { if (this.name.length < 5) { throw new Error("用户名长度不能小于五位"); } return true; }; } let as = new User("阿顺特烦恼"); as.host = "https://www.SHUN.com"; console.log(as["#host"]);//实例中不能访问类的私有属性 console.log(as.host); //可以通过getter间接访问
对比普通构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
function User(name) { let privateProp = { name }; Object.defineProperty(this, "name", { get() { return privateProp.name; }, set(newVal) { privateProp.name = newVal; }, }); } let as = new User("Ashun"); console.log(as.name); console.log(as.privateProp); //undefined
详解继承
属性继承
class实现属性继承的原型如下
1 2 3 4 5 6 7 8
function User(name) { this.name = name; } function Admin(name) { User.call(this, name); } let as = new Admin("阿顺"); console.log(as);
这就解释了为什么在子类构造函数中要先执行super
1 2 3 4 5 6 7 8 9 10 11 12
class User { constructor(name) { this.name = name; } } class Admin extends User { constructor(name) { super(name); } } let as = new Admin("阿顺"); console.log(as);
继承原理
class 使用extends关键字实现原型继承。
1 2 3 4 5 6 7 8 9 10 11 12 13
class User { show() { console.log("user.show"); } } class Admin extends User { info() { this.show(); } } let as = new Admin("阿顺"); console.dir(as); as.info();
function controller() { return class { show() { console.log("user.show"); } }; } class Admin extends controller() { info() { this.show(); } } let as = new Admin(); as.show()
super
表示从当前原型中查找方法,
super 一直指向当前对象
super只能在类或对象方法中使用,不能在 独立存在的函数 中使用
下面是使用 this 模拟super,会有以下问题
this指向调用该方法的对象,结果并不是 admin的name值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
let user = { name: "user", show() { return this.name; } }; let admin = { __proto__: user, name: "admin", show() { return this.__proto__.show(); //this=>user } }; console.log(admin.show()); //user
为了解决以上问题,需要调用父类方法时改变this指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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());//admin
上面看似结果正常,但如果是多层继承时,会出现新的问题
我们期望使用common.show,但因为始终传递的是当前对象this ,造成从 this 原型循环调用
let common = { show() { console.log("common.init"); console.log(this.name); }, }; let user = { __proto__: common, name: "user", show() { return super.show(); }, }; let admin = { __proto__: user, name: "admin", get() { return super.show(); //虽然使用super调用父级方法,但this指向当前对象 }, }; admin.get(); //common.init admin
super只能在类或对象的方法中使用,而不能在独立存在的函数中使用,下面将产生错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14
let user = { name: "user", show() { return this.name; } }; let admin = { __proto__: user, name: "admin", get: function() { return super.show(); } }; console.log(admin.get()); //Uncaught SyntaxError: 'super' keyword unexpected here
constructor
super 指向父类引用,在构造函数constructor 中必须先调用super(),因为这样才能够继承父级原型的属性、方法。
super() 调用父类的构造函数constructor()
必须在 constructor 函数里的this 使用前执行 super()
若不手动配置super(),系统会默认执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class User { constructor(name) { this.name = name; } show() { console.log(this.name); } } class Admin extends User { constructor(name) { super(name); } } let as = new Admin("阿顺"); as.show();
constructor 中先调用 super 方法的原理如下
利用父级构造函数,初始化实例属性
1 2 3 4 5 6 7 8 9 10
function Parent(name) { this.name = name; } function User(...args) { Parent.apply(this, args); } User.prototype = Object.create(User.prototype) User.prototype.constructor = User; const as = new User("阿顺"); console.log(as.name);
若不手动配置super(...args),系统会默认执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class User { constructor(name) { this.name = name; this.age = age; } show() { console.log(this.name, this.age); } } class Admin extends User { constructor(...args) { super(..args); } } let as = new Admin("阿顺",18); as.show();
与下方代码结果相同
1 2 3 4 5 6 7 8 9 10 11 12
class User { constructor(name) { this.name = name; this.age = age; } show() { console.log(this.name, this.age); } } class Admin extends User {} let as = new Admin("阿顺",18); as.show();
class User { static site = "阿顺特烦恼"; static host() { return "Ashuntefannao.com"; } show() { console.log(User.site, User.host()); } } class Admin extends User {} console.log(Admin.site); let as = new Admin(); as.show();
function User() {} function Admin() {} Admin.prototype = Object.create(User.prototype); let as = new Admin(); console.log(as instanceof Admin); //true console.log(as instanceof User); //true
function checkPrototype(obj, constructor) { if (!obj.__proto__) return false; if (obj.__proto__ == constructor.prototype) return true; return checkPrototype(obj.__proto__, constructor); }
class 内部实现就是基于原型,所以使用instanceof 判断和上面原型是一样的
1 2 3 4 5
class User {} class Admin extends User {} let as = new Admin(); console.log(as instanceof Admin); console.log(as instanceof User);
isPrototypeOf
使用 isPrototypeOf 判断一个对象是否在另一个对象的原型链中,下面是原理分析
1 2 3 4 5 6 7 8 9
const a = {}; const b = { __proto__: a }; const c = { __proto__: b }; console.log(a.isPrototypeOf(b)); //true console.log(a.isPrototypeOf(c)); //true
下面在使用 class 语法中使用
1 2 3 4 5
class User {} class Admin extends User {} let as = new Admin(); console.log(Admin.prototype.isPrototypeOf(as)); console.log(User.prototype.isPrototypeOf(as));
继承内置类
使用原型扩展内置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
function Arr(...args) { args.forEach(item => this.push(item)); this.first = function() { return this[0]; }; this.max = function() { return this.data.sort((a, b) => b - a)[0]; }; }
Arr.prototype = Object.create(Array.prototype); let arr = new Arr("阿顺", "123", 18); console.log(arr.first()); console.log(arr.max())
使用 class扩展内置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class Arr extends Array { constructor(...args) { super(...args); } add(val) { this.push(val); } remove(val) { let index = this.findIndex((v) => v == val); index && this.splice(index, 1); return index; } } let arr = new Arr(1, 2, 3); arr.push("ASHUNTEFANNAO"); console.log(arr); console.log(arr.remove(3)); console.log(arr);