文章目录
继承
当一个不具有某个功能或特征的事物,通过某种方式,能够使用另一个具有某个功能或特征的事物的功能或特征的方式叫继承
实例和类的关系是基本的继承
function fn(n){
this.name = n
}
fn.prototype.show = function(){
console.log(this.name)
}
var f = new fn("admin")
f.name
f.show();
// new的原理
// 1. 创建一个新对象
// 2. 改变函数的this指向这个新对象
// 3. 将这个新对象的原型属性__proto__指向了函数的原型对象prototype
// 4. 检测原函数是否主动返回对象,如果没有,返回这个新对象
类与类之间的继承
改变this指向的继承
- 构造函数继承
- 只能继承构造函数的内容,不能继承原型上的内容
- 多继承
// 改变this指向继承
function test01() {
this.test01 = "test01"
}
function test02() {
this.test02 = "test02"
}
function test03() {
this.test03 = "test03"
}
function test() {
this.test = "test";
test01.call(this);
test02.apply(this);
// 因为bind返回的返回值是改变this执行之后的新函数,所以需要执行这个新函数
test03.bind(this)();
}
const otest = new test();
console.log(otest);
原型继承 - 原型对象继承 - 拷贝的是prototype
- 只能继承原型对象上的内容,不能继承构造函数的内容
- 注意参数的传递
- 原型对象是一个对象!注意拷贝方式,深拷贝
- 多继承
function test1() {}
test1.prototype.show = function () {
console.log("This is test1's method");
}
function test2() {}
test2.prototype.say = function (msg) {
this.msg = msg;
console.log(this.msg);
}
const t1 = new test1();
const t2 = new test2();
t2.say("test")
console.log(t1)
console.log(t2)
function test() {}
// 用对象的深拷贝复制其他的对象
for (let i in test1.prototype) {
test.prototype[i] = test1.prototype[i];
}
for (let i in test2.prototype) {
test.prototype[i] = test2.prototype[i];
}
const test3 = new test();
test3.say("test3")
console.log(test3)
console.log(test3.show())
原型继承 - 原型链继承
- 既能继承构造函数的内容,又能继承原型对象上的内容
- 不方便处理参数
- 不方便实现多继承
- 会多消耗一些性能,多了一层原型链
function Test(msg) {
this.info = msg;
}
Test.prototype.say = function () {
console.log(this.info);
}
const test = new Test("test");
test.say();
console.log(test)
console.log(test.info)
function Test2(msg) {
}
Test2.prototype=new Test("test2");
const test2 = new Test2();
// test2.say = function(){
// console.log("info")
// }
console.log(test2);
console.log(test2.info);
// console.log(test2.say())
混合继承 - 改变this指向+原型对象继承
- 既能继承构造函数的内容,又能继承原型上的内容
- 原型对象是一个对象!注意拷贝方式,深拷贝
- 多继承
function Test(msg) {
this.msg = msg;
this.show = function () {
console.log(this.msg)
}
}
Test.prototype.say = function () {
console.log("I am test");
}
const test = new Test("test");
test.show()
test.say();
console.log(test);
function Test01(msg) {
// 改变this指向只能继承构造函数
Test.call(this, msg);
}
// 继承Test的原型对象
Test01.prototype={
constructor:Test01,
// 展开Test对象的prototype中的属性然后赋值给Test01的prototype中的属性(对象的拷贝,有深浅拷贝)
...Test.prototype,
say1:function(){
console.log("I am test1")
}
};
const test01 = new Test01("test1");
console.log(test01);
test01.say()
test01.say1()
ES6提供的class继承
- 语法层面上的继承
- 原理:构造函数继承+原型链继承
class Test {
constructor(msg) {
this.msg = msg;
this.say();
}
say() {
console.log("I am " + this.msg)
}
}
const test = new Test("test");
console.log(test);
test.say();
class Test01 extends Test{
constructor(msg){
super(msg);
}
}
const test1 = new Test01("test1");
console.log(test1);
test1.say();
继承的应用场景
只要有重复功能出现,就可以将重复功能抽象成公共类,其他原有类都先继承公共类,再做具体的实现
例子-拖拽的继承
html
<div class="box1"></div>
<div class="box2"></div>
css
div {
width: 100px;
height: 100px;
position: absolute;
}
.box1 {
background-color: cornflowerblue;
}
.box2 {
top: 200px;
background-color: darkslateblue;
}
js
// 拖拽的功能
// 公共类
class Drag {
// ele是要操作的元素
constructor(ele) {
this.ele = ele;
this.clientWidth = document.documentElement.clientWidth;
this.clientHeight = document.documentElement.clientHeight;
this.addEvent();
}
addEvent() {
const that = this;
this.ele.addEventListener("mousedown", function (eve) {
// 添加事件对象,方便后面获取坐标
that.downE = eve || window.event;
that.down();
});
}
down() {
const that = this;
document.onmousemove = function (eve) {
that.moveE = eve || window.event;
that.move();
}
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
}
}
}
class Drag1 extends Drag {
constructor(ele) {
super(ele);
}
move() {
this.ele.style.left = this.moveE.pageX - this.downE.offsetX + "px";
this.ele.style.top = this.moveE.pageY - this.downE.offsetY + "px";
}
}
const box1 = document.querySelector(".box1");
const test1 = new Drag1(box1);
console.log(test1);
class Drag2 extends Drag {
constructor(ele) {
super(ele);
}
move() {
let left = this.moveE.pageX - this.downE.offsetX;
let top = this.moveE.pageY - this.downE.offsetY;
if (left <= 0) {
left = 0;
} else if (left >= this.clientWidth - this.ele.offsetWidth) {
left = this.clientWidth - this.ele.offsetWidth;
}
if (top <= 0) {
top = 0;
} else if (top >= this.clientHeight - this.ele.offsetHeight) {
top = this.clientHeight - this.ele.offsetHeight;
}
this.ele.style.left = left + "px";
this.ele.style.top = top + "px";
}
}
const box2 = document.querySelector(".box2");
const test2 = new Drag2(box2);
console.log(test2);
闭包
是一个定义在一个函数内部的函数
函数嵌套时,利用作用域的嵌套,将原本的局部变量变成私有变量的方式(内部函数调用外部函数的局部变量,那么这个时候,这个局部变量就会变成内部函数的私有变量)
闭包的原理
函数的词法作用域
- 函数的定义作用域:函数被定义的地方
- 函数在执行时,可以使用自身词法作用域中的数据(变量)
闭包的应用
- 循环中的事件处理函数内,使用循环每次执行到的计数器
- 可以给系统功能的不能传参的回调函数传参
- 可以给“事件处理函数”传参
实现调用函数累加的效果
const test = (function fn(){
let a=0;
// 将函数f作为返回值给函数fn,函数fn再执行然后作为test的执行结果
return function f(){
a++;
console.log(a);
}
})()
console.log(test)
test()
test()
test()
function f2(){
const aa=1;
}
console.log(f2)
用闭包实现点击当前对象的效果
html
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
js
const liList = document.querySelectorAll("li");
for (var i = 0; i < liList.length; i++) {
liList[i].index = i;
liList[i].onclick = function () {
console.log(this.index);
}
}
// 通过闭包实现点击
for (var i = 0; i < liList.length; i++) {
// 把功能放入一个匿名函数体中,自动执行
(function (i) {
liList[i].onclick = function () {
console.log(i);
}
})(i);
}
for (var i = 0; i < liList.length; i++) {
// 把i放入函数f1中作为函数f2的返回值给f2,点击函数再执行f2
liList[i].onclick = (function f2(i) {
return function f1() {
console.log(i);
}
})(i);
}
// 用let关键字触发词法作用域
for(let i=0;i<liList.length;i++){
liList[i].onclick=function(){
console.log(i);
}
}
实现延时器的效果
setTimeout(test("test"), 2000);
// 这样写会直接执行,延时器没有作用
function test(msg){
console.log(msg);
}
// 闭包,将函数test01作为返回值给函数test,将来执行的时候执行test01内的语句
function test(msg) {
return function test01() {
console.log(msg);
}
}
闭包的特点
- 可以在作用域外部操作作用域内部的变量
- 可以读取函数内部的变量
- 内部和外部沟通的桥梁
- 让这些变量的值,始终保存在内存中,不会在调用结束后被系统回收,避免全局变量命名空间的污染
- 消耗性能
- 闭包有可能会造成内存泄漏(低版本IE8-)
- 小心使用
设计模式
设计模式是一种可以复用的解决方案,是一套被反复使用,多数人知晓的,经过分类的,代码设计经验的总结
1. 单例模式
先判断这个函数的属性是否存在,初始肯定是不存在的,然后去创建这个属性,利用对象的键值对去创建,如果这个属性存在,则这个属性作为返回值给这个函数
- 单个实例,只有一个对象,多次创建,返回同一个对象
function fn() {
// 先判断这个函数的属性是否存在,初始肯定是不存在的,然后去创建这个属性,利用对象的键值对去创建,如果存在这个属性,则这个属性作为返回值给这个函数
if (!fn.test) {
// 利用对象的键值对,创建对象
fn.test = {
test: "test"
};
}
return fn.test;
}
const test1 = new fn();
const test2 = new fn();
console.log(test1)
console.log(test2)
console.log(test1 === test2) // true
- 通过手动模拟 new 的原理,实现单例模式
// 手动模拟new原理实现单例模式
// 创建一个新对象
// 把函数的this指向这个新对象
// 新对象的__proto__指向构造函数的prototypeof
// 看函数是否有返回值,如果没有就返回这个新对象
function Test() {
if (!Test.obj) {
Test.obj = {
obj:"构造函数"
}
Test.obj.__proto__=Test.prototype;
}
return Test.obj;
}
Test.prototype.show=function () {
console.log("原函数");
return "原函数的show方法"
}
const test1 = new Test();
const test2 = new Test();
console.log(test1);
console.log(test2);
console.log(test1 === test2);
console.log(test1.show());
test2.show();
- 单例模式的弹出框(多次创建都只有一个对象)
html
<dialog>提示框</dialog>
<input type="button" value="信息" id="btn1">
<input type="button" value="成功" id="btn2">
<input type="button" value="失败" id="btn3">
<input type="button" value="警告" id="btn4">
js
const obtn1 = document.getElementById("btn1")
const obtn2 = document.getElementById("btn2")
const obtn3 = document.getElementById("btn3")
const obtn4 = document.getElementById("btn4")
// 通过传参实现不同的效果
obtn1.onclick = function () {
new Toast({
message: "这是一句提示信息",
type: "info"
});
}
obtn2.onclick = function () {
new Toast({
message: "xxx成功",
type: "success"
});
}
obtn3.onclick = function () {
new Toast({
message: "xxx失败",
type: "error"
});
}
obtn4.onclick = function () {
new Toast({
message: "注意!危险",
type: "warning"
});
}
function Toast(ops) {
if (!Toast.obj) {
Toast.obj = {};
Toast.obj.ele = document.createElement("dialog");
document.body.appendChild(Toast.obj.ele);
}
const that = Toast.obj;
that.ele.innerHTML = ops.message;
let color = "";
if (ops.type === "info") {
color = "#55f";
} else if (ops.type === "success") {
color = "#5f5";
} else if (ops.type === "error") {
color = "#f55";
} else if (ops.type === "warning") {
color = "yellowgreen";
}
that.ele.style.cssText = `border-color:${color};color:${color};display:block;`;
clearTimeout(that.t);
that.t = setTimeout(() => {
that.ele.style.display = "none";
}, 2000);
return that;
}
2. 观察者模式(发布订阅者)
- 发布:被观察者,主题对象
- 订阅:观察者
- 一个主题对象发布或更新信息,多个订阅者接收信息,并根据信息作出不同的功能处理
- 可以实现广播通信,一对多关系,耦合低
- 使用观察者模式的好处
- 支持简单的广播通信,自动通知所有已经订阅过的对象
- 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性
- 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用
// 观察者根据订阅的内容做出不同的功能
// 发布者
function Son(name) {
this.name = name;
this.station = function (station) {
this.station = station;
}
}
// 观察者
function Father() {
this.listen = function (station) {
// 根据不同的状态做不同的反应
switch (station) {
case "play": {
console.log("Son,enjoin it!");
break;
}
case "sleep": {
console.log("Have a good neight");
break;
}
default: {
console.log("I am Father,My son " + station);
break;
}
}
}
}
function Mather() {
this.listen = function (station) {
// 根据不同的状态做不同的反应
switch (station) {
case "play": {
console.log("Dont't play, go to staty!");
break;
}
case "sleep": {
console.log("My little boy");
break;
}
default: {
console.log("I am Mather,My son " + station);
break;
}
}
}
}
function Sister() {
this.listen = function (station) {
console.log("My borther do " + station);
}
}
const son = new Son("son");
son.station("sleep");
console.log(son);
const father = new Father();
father.listen(son.station);
console.log(father);
const mather = new Mather();
mather.listen(son.station);
console.log(mather);
const sister = new Sister();
sister.listen(son.station);
console.log(sister);
3. 适配器模式
- 适配器
- 假如现有数据A,和功能B,功能B只能接受B类型的数据,可是现有的数据类型是A
- 将数据A改成B类型的数据 x
- 将功能B接受的数据类型改成A类型 x
- 此时需要一个适配器,将数据A进行一层包装,让数据A能被B类型的数据接收,所以包装的时候将B中A没有的功能给A,在A新增的功能里面说明此功能A并没有
// 模拟电脑的功能
function Computer() {
// 看视频
this.view = function () {
console.log("Warthing Video");
}
// 写程序
this.program = function () {
console.log("Write program");
}
}
// 模拟电视的功能
function Tv() {
//看视频
this.view = function () {
console.log("Warthing Video");
}
}
// 测试模块
function Test(obj) {
obj.view();
obj.program();
}
const computer = new Computer();
const tv = new Tv();
console.log(computer)
console.log(tv)
// 进行测试
// Test(computer)
// 没有对tv进行适配之前是会报错的,因为tv就没有program方法
// Test(tv)
// 适配
function Adaptation(obj) {
obj.program = function () {
console.log("没有这个功能");
}
// 处理之后再返回这个对象,执行的是这个函数的返回值
return obj;
}
Test(computer);
// 经过适配后的
Test(Adaptation(tv));
4. 代理模式
- 代理
- 系统功能A在调用系统功能B的过程中,传输了数据,我们需要记录数据,或改写数据,或拦截数据
- 先断开执行,获取系统功能B的执行权限,编写代理程序执行功能B,让功能A调用代理程序,将原有的数据,发给代理程序,代理程序将数据发给功能B
// A(发)要向B(收)发送信息,经过代理
function B(name) {
this.name = name;
}
// 发送者要先获取要接收者的基本信息
function A(b) {
this.name = "a";
this.b = b;
this.send = function (msg) {
console.log("这是" + this.name + "发给" + this.b.name + "的" + msg);
}
}
// 代理通过收发人信息发送并记录信息内容
function Agent(b) {
this.measges = [];
this.b = b;
this.send = function (msg) {
const a = new A(this.b);
// 拦截并记录
this.measges.push({
sentName: a.name,
accptNmae: this.b.name,
msg: msg,
data: Date.now()
});
a.send(msg);
}
}
const b = new B("b");
console.log(b);
const agent = new Agent(b);
console.log(agent);
agent.send("信息");
5. 策略模式
- 策略,计划
- 根据程序执行过程中产生的不同的状态或信息,决定后续的功能执行
- 利用不同的功能接口(方法)决定执行不同的功能
const Obj={
test01:function(){
return "test01";
},
test02:function(){
return "test02";
},
test03:function(){
return "test03";
}
}
// 根据不同的类型执行不同的功能
function select(type) {
// 这里的()是使用返回值的地方执行这个函数
return Obj[type]();
}
console.log(select("test01"));
console.log(select("test02"));
console.log(select("test03"));
6. 组合模式
- 按照一定的组织关系,将多个对象进行组合
- 树状结构(html结构)
- 组合模式就是为动态的html而生的
- 组合模式为了能够实现批量操作,节省操作过程,使用了递归思想,所以消耗比较多的性能
- 利用组合器将对象组合起来
- 枝对象:只有具有子对象,无论层级在哪,都是枝对象
- 叶对象:只有不具有子对象,无论层级在哪,都是叶对象
例子:动态操作html结构,批量操作属性
css
img {
width: 100px;
}
* {
padding: 10px;
}
js
// 叶
// 创建一个图片标签
class Item {
constructor(src) {
// 当前对象作为创建出的img标签
this.ele = document.createElement("img");
// 当前对象上的图片路径属性
this.ele.src = src;
}
// 叶对象没有子对象
add() {
console.log("叶对象无法添加子对象");
}
remove() {
console.log("叶对象无法删除子对象");
}
// 添加边框的方法
border() {
this.ele.style.border = "1px solid black";
}
// 取消边框的方法
none() {
this.ele.style.border = "none";
}
}
// 枝
// 创建一个div
class Team {
constructor(id) {
// 当前对象作为创建出的div标签
this.ele = document.createElement("div");
// 标签上设置属性id,方便操作
this.ele.id = id;
// 预先设置子节点
this.child = [];
}
// 添加子节点的方法
// 参数为子对象
add(chile) {
// 将子对象添加到子节点的数组中
this.child.push(chile);
// DOM操作,将创建好的当前子节点添加到叶节点中
this.ele.appendChild(chile.ele);
}
// 移出子节点的方法
remove(child) {
// 用数组的some方法通过id寻找要删除的子节点
// 初始化索引
let i = 0;
this.child.some((val, index) => {
// some方法返回true时将找到的索引赋给初始索引
i = index;
// 当传入的值找到时为true
return val === child;
});
// 在数组中删除这个值
this.child.splice(i,1);
// DOM里面移出这个元素
child.ele.remove();
}
// 添加边框的方法
border() {
this.ele.style.border = "1px solid black";
// 通过操作批量操作子达到调用这个方法操作子的效果
this.child.forEach(val => {
// 递归
val.border();
});
}
// 取消边框的方法
none() {
this.ele.style.border = "none";
this.child.forEach(val => {
// 递归
val.none();
});
}
}
// 创建叶对象,插入到其枝
const img1 = new Item("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
const img2 = new Item("https://fanyi-cdn.cdn.bcebos.com/static/translation/img/header/logo_e835568.png");
const img3 = new Item("https://inews.gtimg.com/newsapp_bt/0/0923142908664_4470/0");
console.log(img1);
// 创建相对于上一个叶对象的枝对象,把上一个叶对象放入这个枝对象中
const imgBox1 = new Team("imgBox1");
const imgBox2 = new Team("imgBox2");
console.log(imgBox1);
imgBox1.add(img1);
imgBox2.add(img2);
// 再创建一个大的枝对象将上面创建的叶对象都放入其中,再将这个枝对象插入到页面上
const box = new Team("box");
box.add(imgBox1);
box.add(imgBox2);
box.add(img3);
document.body.appendChild(box.ele);
console.log(box);
// 批量操作
box.border();
img1.none();
img1.add(img2);
imgBox2.none();
7. 抽象工厂模式
- 抽象
- 工厂
- 在工厂模式的继承之上,再次对同一个实例的相同属性进行抽象,抽象成一个公共对象,在做具体的工厂模式的创建
// 原类
// 接收一个id绑定id属性
function Test(id) {
this.id = id;
}
// 原类的方法
Test.prototype.show = function () {
console.log(`这是${this.id},新增的信息:${this.msg}`);
}
// 用函数再次抽象,给原类一些相同的属性
function Test01(id) {
// 先创建原类对象
const test = new Test(id);
// 给这个对象新增属性
test.msg = "这是这个类的公共信息test01";
// 最后返回这个对象,在new这个类时就把新增的属性加到原类身上了
return test;
}
// 用函数再次抽象,给原类一些相同的属性
function Test02(id) {
// 先创建原类对象
const test = new Test(id);
// 给这个对象新增属性
test.msg = "这是这个类的公共信息test02";
// 最后返回这个对象,在new这个类时就把新增的属性加到原类身上了
return test;
}
const test01 = Test01("test01");
const test02 = Test01("test02");
const test03 = Test02("test03");
console.log(test01);
console.log(test02);
console.log(test03);
test01.show();
test02.show();
test03.show();
// 执行这个函数new出来的show方法都一样,代表这个同一个类
console.log(test01.show===test02.show); // true
console.log(test01.show===test03.show); // true
console.log(test03.show===test02.show); // true
8. MVC模式
- M:model:模型层,只负责数据的管理
- V:view:视图层,只负责将来数据展示的视图的管理
- C:control:控制器层,负责接收用户的指令,根据指令,选择不同的模型,将模型中的数据,加给不同的视图渲染
- 工作流程:
- 控制器接收到指令后,根据指令读取指定的模型,拿到数据
- 控制器再根据指令获取指定的视图
- 将第一步读取到的数据,传给获取到的视图
- 由视图负责将接收到的数据,渲染
// 模型层,只负责数据的管理
class Model {
constructor() {
this.data01 = "data01";
this.data02 = "data02";
}
Data01() {
return this.data01;
}
Data02() {
return this.data02;
}
}
// 视图层,只负责将来数据展示的视图的管理
class View {
constructor() {
this.msg = "视图层,只渲染数据";
}
// 展示数据的方式
// 参数为数据
show01(data) {
console.log(data);
}
show02(data) {
document.write(data);
}
show03(data) {
alert(data);
}
}
// 控制器层,负责接收用户的指令,根据指令,选择不同的模型,将模型中的数据,加给不同的视图渲染
class Control {
constructor() {
// 创建模型和视图对象
this.model = new Model();
this.view = new View();
}
// 组合数据和渲染方式
way01(){
this.view.show01(this.model.Data01());
}
way02(){
this.view.show01(this.model.Data02());
}
way03(){
this.view.show02(this.model.Data01());
}
way04(){
this.view.show02(this.model.Data02());
}
way05(){
this.view.show03(this.model.Data01());
}
way06(){
this.view.show03(this.model.Data02());
}
}
const control = new Control();
console.log(control)
control.way01();
control.way02();
control.way03();
control.way04();
control.way05();
control.way06();