ES6入门六:class的基本语法、继承、私有与静态属性、修饰器
- 基本语法
- 继承
- 私有属性与方法、静态属性与方法
- 修饰器(Decorator)
一、基本语法
class Grammar{
constructor(name,age){ //定义对象自身的方法和属性
this.name = name,
this.age = age
}
// 在原型上定义只读属性
get inva(){
return "JS";
}
//在原型上定义可读写属性
set skill(val){
this._skill = val;
}
get skill(){
return this._skill;
}
action(){ //定义原型上的方法
console.log("使用" + this.inva + "实现web项目。");
}
}
将ES6的class类示例用ES5语法实现:
function Obj(name,age){
this.name = name;
this.age = age;
}
Object.defineProperty(Obj.prototype,"inva",{
get:function(){
return "JS";
},
configurable:true,
enumerable:false
});
Object.defineProperty(Obj.prototype,"skill",{
set:function(val){
this._skill = val;
},
get:function(){
return this._skill;
},
configurable:true,
enumerable:false
});
Object.defineProperty(Obj.prototype,"saction",{
value:function action(){
console.log("使用" + this.inva + "实现web项目。");
},
writable:true,
configurable:true,
enumerable:false
});
1.1Class简单说明:
Class声明的类本质上还是一个函数:
typeof Grammar; //"function"
Grammar === Grammar.prototype.constructor; //true
类虽然是函数,但是不能直接被调用执行,必须使用new指令执行构造行为:
Grammar();//Class constructor Grammar cannot be invoked without 'new'
constructor方法是类的默认方法,一个类必须有constructor方法,如果没有显式的定义,会隐式的添加一个空的constructor方法,不会报错:
class ObjFun{
get a(){
return "这是一个没有显式定义constructor方法的类";
}
}
var objFun = new ObjFun();
console.log(objFun.a);//这是一个没有显式定义constructor方法的类
Class声明的类不存在变量提升,所以在类前面调用会报错:
new Foo(); //Cannot access 'Foo' before initialization
class Foo{}
二、继承
class InheritGrammar extends Grammar{
constructor(name,age,color){
super(name,age); //调用父类的constructor
this.color = color;
}
introduce(){
this.action();
console.log("我的颜色是"+this.color);
}
}
var inGrammar = new InheritGrammar("他乡踏雪",18,"blur");
inGrammar.introduce();//使用JS实现web项目 我的颜色是blur
在控制台展开inGrammar实例:
InheritGrammar {name: "他乡踏雪", age: 18, color: "blur"}
将ES6的class继承语法使用ES5的语法实现:
function InheritObj(name,age,color){
var InSuper = new Obj(name,age); //这行代码可以替换(没有差异):var InSuper = new Grammar(name,age);
for(var key in InSuper){
this[key] = InSuper[key];
}
this.color = color;
}
InheritObj.prototype.introduce = function introduce(){
this.action();
console.log("我的颜色是"+this.color);
}
var InObjPro = Object.getOwnPropertyNames(Obj.prototype); //将Obj.prototype替换成Grammar没有区别
for(var time of InObjPro){
if(time !== "constructor"){
var attrObj = Object.getOwnPropertyDescriptor(Obj.prototype,time);//将Obj.prototype替换成Grammar没有区别
Object.defineProperty(InheritObj.prototype,time,attrObj);
}
}
测试代码:
var grammar = new Grammar("他乡踏雪",18);
var obj = new Obj("他乡踏雪",18); var inGrammar = new InheritGrammar("他乡踏雪",18,"blur");
var inObj = new InheritObj("他乡踏雪",18,"blur"); console.log(grammar);
console.log(obj);
console.log(".............................");
console.log(inGrammar);
console.log(inObj);
ES6的class实现可定有浏览器内部更优的实现方案,使用ES5的语法实现只是模仿,但ES6语法并不改变JS的语言特性,所以使用ES5模仿实现的class机制不会有差异。ES5实现class的机制其核心就是在属性描述符,核心ES5的API就是:(**重点**)
Object.getOwnPropertyNames(Obj.prototype);//获取对象的属性名称列表
Object.getOwnPropertyDescriptor(Obj.prototype,time);//获取属性描述符对象
Object.defineProperty(InheritObj.prototype,time,attrObj);//给对象的属性绑定属性描述符对象
三、私有属性与方法、静态属性与方法
2.1静态属性与方法
在class语法中除了引入继承的特性,还实现了静态属性和静态方法的特性,在ES提案中的私有属性和方法也有比较多的支持者。目前,私有属性还是基于现有的语法特性,然后开发者们按照约定熟成的方式来实现,但并不能完全实现属性和方法的私有性。
//使用static指令实现静态属性和静态方法
class Foo{
static a ;
static b = 10;
static c = function c(){
console.log(this.a + this.b);
}
}
class Fun extends Foo{
constructor(){
super()
}
}
Foo.a = 18;
Foo.c();//
Fun.a = 10;
Fun.c();//
Foo.c();//
通过上面的示例可以看到,在JavaScript中的class机制同样实现了类的静态方法特性,被正常的继承,其实静态方法被继承相比原型属性和方法的继承还要简单,因为static声明的静态属性是可以枚举的,不需要复杂的获取实行描述符。所以,如果父级类的静态属性是引用值类型的话,就需要小心使用,因为class的继承机制仅仅只是将父级的引用值引用赋给了子类,所以它们可以共同修改这个引用值。
class Foo{
static e = [1,2,3];
}
class Fun extends Foo{
constructor(){
super()
}
}
console.log(Foo.e); //[1,2,3]
Fun.e.push(4);
console.log(Fun.e);//[1,2,3,4]
console.log(Foo.e);//[1,2,3,4]
这也是JavaScript类机制与像Java那些语言的根本区别,Java的继承是完全重新复制一份,互不干扰。但是如果要在JavaScript中实现深拷贝显然在性能上是个很大的损失。
一个额外的问题,直接在class中写静态属性并赋值的方式,是ES7的写法,在老版本的浏览器中不能识别(报错),我是用最新的chrome浏览器已经能识别。但是,目前来讲浏览器能不能识别并不重要,因为我们在使用ES6还是ES7的时候都会使用编译工具进行降级,在babel中我使用的7.5.5版本都不支持编译示例中的静态属性的语法,在编译的时候回报以下错误:
Support for the experimental syntax 'classProperties' isn't currently enabled (2:11): 1 | class Foo{
> 2 | static e = [1,2,3];
| ^
3 | static bar(){
4 | return "bar";
5 | }
但同时,在后面babel也提示了能编译这个语法的插件:
Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.
下载插件:
npm install @babel/plugin-proposal-class-properties --save-dev
然后在(.babel文件中配置,这种模式任选一种配置,这里我选择了宽松模式):
//严格模式
{
"plugins": ["@babel/plugin-proposal-class-properties"]
}
//宽松模式(我测代码时使用了这个)
{
"plugins": [
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}
其实,bable编译后的代码非常的简单暴力:
Foo.e = [1, 2, 3];
2.2私有属性和方法:
这里有一篇作为参考:https://www.oschina.net/news/100766/tc39-approval-emcascript
关于私有方法的TC39提案:https://github.com/tc39/proposal-private-methods
TC39官网: https://tc39.es/,2019年1月通过的方案
私有属性示例:(使用同静态属性和方法的转码器插件:@babel/plugin-proposal-class-properties,安装和使用详细查看前面静态属性和方法的相关插件安装内容)
class Foo{
#x = 10;
static poor(numVal){
//这是会报错的,因为静态方法的this指向Foo,而私有属性的this指向实例对象
// 所以静态方法中不能使用私有属性
console.log(this.#x - numVal);
}
constructor(x = 0){
this.num = this.#x; //公有属性可以获取私有属性的值
}
set x(value){
this.#x +=value; //这里可以理解为公有方法内部给私有属性写入值
}
get x(){
return this.#x; //这里可以理解为公有方法内部读取私有属性的值
}
sum(){
console.log(this.num + this.#x); //这里可以理解为公有方法读取私有属性的值
}
}
class Fun extends Foo{
constructor(){
super()
}
y(){
// 子类方法不能直接使用父类的私有属性,babel插件无法编译
// return this.#x;
}
}
var foo = new Foo();
var fun = new Fun();
console.log(fun.x);//10:这里可以理解为子类的实例对象,可以通过父类的公有方法获取父类的私有属性值
fun.x = 10; // 10+10: 这里会发生属性赋值遮蔽效果,如若是数组push就可以修改到真正的私有属性值
console.log(foo.x);//10: 类的实例可以通过类的公有方法获取类的私有属性值
console.log(fun.x);//20: 类的实例可以通过类的公有方法获取类的私有属性值
console.log(fun.num);//10:这个不能说明什么,因为是原始值类型赋值,然读取被赋值的公有属性(但是这个赋值发生在实例对象构造时期)
foo.x = 10;//10+10:这里可以理解为类的实例调用类的公有方法,给类的私有属性赋值
console.log(fun.x);//20:调用父类的方法读取父类的私有属性值
foo.sum();//10 + 20:这里同样是调用父类的方法打印父类的公有属性与父类的私有属性的数值之和
关于私有属性的语法:
- 声明私有属性直接使用#开头作为属性名,声明不能在私有属性前加this,但是使用时必须使用this调用
- 不能在子类调用父类的私有属性,但是可以在子类定义与父类同名的私有属性,并且互不干扰,但是我知道你不会这么做
- 不能在constructor中定义私有属性和方法
- 私有属性语法同样需要@babel/plugin-proposal-class-properties插件才能编译(ES7语法)
- 私有方法语法需要@babel/plugin-proposal-private-methods插件才能编译
关于私有属性和方法一直是困扰的问题,什么是私有,私有与静态的区别是什么?私有与公有又有什么区别?
其实可以这么开描述私有属性和方法:它不是静态的,也不是公有的属性和方法。这就好像哲学一样,我们可以用“关于数的学科”来描述数学,但是我们不能用“关于哲的学科”来描述哲学一样,私有属性和方法不能用类的私有属性和方法来理解它。
在程序中,静态属性和方法中的“静态”描述的是属性和方法相对于类是静止的,通俗的说法就是类在那里出现,静态属性和方法就在那里出现。所以静态属性和方法永远是被类名用(.)或者([key])的方式引用。
而公有属性和方法意思就是只有属于这个类的所有成员都能拥有,并非公共所有,而是公开所有,每个类的成员动能拥有的属性和方法。
注意,不管是静态的属性和方法,还是公有的属性和方法,都是相对类而言,相对类静止、相对类公开所有。静态就是相对类而言,类在那里出现,静态属性和方法才可以在那里出现;公有就是相对类是公开的,公开属性和方法只要是归属于类的对象就可以拥有,这个拥有是对象拥有一个独立的公开的属性和方法。
最后,私有同样也是相对类而言,与私有对立的就是公有,公有属性和方法有一个非常重要的隐式特性,就是对象拥有这个属性和方法的所有权限的意思,实例对象可以任意的将这个属性和方法赋值给别的对象,可以任意的读写属性和方法。而相对公有属性的私有属性,就是属于类的不公开所有的属性和方法(不能继承),也就是说类的实例对象没有私有属性和方法的所有权,类的实例对象不能对私有属性和方法进行读写。但是类的实例对象继承了类的公有方法,类在定义公有方法时,类自己可以将私有属性和方法用在任意自己公开的方法中。
所以,实例对象可以调用类定义的公开方法获取和修改私有属性,可以调用类的公开方法执行私有方法。实例对象一定要通过类的公有方法才能使用私有属性和方法。
私有方法:
class Foo{
#a(){
console.log("我是一个私有方法");
}
n(){
console.log("我是一个公有方法,我可以调用一个私有方法");
this.#a()
}
} var foo = new Foo();
foo.n();
foo.a();//foo.a is not a function
foo.#a();//这样导致无法编译
使用私有方法时,babel编译器还需要一个的插件:
npm install @babel/plugin-proposal-private-methods --save-dev
然后,再.babel文件中配置:
{
"presets":[
"@babel/preset-env"
],
"plugins": [
["@babel/plugin-proposal-class-properties", { "loose": true }],
["@babel/plugin-proposal-private-methods", { "loose": true }] //引入支持编译私有方法的插件
]
}
详细可以了解:https://babeljs.io/docs/en/babel-plugin-proposal-private-methods#via-cli
class Foo{
#a(){
console.log("我是一个私有方法");
}
n(){
console.log("我是一个公有方法,我可以调用一个私有方法");
this.#a();
}
}
class Fun extends Foo{
constructor(){
super()
}
m(){
this.n();
// this.#a();//无法编译,子类不能直接调用父类的方法
}
}
var foo = new Foo();
var fun = new Fun();
foo.n();
fun.m();//子类可以调用包含私有方法的父类公有方法
四、修饰器(Decorator)
修饰器是面向切面编程思想的,用来修改类的行为的API。ES7引入这项功能,Babel转码器已经支持。在了解和使用修饰器之前,先来看一个案例需求和模拟实现:
需求一:模拟搜索引擎,输入搜索内容,点击搜索按钮实现搜索功能(不需要实际实现,只需要触发触发点击事件,打印出“向+urlA+发送请求+data")。
需求二:不改变搜所引擎基本功能(即不修改第一条需求的代码),当搜索功能被触发时,同时模拟实现记录搜索关键字的搜索次数(只需要将搜索数据发送到另一个url,即打印出“向+urlB+发送请求+data”)。
使用ES5的js语法基于面向切面的编程思想,实现以上需求:
<input type="text" name="" id="inp">
<button id="but">搜索</button>
<script>
var inpDom = document.getElementById("inp");
var butDom = document.getElementById("but"); var keyValue = "";
inpDom.oninput = function(){
keyValue = this.value;
} var requestFun = dealFun(getContent);
butDom.onclick = function(){
requestFun(keyValue);
} //模拟实现的搜索请求功能
function getContent(data){
var url = "urlA"
console.log("向" + url + "发送请求搜索信息,数据:" + data);
} //模拟实现的关键字记录请求功能,并应用面向切面编程思想代理搜索请求功能
function dealFun(func){
return function(data){
var url = "urlB";
console.log("向" + url + "发送请求记录关键字,数据:" + data);
return func.apply(this,arguments);
}
}
</script>
测试效果:
向urlB发送请求记录关键字,数据:TC39
向urlA发送请求搜索信息,数据:CT39
使用ES7修饰器基于面向切面编程思想,实现模拟案例需求:
let inpDom = document.getElementById("inp");
let butDom = document.getElementById("but"); //实现搜索功能的类
class Search{
constructor(){
this.keyValue = "";
}
#url = 'urlA';
@dealFun
getContent(){
console.log("向" + this.#url + "发送请求搜了信息,数据:" + this.keyValue);
}
}
//模拟实现关键字记录请求功能,基于ES7修饰器应用面向切面编程实现
function dealFun(proto,key,descriptor){
let dealGetContent = descriptor.value;
descriptor.value = function(){
let urlDeal = "rulB";
console.log("向" + urlDeal + "发送请求记录关键字,数据:" + this.keyValue)
return dealGetContent.apply(this,arguments);
}
}
//实例化搜索对象
let oS = new Search(); inpDom.oninput = function(){
oS.keyValue = this.value;
}
butDom.onclick = function(){
oS.getContent();
}
测试效果:
向rulB发送请求记录关键字,数据:TC39
向urlA发送请求搜了信息,数据:TC39
ES7修饰器(Decorator)转码插件:
npm install --save-dev @babel/plugin-proposal-decorators
在.babelrc中配置装饰器:
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
修饰器配置手册:https://babeljs.io/docs/en/babel-plugin-proposal-decorators#legacy
一个修饰器问题:https://github.com/WarnerHooh/babel-plugin-parameter-decorator/issues/1
什么是修饰器?装饰器有什么功能?
修饰器就是基于现有的属性、方法、类,生成或替换一个全新的功能。可以给现有的属性重新配置属性描述符或重新赋值,甚至可以使用一个全新的值替换。由于类没有属性的描述符,但可以给类添加静态方法,给类重新赋值甚至继承和被继承等操作。先了解一些有必要理解的语法:
- 修饰器使用“@”开头命名,然后取非“@”部分的名称在类的外面生一个函数。
- 修饰器设置在需要装饰的类、属性、方法的上方,独立一行。
- 修饰器可以是方法,也可以是函数执行后放回的函数,函数执行返回函数就是将修饰器作为函数执行一样放到被装饰者的前面,可以传入参数,例如:@fun(data),这个修饰器执行需要放回一个方法用来装饰被装饰者。
- 修饰器不能装饰私有方法和属性,可以修饰静态属性方法和公开属性方法。
修饰器与属性:
class Search{
@dealAttr
static attr = 10;
}
function dealAttr(proto,key,descriptor){
// proto:属性所属对象--静态属性指向类;公有属性指向构造方法constructor
// key:属性名称
// descriptor:修饰对象--属性描述符+initializer;
// 可以给initializer配置一个函数,函数返回值会作用于属性的初始值
descriptor.value = 20;//通过属性描述符value定义属性初始值
}
修饰器与方法:
@dealSearch
class Search{
@dealFun
getContent(){
console.log("向" + this.#url + "发送请求搜了信息,数据:" + this.keyValue);
}
}
function dealFun(proto,key,descriptor){
// proto:方法所属对象--公有属性指向构造方法constructor,静态属性指向类;
// key:方法名称
// descriptor:修饰对象--属性描述符
descriptor.valuer = function(){}//将方法指向一个全新的函数
}
修饰器与类:
@decorator
class A{}
function decorator(proto){
//proto 指向类本身
return newClass;
}
//同等与
A = decorator(A) || A;
修饰器的表示方式:
@decorator //装饰器对应的函数作用于装饰对象
@decorator() //装饰器对应的函数执行后返回的函数作用于装饰对象
ES6入门六:class的基本语法、继承、私有与静态属性、修饰器的更多相关文章
- ES6入门——类的概念
1.Class的基本用法 概述 JavaScript语言的传统方式是通过构造函数,定义并生成新对象.这种写法和传统的面向对象语言差异很大,下面是一个例子: function Point(x, y) { ...
- ES6 类(Class)基本用法和静态属性+方法详解
原文地址:http://blog.csdn.net/pcaxb/article/details/53759637 ES6 类(Class)基本用法和静态属性+方法详解 JavaScript语言的传统方 ...
- ES6——静态属性与静态方法
静态方法只能写在class内,constructor外.通过static关键字声明 静态属性只能写在class外,通过 类名.属性名 = 属性值 声明 //静态属性与静态方法(ES6明确规定,Clas ...
- ES6入门十二:Module(模块化)
webpack4打包配置babel7转码ES6 Module语法与API的使用 import() Module加载实现原理 Commonjs规范的模块与ES6模块的差异 ES6模块与Nodejs模块相 ...
- ES6入门笔记
ES6入门笔记 02 Let&Const.md 增加了块级作用域. 常量 避免了变量提升 03 变量的解构赋值.md var [a, b, c] = [1, 2, 3]; var [[a,d] ...
- es6入门3--箭头函数与形参等属性的拓展
对函数拓展兴趣更大一点,优先看,前面字符串后面再说,那些API居多,会使用能记住部分就好. 一.函数参数可以使用默认值 1.默认值生效条件 在变量的解构赋值就提到了,函数参数可以使用默认值了.正常我们 ...
- ES6入门基础
let和const 一.块级作用域 ES5 只有全局作用域和函数作用域,没有块级作用域,这样的缺点是:1.用来计数的循环变量泄露为全局变量.2.内层变量可能会覆盖外层变量 var tmp = new ...
- es6入门5--class类的基本用法
在ES6之前,准确来说JavaScript语言并无类的概念,却有模拟类的做法.相比在类似java这类传统面向对象语言中通过类来生成实例,js则通过构造函数模拟类来生成实例. 这是因为在JS设计初期,作 ...
- es6入门总结
let和const命令 let命令 循环体的let变量只对花括号作用域可见,花括号外不可见 循环体的语句部分是一个父作用域,而循环体内部是一个单独的子作用域 let声明的变量不存在变量提升,未声明的使 ...
随机推荐
- SQL-W3School-高级:SQL 撤销索引、表以及数据库
ylbtech-SQL-W3School-高级:SQL 撤销索引.表以及数据库 1.返回顶部 1. 通过使用 DROP 语句,可以轻松地删除索引.表和数据库. SQL DROP INDEX 语句 我们 ...
- Android 显示系统:SurfaceFlinger详解
一.Android系统启动 Android设备从按下开机键到桌面显示画面,大致过程如下图流程: 开机显示桌面.从桌面点击 App 图标到 Activity显示在屏幕上的过程又是怎样的呢?下面介绍And ...
- c++ string构造函数学习
#include <iostream>#include <string> using namespace std; int main(){ string a1; cout &l ...
- ios 自动去重
//resultArrM 数据原//_indexArray 过滤后的数据//MYSelectAreaModel 模型 /* 重定义索引 */ - (void)sy_indexArray{ /* 索 ...
- SSD总结
SSD: Single Shot MultiBox Detector 1. Introduction 改进点: 以前的方法都是先搞bounding box,再对box里面做分类.特点:精度高,速度慢. ...
- vue-cli3项目运行时一直发http://localhost:8080/sockjs-node/info?t=1462183700002请求
报错如下图: 解决方式: 一.如果是在开发环境,应该是开发的时候网络环境变更导致,比如你切换无线网络,导致开发服务器的IP地址换了,这样开发服务器会不知道如何确定访问源.开发环境中关闭npm dev ...
- Win10 企业版 激活 批处理
cd %SystemRoot%\System32 wscript.exe slmgr.vbs /upk wscript.exe slmgr.vbs /ipk NPPR9-FWDCX-D2C8J-H87 ...
- Windows下的3389端口渗透
1.Win7.Win2003.XP系统 在CMD命令行开启3389端口:REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" &qu ...
- 3rd.botan
1.HOME 1.官网:https://botan.randombit.net/ Win下 编译步骤:https://botan.randombit.net/handbook/building.htm ...
- OpenGL.资料积累
1.又一种Qt + OpenGL 的离屏渲染方法 - liji_digital的博客 - CSDN博客.html(https://blog.csdn.net/liji_digital/article/ ...