文章有点长,边听课边整理的笔记,如有疏漏不当之处,还请指教,立即更改。
类是一个函数,基层依旧是原型继承,class语法糖使类的声明和使用、继承更加的简洁。
类的定义方式
类的定义可以分为两种,一种是声明类,一种是赋值类。
//声明类
class User{
// constructor起到了传递对象的初始参数的作用,不是必须定义声明的。在new的收,会执行constructor方法。
constructor(name){
this.name = name;
// show 是构造函数的方法。
this.show = function(){ console.log( 'show' )}
};
// getName是原型上的方法。
getName(){
return this.name
}
}
//赋值类
let class = class{
constructor(name){
this.name = name;
}
};
var chen = new User('陈');
chen.getName();
chen.show();
typeof User; // function
constructor
类如果不设置constructor,则类会默认设置如下类型:在子构造器调用完super后才可以使用this
。
constructor( args ){
super( ...args )
}
类和构造函数
- 类就是一个函数,是一种语法糖。下式代码中,两者所起到的作用是一样的。
- 在使用in遍历的时候,通过方式一class定义的,原型上的方法getName不能遍历到,而方式二构造函数追加到原型上的方法getName则可以遍历到。
- class默认使用严格模式(use strict)执行。
// 定义类
class User{
// site name 为对象上的两个属性
site = 'www.chen.com';
constructor(name){
this.name = name;
// show 是构造函数的方法。
this.show = function(){ console.log( 'show' )}
};
// 原型上的方法
getName(){
console.log( 'getName' );
}
}
let chen = new User('陈');
for (const key in chen) {
console.log(key); // site name show
}
// 定义构造函数
function User(name){
this.site = 'www.chen.com';
this.name = name;
this.show =function(){
console.log( 'show' );
};
};
// 为构造函数原型添加方法
User.prototype.getName = function(){
console.log( 'getName' );
}
let chen = new User('陈');
for (const key in chen) {
console.log(key); // site name show getName
}
静态属性和静态方法(static)
- 可以把为所有对象使用的值定义为静态属性。
- 一般来讲方法不需要对象属性参与计算就可以定义为静态方法。
- 静态方法和静态属性只能通过类来访问,继承的类也可以访问到,但是不能通过实例来获取。
// 静态访问实现原理
function User(){
this.show = function(){
console.log('inside show');
}
};
User.show = function(){
console.log( 'outside show' );
};
User._name = 'Miracle';
let chen = new User();
User.show(); // outside show
console.log( User._name ); // Miracle
chen.show(); // inside show
console.log( chen.name ); // undfined
// 类中静态方法和静态属性使用方法
class User{
constructor(fn){
this.fn = fn;
}
// 静态属性
static LAST_NAME = 'chen';
// 静态方法
static getFullName (fn) {
console.log( this );
return '大家好,我是' + fn + ' ' + User.LAST_NAME;
}
query(fn){
return fn + ' ' + User.LAST_NAME;
};
sayWord(){
return User.getFullName();
}
};
let chen = new User();
chen.query('miracle');
User.getFullName('xiaowu');
属性的访问控制(访问器)
在函数前加上 get/set
修饰,防止属性被随意的更改。
class User{
constructor(name, age){
this.data = {
name: name,
age: age
}
};
get name(){
return this.data.name;
};
set name(value){
if( value.length > 10 ){
throw new Error( 'name长度过长' );
};
this.data.name = value;
}
};
let chen = new User('chen', 18);
chen.name = 'miracle';
chen.name;
私有属性
命名保护
- Symbol定义私有属性
let protecteds = Symbol();
class User {
constructor(host) {
this[protecteds] = {};
this[protecteds].host = host;
};
set host(url) {
if (!/^https?:/i.test(url)) {
throw new Error("url格式有误");
}
this[protecteds].host = url;
}
get host() {
return this[protecteds].host;
}
}
class Admin extends User {
constructor(name) {
super();
this[protecteds].name = name;
}
get name() {
return this[protecteds].name;
}
}
let hd = new Admin("zhangsan");
hd.host = "https://www.zhangsan.com";
- 不成文的规定:已_开头的属性和方法,为私有属性和私有方法。
private
- 为属性或方法名前加 # 为声明为私有属性
- 只有在声明的类里使用,在继承的类中也访问不到。
下边代码中定义了私有属性host以及私有方法check
class User{
// 私有属性
#host = '#host';
constructor(name){
this.name = name;
this.#check(name);
};
// 私有方法
#check(value){
if( value.length < 5 ){
throw new Error('用户名长度不能少于5位');
}
}
get host(){
return this.#host;
};
set host(url){
if(!/^https?:/.test( url)){
throw new Error( 'url格式有误' );
};
return true;
};
};
let hd = new User('zhangsan');
// hd.#host; //SyntaxError: Private field '#host' must be declared in an enclosing class
属性保护
保护属性并使用访问器控制
const protecteds = Symbol("protected");
class User {
constructor(name) {
this[protecteds] = { name };
}
get name() {
return this[protecteds].name;
}
set name(value) {
if (value.trim() == "") throw new Error("invalid params");
this[protecteds].name = value;
}
}
let hd = new User("zhangsan");
hd.name = "lisi";
console.log(hd.name);
console.log(Object.keys(hd));
继承的原理分析
属性继承
属性继承的原型如下代码所示:
function User(name) {
this.name = name;
}
function Admin(name) {
User.call(this, name); // 相当于类继承中的super(name)
}
let hd = new Admin("后盾人");
console.log(hd);
方法继承
在类中,可以采用如下代码来在子类中调用父类中的方法(通过super关键字)
// 调用父类的show方法
class User{
show(){
console.log( 'User.show', this.name );
}
};
class Admin extends User{
constructor(name){
super();
this.name = name;
};
show(){
super.show();
}
};
let admin = new Admin('admin');
admin.show();
我们也可以使用以下对象和__proto__
的方式来实现类似的功能:
let user = {
name: 'user',
show(){
console.log( 'user show', this.name );
}
};
let admin = {
name: 'admin',
__proto__: user,
show(){
// 调用原型上的show方法,将其this指向admin。类似于类中的 super
this.__proto__.show.call(this);
}
}
admin.show();
但是这个代码有个缺陷,就是一旦层级超过二级,就会出现死循环。如:
let common = {
name: 'common',
show(){
console.log( 'common show' );
}
}
let user = {
name: 'user',
__proto__: common,
show(){
this.__proto__.show.call( this ); // 此时this指向的是admin对象,在此处调用的一直都是user对象的show方法,走进死循环。
}
};
let admin = {
name: 'admin',
__proto__: user,
show(){
// 调用原型上的show方法,将其this指向admin。类似于类中的 super
this.__proto__.show.call(this);
}
}
admin.show();
为了解决上述的死循环问题,可以采用super
关键字。
let common = {
name: 'common',
show(){
console.log( 'common show', this.name );
}
}
let user = {
name: 'user',
__proto__: common,
show(){
super.show();
}
};
let admin = {
name: 'admin',
__proto__: user,
show(){
super.show();
}
}
admin.show();
super关键字
-
super的作用是从当前对象的原型中执行方法,进行原型攀升。
-
在class方法中,继承是使用extends关键字来实现继承的。
-
子类必须在constructor()中调用super()方法,否则新建实例时会报错。报错的原因是,子类是没有自己的this对象的,它只能继承父类的this对象,然后对其进行加工,而super()就是将父类中的this对象继承给子类的。没有super,子类就得不到this对象。
-
super只能在类或者对象中使用,不能在方法中使用, 如:
show: function(){ return super.show(); // //Uncaught SyntaxError: 'super' keyword unexpected here }
constructor
-
super指向父类引用,在有继承关系的子类中,在构造函数中,必须先调用
super()
, 因为防止父类变量方法覆盖子类变量方法。class Admin extends User{ constructor(name){ super(); // 其原理如: User.apply(this, args); this.name = name; } }
静态方法和静态属性
先来看看类的静态方法和静态属性用法:
class User {
static name = 'herry';
static showName() {
return this.name;
};
sum(){};
};
class Admin extends User {};
let admin = new Admin();
console.log( admin );
代码原理分析:
在上述代码中,静态属性和静态方法都是被加在了类User
的对象上,而不是在其prototype
原型上,但是sum
是加在了其prototype
上。类Admin
继承了类User,Admin.prototype.__proto__
就指向了User.prototype
,也就是 Admin.prototype.__proto__ === User.prototype
。然后通过new
创建一个Admin
实例 admin
, admin.__proto__
就指向了 Admin.prototype
, 所以 admin.__proto__.__proto__ === User.prototype
。在上述代码中, sum
是在User.prototype上
,所以 admin
可以访问到 sum, 但是访问不到 User
的 静态方法showName
和静态属性name
下边通过构造函数来分析下其实现原理
function User(){};
User.name = 'herry';
User.showName = function(){ };
User.prototype.sum = function(){};
function Admin () {};
Admin.prototype.__proto__ = User.prototype;
let admin = new Admin();
console.dir( admin );
调用父类方法
- super不接方法名,默认调用父类的constructor。
- super后跟着方法名,则调用父类对应的方法。
- 可以再子类中重写父类的方法。
代码如下所示:
let data = [
{ name: "js", price: 100 },
{ name: "mysql", price: 212 },
{ name: "vue.js", price: 98 }
];
class User {
constructor(name) {
this.name = name;
};
showName() {
return this.name;
};
//匹配包含名字的课程
filterListByName(name){
return data.filter( i => i.name.includes( name ));
};
// 父类中的,计算所有课程价格的总和
sum(){
return data.reduce((acc, cur) => acc + cur.price, 0 );
};
// 获取课程中价格最大的
max(){
return data.map( i => i.price ).sort((a, b) => b - a )[0];
}
};
class Admin extends User {
constructor(name) {
// super后不跟方法名,默认执行的是父类的constructor构造函数。
super(name);
};
// 在子类重写父类的方法, 只获取对应课程的名字
filterListByName(value){
// super后跟着方法,是调用父类中对应的方法。
return super.filterListByName(value).map( i => i.name )
}
};
let hd = new Admin('admin');
hd.showName();
hd.sum();
hd.max();
hd.filterListByName('js');
mixin
JS
不能实现多继承,如果要使用多个类的方法时可以使用mixin
混合模式来完成。
mixin
类是一个包含许多供其它类使用的方法的类mixin
类不用来继承做为其它类的父类- 其原理与
mixin
在原型中是一样的。
直接上代码:
const Tool = {
max(key) {
return this.data.sort((a, b) => b[key] - a[key])[0];
}
};
class Lesson {
constructor(lessons) {
this.lessons = lessons;
}
get data() {
return this.lessons;
}
}
// 让Lessons的实例可以同时使用其原型与Tools对象中的方法。将Tools对象压到Lessons.prototype中。
Object.assign(Lesson.prototype, Tool);
const data = [
{ name: "js", price: 100 },
{ name: "mysql", price: 212 },
{ name: "vue.js", price: 98 }
];
let hd = new Lesson(data);
console.log(hd.max("price"));