一、JS 中的继承方式:
面向对象:(类和实例)
- 继承 子类继承父类
- 封装 为实例添加方法、为原型增加方法
- 多态(重写、重载)
继承:两个类 A B ,想让 B 类的实例使用 A 类上的属性和方法,就需要让 B 类继承 A 类,我们称 B 类为子类,A 类为父类(超类)
function Super() {
}
function A() {}
A.prototype.say = function () {
console.log('hello World');
};
let b = new B();
// b 是 B 类的实例 想使用 A 类的属性和方法
console.log(b.say); // undefined
// 为什么是 undefined?对象.属性名 先看自己是否有私有属性,如果私有属性没有,就去实例所属类的原型查找公有的属性和方法,如果原型上也没有,就根据原型对象的 __proto__ 一直找到 Object 基类的原型上为止,如果还没有就返回 undefined;
// 现在需要 b 可以使用 A 类的 say 方法,现在就需要继承;
1.1 原型链继承
- 原型继承: 将子类 B 的原型对象 重写成父类 A 的一个实例。
B.prototype = new A(); 把父类共有属性和私有属性变成子类的公有属性
function A() {
}
A.prototype.age = 19;
A.prototype.say = function () {
console.log('来自 A 类原型的 say 方法')
};
function B() {}
B.prototype = new A(); // 把 B 类的原型改写成一个 A 类的实例;
let b = new B();
console.log(b);
console.log(b.__proto__); // {text: 'xxx', __proto__....}
console.log(b.age);
console.log(b.say);
把 B 的原型改写成 A 类的一个实例,此时通过 b.age 访问 b 的 age 属性,首先在私有属性中查找,私有属性中没有 age 属性,接着去 b 所属类的原型(B.prototype)上查找,此时原型对象是 A 的实例对象,在原型对象也没有 age 属性,然后通过原型对象的 proto__ 就找到了A.prototype 上,A 的 prototype 上有 age 属性
- 原型继承是把子类公有的属性和私有的属性都变成了子类私有的属性;
- 缺点:改写子类的原型对象,会导致子类原型对象上的 constructor 属性被改写,需要重新指定继承后的 constructor;
1.2 借用构造函数继承
借用构造函数:把父类当做普通函数,在子类的函数体中 call 执行父类的函数;
function A() {
this.a = 'aa';
this.say = function () {
console.log('A say');
}
}
A.prototype.public = 'public';
function B() {
A.call(this); // this 是 B 类的一个实例,A.call(this) 的意思是把 A 中的 this 就该成 B 的实例(而在 B 的构造函数中 this 就是 B 的实例)这样在 A 中通过 this.xxx = xxx 的方式添加的属性都会添加到 B 的实例身上。
}
let b = new B();
console.log(b); // {a: 'aa', say: fun.....}
let b2 = new B();
console.log(b2.say === b.say); // false;
借用构造函数继承:
- 把父类当做普通函数,在子类的函数体里面,通过 call 方法执行 A.call(this)
- call 方法是用来修改 this 指向的,这样一来就把 A 中的 this 修改成了 b 的实例;在函数 A 中通过 this.xxx = xxx 添加的属性都添加到了 B 的实例身上;
- 特点:只能把父类的私有属性和方法继承为子类的私有属性和方法;
1.3 组合继承
组合继承:原型链继承 + 借用构造函数继承
- 原型链继承:把父类私有的和共有的继承为子类公有的;
- 借用构造函数继承:把父类私有的继承为子类私有的
function A() {
this.a = '私有的'
}
A.prototype.text = '公有';
A.prototype.say = function () {
console.log('A公有的say方法')
};
function B() {
A.call(this); // 借用构造函数继承,继承父类私有的
}
B.prototype = new A(); // 原型链继承,继承父类私有和公有的属性;
B.prototype.constructor = B;
let b = new B();
console.log(b.text); // 公有的
console.log(b.a); // 私有的
- 组合继承也并非没有缺点,组合继承会父类的私有继承两次,一份在借用构造函数继承时成为私有的,而另一份是在原型继承时成为公有的;
1.4 原型式继承
原型式继承:把父类的公有属性继承为子类的公有属性;
创建一个新的对象,并且新对象的 proto** **指向 A.prototype,最后把这个新对象作为 B 类的原型;
- 创建一个对象,并且对象的 proto**** 指向 obj
Object.create(obj)
- 原型式继承示例
function A() {
this.private = 'private私有';
}
A.prototype.public = 'public公有';
function B() {}
B.prototype = Object.create(A.prototype); // 创建一个指定原型的对象 创建一个对象,并且这个对象的 __proto__ 指向 A.prototype
B.prototype.constructor = B; // 原型式继承同样是修改 B 类原型的指向,所以需要重新指定构造函数
let b = new B();
console.log(b.public);
1.5 寄生组合式继承
- 寄生组合式继承:原型式继承 + 借用构造函数继承
function A() {
this.private = '私有属性';
}
A.prototype.public = '公有属性';
function B() {
A.call(this); // 借用构造函数继承
this.name = 'b私有的';
}
// 原型式继承:把父类公有的 继承为子类实例公有的
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
let b = new B(); // {name: '私有的', private: '私有属性'}
console.log(b.private); // 继承过来的私有属性
console.log(b.public); // 继承过来到的公有属性
1.6 冒充对象继承
冒充对象继承:在子类的构造函数中生成一个父类的实例,把父类的这个实例进行遍历,把属性都添加子类的实例上;
function A() {
this.private = '私有属性';
}
A.prototype.public = '公有属性';
function B() {
this.name = 'B私有的属性';
let tmp = new A();
for (let key in tmp) {
this[key] = tmp[key];
}
}
let b = new B();
console.log(b);
1.7 ES6 中的类
- ES5 一个函数就是一个类
- ES6 借鉴后端语言,增加了 class 关键字,创建一个类
- ES5
function Teacher(name, age, subject) {
this.name = name;
this.age = age;
this.subject = subject;
}
Teacher.motor = '传道 授业 解惑';
Teacher.prototype.teach = function () {
console.log(this.name + this.age);
};
let t = new Teacher('mabin', 19, 'js');
- ES6
class Teacher {
constructor (name, age, subject) {
// 这里面通过this.xxx = xxx 是给实例添加私有属性
this.name = name;
this.age = age;
this.subject = subject;
}
添加公有属性
teach () {
console.log(this.name + this.age + this.subject);
}
添加静态方法
static motor = '传道授业解惑';
static getMotor () {
console.log('We are U');
}
}
let t = new Teacher('马宾', 18, 'JS');
t.teach();
console.log(Teacher.motor);
Teacher.getMotor();
1.7 ES6继承
ES6继承:extends 关键字
class A {
constructor (name, age) {
this.name = name;
this.age = age;
}
// 公有方法(添加到原型上)
say () {
console.log(`${this.name} say`);
}
}
// ES6继承时使用 extends 关键字实现继承
class B extends A { B继承A类
constructor (x, y, forName, forAge) {
// 注意:在使用 ES6 的 extends 关键字之前,必须使用 super(); super 表示父类的构造函数
super(forName, forAge); 注意:
this.x = x;
this.y = y;
}
}
let b = new B('x', 'y', 'mabin', 18);
console.log(b);
b.say();
- ES6 的继承原理是:寄生组合式继承
二、事件
2.1 事件及常见的事件
事件:文档(html 元素及 document)或者浏览器的窗口发生的一些特定的交互瞬间;可以监听这些事件,来实现在事件发生时执行特定的操作;
let btn = document.querySelector('.btn');
let box = document.querySelector('.box');
常见的事件:
- 2.1.1 鼠标事件:鼠标动作时触发的事件
- 元素.onclick = function() {} 单击事件
- 元素.ondbclick = function () {} 双击事件
- 元素.onmouseenter = function () {} 鼠标移入
- 元素.onmouseleave = function () {} 鼠标移出
- 元素.onmouseover = function () {} 鼠标滑过
- 元素.onmouseout = function () {} 鼠标划出
- 元素.onmousemove = function () {} 鼠标移动
- 元素.onmousedown = function () {} 鼠标按下
- 元素.onmmouseup = function () {} 鼠标抬起
- 元素.onmousewheel = function () {} 鼠标滚轮滚动
box.onmouseover = function () {
console.log(1111)
};
box.onmouseout = function () {
console.log(2222)
};
box.onmousedown = function () {
console.log('3333')
};
box.onmouseup = function () {
console.log(4444)
};
box.onmousewheel = function () {
console.log(555);
};
- 2.1.2 键盘事件:一般input、textarea、document.body、document、window、document.documentElement 监听键盘事件
- 元素.onkeydown = function() {} 键盘按下
- 元素.onkeyup = function () {} 键盘抬起
let input = document.querySelector('#input');
input.onkeydown = function () {
console.log('123')
};
document.onkeyup = function () {
console.log('keyup')
};
window.onkeydown = function () {
console.log('window key down')
};
- 2.1.3 表单元素事件
- onfocus 获取焦点(光标)事件
- onblur 失去焦点时触发
- onchange 事件,表单的值(value)发生改变时触发
- oninput input、textarea 等元素的输入事件
input.onfocus = function () {
console.log('获取焦点')
};
input.onblur = function () {
console.log('失去焦点')
};
input.oninput = function () {
console.log(this.value);
};
- 2.1.4 系统事件:
- window.onload 页面中所有的资源全部加载完触发
- window.onresize 当前窗口尺寸发生改变时触发
- window.onsroll 滚动条滚动时触发
window.onresize = function () {
console.log('变了、变了');
};
- 2.1.5 移动端事件`
ontouchstart 触摸元素时触发
ontouchend 离开元素时触发
ontouchmove 滑动时触发 - 移动端使用 click 大概有 300ms 的延迟
box.ontouchstart = function () {
console.log('start');
};
box.ontouchend = function () {
console.log('end')
};
2.2 事件对象
在事件触发时,浏览器传递给事件函数的实参;其中包含了本次事件触发的具体信息;
let box = document.querySelector('.box');
box.onclick = function (e) {
console.log(e); // MouseEvent e 一般称为事件对象,
// e.clientX 鼠标点击的位置相对于当前浏览器可视窗口的 左偏移值
// e.clientY 鼠标点击位置相对于当前浏览器可视窗口的 上偏移值
// e.pageX 鼠标点击位置相对于 body 左边缘的偏移值
// e.pageY 鼠标点击位置相对于 body 上边缘的偏移值
// e.target 触发事件的元素(点击事件中是点击的元素)对象,称为事件源
// IE 事件触发时把事件信息挂载在 window.event 这个属性上
};
document.onkeydown = function (e) {
// 键盘事件 KeyboardEvent
console.log(e);
console.log(e.keyCode);
// e.keyCode 键码:键盘上每个键对应一个键码,当 keydown 事件触发时,
};
2.3 默认事件和默认行为
有些元素时由默认行为的,如a标签点击会跳转。
let baidu = document.getElementById('baidu');
// 阻止默认行为:
baidu.onclick = function (e) {
alert(111);
e.preventDefault(); // 阻止元素的事件默认行为
// IE 低版本:e.returnValue = false
return false; // return false 也可以阻止默认行为
};
2.4 事件传播:
let $ = (selector) => document.querySelector(selector);
let outer = $('.outer');
let inner = $('.inner');
let center = $('.center');
document.onclick = function () {
console.log('document')
};
outer.onclick = function () {
console.log('outer')
};
inner.onclick = function () {
console.log('inner')
};
center.onclick = function (e) {
console.log('center');
e.stopPropagation();
};
- 点击 center 会触发 center 的点击事件,同时 inner 和 outer 的以及更高层级的元素的点击事件也会被触发。这种现象称为事件的冒泡机制;
事件的触发过程:事件的触发分为两个三个阶段:捕获阶段、目标阶段、冒泡阶段。在事件触发时,浏览器先从最外层的元素开始找,一直找到事件源的过程称为捕获阶段,触发事件源绑定事件后开始向上冒泡,依次触发父级元素的该事件; - 元素.onXXX = func… 绑定的事件都是 DOM0 级事件,DOM0 级事件都是绑定在冒泡阶段的。
- 取消冒泡:e.stopPropagation()
- IE 低版本:e.cancelBubble = true;
- 取消冒泡后,事件不会再向上传播;更高层级的元素的对应事件不会触发;
2.5 onmouseover 和 mouseenter
let $ = selector => document.querySelector(selector);
let inner = $('.inner');
let outer = $('.outer');
- mouseover
inner.onmouseover = function () {
console.log('inner over');
};
inner.onmouseout = function () {
console.log('inner out')
};
outer.onmouseover = function () {
console.log('outer over')
};
outer.onmouseout = function () {
console.log('outer out')
};
- mouseenter
inner.onmousenter = function () {
console.log('inner enter');
};
inner.onmouseleave = function () {
console.log('inner leave')
};
outer.onmouseenter = function () {
console.log('outer enter')
};
outer.onmouseleave = function () {
console.log('outer leave')
};
- mouseenter & mouseover 的区别:
- over 属于划过事件,从父元素进入子元素,属于离开了父元素,会触发父元素的out事件,然后触发子元素的 over 事件;
- enter 属于进入,从父元素进入子元素,不算离开父元素,不会触发父元素的 leave 事件;
- enter 和 leave 是阻止了事件冒泡的,而 over 和 out 还存在冒泡传播;
所以对于父子嵌套的这种情况,使用 over 会触发很多不可控的情况,使用 enter 更加简单。