1. ES6 Class 类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
1.1 . 复习构造函数
类的功能是创建对象的,js之前实现类的功能需要利用构造函数模拟类的功能,通过new操作符实例化一个对象,对象上的原型[[Prototype]]上有个constructor方法指向构造函数
function person(name,age) { //构造函数
this.name=name;
this.age=age
}
person.prototype.sayName = function(){ //原型方法
console.log(`我的名字叫${this.name}。`)
}
person.prototype.sayAge = function(){ //原型方法
console.log(`我今年${this.age}岁了`)
}
person.run=function(){ //静态方法
console.log('静态方法')
}
let student =new person('xiaoming',18)
console.log(student);
console.log(student.sayAge());
console.log(student.sayName());
console.log(person.run()); //静态方法需要使用构造函数调用
但是之前讲了一个合并对象的,那么我们可以将新增的方法放在一个对象里合并到原型上
<script type='module'>
let Person = class { //类名为Person的ES6的类的声明
//之前es5写的类功能分为构造函数,原型方法,静态方法,最终实例化,都是分开的。现在es6实现了整体的概念,将他们罗列到一个大括号容器里,其不是代码块,也不是对象,里面不加逗号。
constructor(name, age) { //构造函数
this.name = name;
this.age = age;
this.aa = function () { //实例对象上的方法,生成到实例对象上的方法
console.log(这是实例方法);
}
};
//除了constructor方法、使用关键字static的方法,其他方法都是原型上的方法
sayName() { //原型方法
console.log(`我的名字叫${this.name}。`)
};
sayAge() { //原型方法
console.log(`我今年${this.age}岁了`)
};
static run() { //关键字static 写的方法是静态方法
console.log(`这是静态方法`);
}
}
let student = new Person('李四', 20); //通过类实例化对象时本质上执行类内部的constructor函数
console.log(student);
student.sayAge()
console.dir(Person) //对象方式打印这个类,可以看到静态方法
Person.run() //在这种类里静态方法只能通过类名调用
console.log(typeof Person); //通过typeof检测类名结果是function,因此class声明类实际上是创建了一个具有构造函数方法行为的函数
console.log(Person.prototype);
</script>
<script type='module'>
let Person = class { //匿名类的表达式赋值给变量
}
</script>
1.2 ES6 变形的类Class
// 这是类 不是json 所以里面不需要加逗号
class Person{
// 这是一个构造方法(函数),只要new Person() 这个构造函数会自动执行
constructor(name,age){
console.log(`构造函数执行了,name:${name},age:${age}`)
}
//以下是构造函数原型上的方法
showName(){
return `名字:${this.name}`;
} // 不需要加逗号,加逗号就报错
showAge(){
return `年龄:${this.age}`;
}
}
// 这个Person也是要给函数,typeof Person 打印function
let p1 = new Person("wuwei",18);
console.log(p1.showName());
console.log(p1.showAge());
表达式的类(不推荐)
var Person = class {
constructor(){
this.name = "wuwei";
}
showName(){
return `名字:${this.name}`
}
}
2. constructor
constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
class Fn {
}
// 等同于
class Fn {
constructor() {}
}
constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
//constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。
3. 类必须使用new调用
类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
4. 方法名字可以是变量
var aaa = 'wu';
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
[aaa]() {
return `此时属性名是一个变量,就是wu`
}
}
// console.log(typeof Person)
let p1 = new Person("wuwei", 18);
console.log(p1.wu()); // 此时不能通过aaa调用函数,aaa是变量,通过wu调用,可以通过[aaa]调用 p1[aaa]();
console.log(p1[aaa]());
这里就可以发现属性名可以为表达式
var aa = 'wu';
var bb = 'wei';
var obj = {
[aa + bb]: "无为"
}
5. ES6 class类没有提升和预解析的功能
也就是说如果写法会报错
var p1 = new Person()
console.log(p1);
class Person{
constructor(){
this.name = "aaa"
}
}
但是以前通过ES5 构造函数模拟类是可以变量提升的,因为本身就是函数
// 这种写法构造函数会提升,正常打印一个对象
var p1 = new Person()
console.log(p1);
function Person(){
this.name = "aaa"
}
6. get set
取值函数 get 存值函数 set
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
get aa(){
console.log(`成功获取a的值`)
return this.age;
}
set aa(val){
console.log(`成功设置a的值为${val}`);
this.age = val;
}
}
var p1 = new Person('wuwei',18)
7.静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
ES6 明确规定,Class 内部只有静态方法,没有静态属性。
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
showName(){
return `这是通过构造对象调用方法`;
}
static aaa(){
return `这是静态方法,是通过类来调用的方法`;
}
}
var p1 = new Person('wuwei',18)
console.log(p1.showName);
console.log(Person.aaa());
8. 继承
子类继承父类
8.1 之前的子类继承父类
// 父类
function Person(name){ //构造函数
this.name = name;
}
Person.prototype.showName = function(){ //构造函数原型对象上的方法
return `名字是:${this.name}`;
}
// 子类
function Student(name,skill){
Person.call(this,name); // 继承父类的属性 处理构造函数的继承,调用构造函数的方法,同时改变this指向
this.skill = skill;
}
Student.prototype = new Person; // 继承父类的方法,继承原型链
var stu = new Student("wuwei","逃课");
console.log(stu);
console.log(stu.name);
console.log(stu.showName())
8.2 ES6 类的继承 extends
class Person{ //基类
constructor(name){
this.name = name;
}
}
class Student extends Person{ //派生类 es6的继承通过关键字extends
// constructor(name){ //这种继承 没法添加自己扩展的属性和方法,添加就报错
// this.name = name;
// }
}
let stu = new Student('张三')
console.log(stu);
以上这种继承 没法添加自己扩展的属性和方法,添加就报错
通过super()扩展自己的属性和方法,super就是调用基类中的构造函数
super注意点:
1、super只可在派生类的构造函数中使用,如果尝试在非派生类(未使用extends的类)或函数中使用则会导致程序报错
2、在构造函数中访问this之前一定要调用super(),因为super()负责初始化this,如果在super之前访问this会导致报错
3、如果不想调用super(),则唯一的方法就是让类的构造函数显示返回一个对象
// 父类
class Person{
constructor(name){
this.name = name;
}
showName(){
console.log('父类的showName方法');
return `名字:${this.name}`;
}
}
// 子类
class Student extends Person{
constructor(name,skill){
super(name); // 先执行父类的属性,继承后在设置自己的属性
this.skill = skill;
}
// 继承父类的方法同时扩展内容
showName(){
super.showName(); // 先执行父类的showName方法
// 在设置自己的showName方法
console.log('执行了子类里面的showName');
}
// 设置自己的方法
showSkill(){
return `技能:${this.skill}`
}
}
var stu = new Student("wuwei","逃课");
console.log(stu.name);
console.log(stu.showName())
<script type='module'>
class Person {
constructor(name) {
this.name = name;
}
}
class Student extends Person {
constructor(name, age) {
super(name) ; //super继承基类中的中的构造函数,修正初始化this,不能在super之前使用this
// this.name = name; //这个this.name不是继承了父类的this.name,不写是undefined。应该把那么放到super中,相当于扩展,执行super相当于执行基类的构造函数
this.age = age;
// return { name, age }
}
}
let stu = new Student('张三', 18)
console.log(stu);
</script>
类方法的遮蔽,派生类中的方法总会覆盖基类中的同名方法
//遮蔽
let a =10;
function aa(){
let a =20;
function bb(){
let a =30;
console.log(a);
}
bb() //如果aa() bb()不执行a是没有值的
}
aa()
//派生类遮蔽基类中的方法
class Person {
constructor(name) {
this.name = name;
}
sayName() {
return (`'基类'中的${this.name}`); //基类 原型对象的原型上的方法
}
}
class Student extends Person {
constructor(name, age) {
super(name);
this.age = age;
}
sayName() {
return (`'派生类'中的${this.name}`); //派生类 原型对象上的方法
}
}
let stu = new Student('张三', 18)
console.log(stu); //控制台能看到原型上的方法 原型的原型sayName()方法
console.log(stu.sayName());
如果想调用基类中的方法,则可以通过super在派生类同名方法中手动调用基类方法
//派生类扩展基类中的方法(调用基类的方法)
class Person {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`'基类'中的${this.name}`);
}
}
class Student extends Person {
constructor(name, age) {
super(name);
this.age = age;
}
sayName() {
console.log(super.sayName()); //super超级的意思,代表的就是基类。通过super取到基类中的sayName(),先执行基类中的方法,再执行派生类中的同名方法
console.log(`'派生类'中的${this.name}`);
}
}
let stu = new Student('张三', 18)
console.log(stu.sayName());
拖拽例子:
<style>
* {
margin: 0;
padding: 0;
}
#box {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<!-- es5的拖拽 -->
<div id="box"></div>
<script>
let dixTop = 0, dixLeft = 0;//定义两个信号量,记住top和left的值
box.onmousedown = function (e) {// 有了值就可以绑定鼠标点击事件
console.log(e);
let dixL = e.clientX - dixLeft, dixT = e.clientY - dixTop; //鼠标到元素左上角的距离= 鼠标距离页面的距离 - 元素距离页面左上角的距离
console.log(dixL, dixT);
box.onmousemove = function (e) { //鼠标移动事件
dixLeft = e.clientX - dixL;
dixTop = e.clientY - dixT;
console.log(dixTop, dixLeft);
this.style = `top:${dixTop}px;left:${dixLeft}px`
}
box.onmouseup = function () { //鼠标松开时还是跟着拖动,需要解绑自己和移动事件
box.onmouseup = null;
box.onmousemove = null
}
}
</script>
<style>
* {
margin: 0;
padding: 0;
}
#box {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<!-- es5的拖拽 -->
<div id="box"></div>
<script>
//拖动速度过快时,浏览器来不及绘制,鼠标离开了元素,无法触发拖动事件。把拖动事件绑到document 样式style的this为box,就算鼠标脱落了元素,元素也会一直跟着鼠标
let dixTop = 0, dixLeft = 0;
box.onmousedown = function (e) {
console.log(e);
let dixL = e.clientX - dixLeft, dixT = e.clientY - dixTop;
console.log(dixL, dixT);
document.onmousemove = function (e) { //事件绑给document
dixLeft = e.clientX - dixL;
dixTop = e.clientY - dixT;
console.log(dixTop, dixLeft);
box.style = `top:${dixTop}px;left:${dixLeft}px` //修改元素box的样式
}
document.onmouseup = function () { //解绑也要给document 里面为this
this.onmouseup = null;
this.onmousemove = null
}
}
</script>
<style>
* {
margin: 0;
padding: 0;
}
#box {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
//将box修改为this的写法,更优化了
let dixTop = 0, dixLeft = 0;
box.onmousedown = function (e) {
let dixL = e.clientX - dixLeft, dixT = e.clientY - dixTop;
console.log(dixL, dixT);
document.onmousemove = function (e) {
dixLeft = e.clientX - dixL;
dixTop = e.clientY - dixT;
console.log(dixTop, dixLeft);
this.style = `top:${dixTop}px;left:${dixLeft}px` //这里也修改为this,要让它的函数强制修改this
}.bind(this)
document.onmouseup = function () {
this.onmouseup = null;
this.onmousemove = null
}
}
</script>
function Drag(className) {
this.dom = document.createElement('div');
this.dom.className = className;
document.body.appendChild(this.dom)
this.disX = 0;
this.disY = 0;
// 初始化
this.init()
}
Object.assign(Drag.prototype, {
// 初始化函数
init() {
this.dom.onmousedown = function (e) {
this.down(e)
document.onmousemove = this.move.bind(this)
document.onmouseup = this.up
}.bind(this)
},
down(e) {
// console.log(e.clientX, e.clientY)
this.disX = e.clientX - this.dom.offsetLeft;
this.disY = e.clientY - this.dom.offsetTop;
// console.log(this.disX, this.disY)
},
move(e) {
// console.log(this)
this.dom.style.left = e.clientX - this.disX + 'px';
this.dom.style.top = e.clientY - this.disY + 'px';
},
up(e) {
// console.log(this)
document.onmousemove = null;
document.onmouseup = null;
}
})
let left = new Drag('left')
ES6 的拖拽(ES5拖拽不太适合给多个元素绑定拖拽,会很乱)
<style>
* {
margin: 0;
padding: 0;
}
.abox,
.bbox {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
.bbox {
background-color: skyblue;
}
</style>
</head>
<body>
<script>
class Drag { //类
constructor(className) { //构造函数
this.dom = document.createElement('div'); //创建div节点
this.dom.className = className; //节点上添加属性lassName,也就是参数
document.body.appendChild(this.dom) //放到页面中body元素中
this.disX = 0; //信号量用的是属性,变量不适合多个元素的拖拽,会发生覆盖
this.disY = 0;
// 初始化代码
this.init()
}
// 初始化函数
init() {
this.dom.onmousedown = function (e) {
this.down(e) //差值
document.onmousemove = this.move.bind(this) //鼠标移动事件。不强制绑定this就是document
document.onmouseup = this.up //鼠标抬起事件
}.bind(this) //强制绑定使内部的this不再指向内部的document,需要指向外部的this
}
down(e) { //计算差值
// console.log(e.clientX, e.clientY)
this.disX = e.clientX - this.dom.offsetLeft;
this.disY = e.clientY - this.dom.offsetTop;
// console.log(this.disX, this.disY)
}
move(e) {
// console.log(this)
this.dom.style.left = e.clientX - this.disX + 'px'; //元素属性值+px
this.dom.style.top = e.clientY - this.disY + 'px';
}
up(e) {
// console.log(this)
document.onmousemove = null;
document.onmouseup = null;
}
}
let abox = new Drag('abox') //对象,所有的属性都是对象自己控制
let bbox = new Drag('bbox')
// bbox{
// dom:<div class="bbox"></div>,
// disX:0,
// dixY:0
// }
</script>
通过继承创建有边界的拖拽,拖拽控制在边界内部
<style>
* {
margin: 0;
padding: 0;
}
.abox,
.bbox,
.right {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: pink;
}
.bbox {
background-color: skyblue;
}
.right {
background-color: tomato;
}
</style>
</head>
<body>
<script>
class Drag {
constructor(className) {
this.dom = document.createElement('div');
this.dom.className = className;
document.body.appendChild(this.dom)
this.disX = 0;
this.disY = 0;
this.init()
}
init() {
this.dom.onmousedown = function (e) {
this.down(e)
document.onmousemove = this.move.bind(this)
document.onmouseup = this.up
}.bind(this)
}
down(e) {
this.disX = e.clientX - this.dom.offsetLeft;
this.disY = e.clientY - this.dom.offsetTop;
}
move(e) {
this.dom.style.left = e.clientX - this.disX + 'px';
this.dom.style.top = e.clientY - this.disY + 'px';
}
up(e) {
document.onmousemove = null;
document.onmouseup = null;
}
}
let abox = new Drag('abox')
let bbox = new Drag('bbox')
class LimitDrag extends Drag { //扩展父类的拖拽
move(e) {
super.move(e); //继承父类
this.dom.style.left = Math.max(0, this.dom.offsetLeft) + 'px'; //扩展自己的边界限定,不能超出边界
this.dom.style.left = Math.min(window.innerWidth - this.dom.offsetWidth, this.dom.offsetLeft) + 'px';
this.dom.style.top = Math.max(0, this.dom.offsetTop) + 'px';
this.dom.style.top = Math.min(window.innerHeight - this.dom.offsetHeight, this.dom.offsetTop) + 'px';
}
}
let right = new LimitDrag('right') //扩展的拖拽