目录
9.2.2jQuery中的事件委托(delegate)... 19
1.设计模式
1.1设计模式的概述
什么是设计模式: 一套可以被复用的,编目分明的经验总结。
作用:让我们写的代码可复用,提高我们的代码的可维护性
1995年Erich Gamma,Richard Helm, Ralph Johnson John Vlissides合作出版了Design Patterns - Elements of Reusable Object-Oriented Software一书, 在此书中共收录了23个设计模式。
这四位作者在软件开发领域里也以他们的匿名著称Gang of Four(四人帮,简称GoF),并且是他们在此书中的协作导致了软件设计模式的突破。有时这个匿名GoF也会用于指代前面提到的那本书,第一次将设计模式提升到理论高度,并将之规范化,本书提出了23种基本设计模式,自此,在可复用面向对象软件的发展过程中,新的大量的设计模式不断出现。
我们学习的是这23中设计模式中,在js中应用很广的一些,还有js中一些特殊性, 新的模式。
1.2 23种设计模式
创建型模式:解决创建类或者实例化对象时候,产生的问题。如:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。(5)
结构型模式:解决类或者对象在组合在一起时候的问题。如:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。(7)
行为型模式:解决类或者对象之间耦合,职责关系的问题。如:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、 状态模式、 策略模式、职责链模式(责任链模式)、访问者模式。(11)
学习设计模式的目的,就是用它来解决一些实战中的问题, 我们学习要先学习这些问题,再用设计模式给出一套解决问题方案。在前端实际开发中,又引入了许许多多新的模式在后面介绍。
1.3设计模式,框架,架构与工具库
设计模式:就是可以被复用,众人知晓,编目分明的经验的总结,侧重于解决某个(些)问题的
框架:在某一些软件领域中,将公用的部分抽象提出出来形成统一的整体。 往往是一个半成品,我们需要对它们进行再次加工完成项目的开发。设计了一套思想,引导我们去实现
比较设计模式与框架:设计模式是一个单一的思想, 就是为了解决某一类问题,框架是一套思想的统一,因此可以包含多个设计模式,他们在解决问题的思想上是统一的(一个框架中可以包含多个设计模式)
架构:设计的蓝图,没有具体的实现,框架是一种实现了的架构
工具库:只是一些方法的封装,每一个方法之间具有独立性,框架也是一套方法的封装,框架中的方法彼此之间是有联系的,彼此分工合作实现需求。
1.4原型与继承(回顾)
原型:是每一个函数天生拥有的属性,它的值是一个对象
特点:在类原型中的所有内容天生可以被每一个实例化对象所访问
继承:类与类之间的继承
继承的方式有:
1类式继承
2构造函数式继承
3组合式继承
4寄生式继承
5寄生组合式继承
2.工厂模式
2.1简单工厂模式
解决的问题:在创建对象的时候往往会衍生一些副作用(例如创建了全局变量),我们可以将这个创建过程封闭起来,创建完成,将结果返回,这样就可以屏蔽这些副作用对全局作用域的影响
实现:通过将创建过程封装在一个函数内,并将创建的结果返回
特点:
1避免副作用的产生
2我们看不到他的创建过程,只能看到创建的结果
3往往创建的是一个单一的产品
4我们可以简简单单对简单工厂模式的工厂方法改造实现更多的需求
//创建一个对象,要定义许多变量(污染全局作用域),为避免污染且简化创建的过程,
//此时,可以使用简单工厂
function factory(name, age, sex) {
var obj = { //定义对象
name: name,age: age,sex: sex,
intro: function () {console.log('我是' + this.name + ',性别是' + this.sex + ',今年' + this.age + '岁了');}}
return obj }// 返回创建的结果
var obj1 = factory('小白', 20, '男'); //简化对象的创建避免污染全局作用域
console.log(obj1);//{name: "小白", age: 20, sex: "男", intro: ƒ}
obj1.intro();//我是小白,性别是男,今年20岁了
2.2(寄生)增强工厂
作用:对类实例化的时候,对类拓展(实例化对象),而不会影响原类
特征
1在工厂内部创建类的实例化对象(寄生)
2对实例化对象进行拓展(增强)
3将实例化对象返回(工厂)
function factory(name, age, sex) {//增强型工厂
//创建
var p = new People(name, age, sex);//相当于寄生,就是新起一个灶台
//增强创建的结果
if (p.sex === '女') {
p.msg = 'hello',p.say = function (){console.log(this.msg);}}
//注意:fn的此处如果写p.msg指向的是p,而写this,this则指向People
return p; }//返回结果
//通过工厂创建实例
var p1 = factory('小白', 56, '男');
var p2 = factory('小red', 46, '女');
var p3 = factory('小澜', 22, '女');
// 两个有相同的行为
p2.intro();//我是小red,性别是女,今年46岁了
console.log(p1, p3);
// People {name: "小白", age: 56, sex: "男"}
// People {name: "小澜", age: 22, sex: "女", msg: "hello", say: ƒ}
p2.say();//hello
// p1.say();//没有被增强,报错
2.3安全工厂(安全类)
又叫安全类
步骤
判断当前对象是否是当前类的实例化对象
如果是
执行类的业务逻辑(对向前对象赋值)
如果不是
执行简单工厂业务逻辑(返回一个新的实例化对象)
function People(name, age, sex) {//通过类来创建对象
if (this instanceof People) {//判断this是否指向实例对象
this.name = name; this.age = age; this.sex = sex;//正常的存储数据
} else { //this指向window时,重新实例化
return new People(name, age, sex) }}//重新实例化
People.prototype.intro = function () { //注意:方法一般放在原型上
console.log('我是' + this.name + ',性别是' + this.sex + ',今年' + this.age + '岁了'); }
var p1 = new People('小白', 20, '男'); //创建实例
var p2 = People('小红', 21, '女');
console.log(p1);//People {name: "小白", age: 20, sex: "男"}
console.log(p2);//undefined(没有if进行判断时)
//判断后输出:People {name: "小红", age: 21, sex: "女"}
2.4工厂方法
作用:
用来处理多个类的创建的
本质:
对多个类的创建的封装
优势:
可以使用户不必关心多个类的创建,而将精力放在工厂方法的实现上
function Dog(name) {this.type = 'cat';this.name = name;} //创建狗
function Cat(name) {this.type = 'dog';this.name = name;} //创建猫
function Pig(name) {this.type = 'pig';this.name = name;} //创建猪
// //创建动物//太麻烦
// var dog = new Dog('旺财');
// var pig = new Pig('二师兄');
// var cat = new Cat('三师弟');
//👇工厂方法去创建//原始函数改动只用修改/维护工厂内部的数据即可
function animal(name, type) { //通过类型区分不同的类
if (type === 'dog') {return new Dog(name);
} else if (type === 'cat') {return new Cat(name);
} else if (type === 'pig') {return new Pig(name);}}
var cat = animal('老大', 'cat');var dog = animal('老er', 'dog');
var pig = animal('老san', 'pig');
console.log(cat, dog, pig);
// Cat { type: "狗", name: "老大" }
// Dog { type: "猫", name: "老er" }
// Pig { type: "猪", name: "老san"}
2.5原型模式
定义:
让类的原型指向父类的实例对象,这样,让该类创建的实例可以共享父类原型上的数据。
就是一个类式继承(原型式继承)。
是一种创建型的设计模式
是基于JavaScript的原型链继承原理实现的
//定义类:实例数据(自身数据)、原型数据、静态数据
function Book(title) {this.title = title; }//实例数据(自身数据)
//父类原型上的//原型数据
Book.prototype.getTitle = function () {console.log(this.title);}
//继承
function JsBook(title, price) {
this.title = title; this.price = price; } //存储其他数据
JsBook.prototype = new Book(); //让实例对象共享父类原型属性方法
//创建子类实例
var js = new JsBook('js设计模式', 59);
js.getTitle();//js设计模式
console.log(js);// JsBook {title: "js设计模式", price: 59}
2.6单例模式
定义:
是能被实例化一次的类或者对象(只能存在一个)
特点:
只允许实例化一次的对象类。
对于十分复杂的对象类,往往可以节省资源占用
通常也被用来管理命名空间
作用:管理命名空间,管理数据、方法的存储
应用:
一些代码库中命名空间就是通过单例模式实现的管理数据的存储,例如模拟静态变量
//放在闭包中//此种方法只实例化一次//但是不调用也会实例化一次
var Single = (function () {
function Demo() { //定义类
console.log('实例化了');
this.msg = 'hello'; } //此处省略一千亿万行数据
Demo.prototype.getMsg = function () {//方法
console.log(this.msg);
}//此处省略一千亿万行方法
//存储类的实例化对象
var instance = new Demo();
return function () { //暴露一个接口
return instance; } })()//返回s
//获取数据
var s1 = Single();console.log(s1);//Demo {msg: "实例化了"}
var s2 = Single();console.log(s2);//Demo {msg: "实例化了"}
2.7惰性单例
2.7.1常规用法
定义:
延迟单例类的实例化时间
作用:
如果单例类实例化开销很大,页面加载时候,有很多业务逻辑需要执行,并不需要这个单例类,此时我们可以推迟这个单例类的实例化时间
实现:
1通过闭包将惰性类封闭起来,避免外界的访问
2在闭包的返回函数中,我们判断闭包类有没有实现实例化,没有实现再去实例化,实现了直接返回实例化结果
3当调用这个闭包时候,才尝试去实例化,这样就拖延了实例化的时间点
<script>
//定义:类,只能被实例化一次
//有时候,不允许外界访问
//放在闭包中//此种方法只实例化一次//但是不调用也会实例化一次
var Single = (function () {
function Demo() {//定义类
console.log('实例化了'); this.msg = 'hello'; }
//此处省略一千亿万行数据
//方法
Demo.prototype.getMsg = function () { console.log(this.msg); }
//此处省略一千亿万行方法
var instance; //定义单例
return function () { //暴露一个接口
if (instance) { return instance //判断单例是是否被实例
} else {return instance = new Demo(); }}})()//重新实例化并返回
//获取数据
var s1 = Single();
console.log(111, s1);//111 Demo {msg: "hello"}
var s2 = Single();
console.log(222, s2);//222 Demo {msg: "hello"}
2.7.2静态变量
定义:
一旦被定义,只能被读取,无法被修改,其原理:
js中的任何变量,任何方法,只要能够访问,都可以被修改。
如果这些变量不能被访问,那么我们是无法修改他们的
定义在一个闭包(私有作用域)中的变量我们是访问不到,但是闭包方法是可以访问闭包作用域中的数据的,在这个方法中,我们只提供对数据的取值(器)方法,不提供赋值(器)方法,那么就可以实现静态变量
注意:
我们实现的静态变量有个局限性:
只能定义值类型的数据(工作中定义的静态变量也仅仅是值类型的)
// 静态变量
var Conf = (function () {//1.闭包作用域中存储数据,防止外部访问数据
var _data = {
num: 100, color: 'red',
//8.只能存储值类型,不能存储引用类型,该类型数据会被访问与修改
arr: [1, 2, 3],
obj: { msg: 'hello'}};
//2.返回一个方法
//4.闭包函数可以访问内部的数据
return function (key) {
return _data[key]; }})(); //6.取值器方法获取数据
//3.外部无法访问私有作用域的 _data
// console.log(111, _data);//not defined
//5.通过闭包访问
// Conf();//{num: 100, color: "red"}
//7.访问num,color,msg
console.log(Conf('num'));//100
console.log(Conf('color'));//red
console.log(Conf('msg'));//undefined
//10.发现可以读取引用类型的数据,查看是否可以修改数据
Conf('arr').push(3, 54, 3)//[1, 2, 3, 3, 54, 3],可以修改数据
Conf('obj').size = 200;//{msg: "hello", size: 200},可以修改数据
Conf('num') === 66//100,值类型的数据不会被影响,仍然只可读取
//9.读取引用类型的数据
console.log(Conf('arr'));//[1, 2, 3]
console.log(Conf('obj'));//{msg: "hello"}
2.8适配器模式(结构型的设计模式)
2.8.1适配器模式内涵
将一个类(对象)的接口(属性或者方法)适配到另一个类(对象)的接口(属性或者方法)。 来解决类(对象)之间不兼容的问题,实现类(对象)之间的解耦
特点
1.结构型的设计模式
2.是用来解决接口之间不兼容的问题的
3.是对数据进行的一个拆分再封装的一个过程
4.适配器模式往往需要一些额外的开销,但是这些开销要远比修改原有业务逻辑的成本低
//1.定义函数
function demo(arr) {
//第一个成员表示标题
var h1 = document.createElement('h1');
h1.innerHTML = arr[0];
//第二个成员表示内容
var p = document.createElement('p');
p.innerHTML = arr[1];
//第三个成员表示时间
var span = document.createElement('span');
span.innerHTML = arr[2];
//上树
app.appendChild(h1);
app.appendChild(p);
app.appendChild(span);
}
//6.由于数据变了重写方法,代价太大,所以需要适配器
//定义适配器,将对象适配成数组,然后统一使用数组的格式
function dataAdptor(obj) {
return [obj.title, obj.content, obj.time]
}
//7.使用适配器适配的数据
demo(dataAdptor(obj));//正常执行
2.8.2适配器应用
1.请求接口数据的适配
2.参数的适配
3.代码库的适配
创建一个“我是好人”代码库,实现了交互
领导要求换jquery,
将jquery的一些接口方法适配到原有的库的接口方法
<button>切换</button>
<h1>内容</h1>
<!-- 1.引用 -->
<!-- <script src="./自用库01面向对象优化.js"></script> -->
<!-- 3.引用jQ库,不想重新写一遍方法实现的交互,就需要适配她-->
<script src="./jquery1.12.js"></script>
//4.如果开发时间短,没有充足的时间去重写页面,就需要定义适配器,兼容这两个库
<script> var QAQ = {
//5.适配绑定事件方法
bindEvent: function (dom, type, fn) { $(dom).on('click', fn) },
//6.适配修改样式方法
css: function (dom, key, value) { $(dom).css(key, value) },
getStyle: function (dom, key) { //7.适配获取样式方法
return $(dom).css(key)}}; </script>//注意:前面不能加return会打断函数
// 2.老方法实现交互
<script> var h1 = document.getElementsByTagName('h1')[0];
var btn = document.getElementsByTagName('button')[0];
//设置交互:点击button切换h1的显隐
QAQ.bindEvent(btn, 'click', function () {
//切换显隐
QAQ.css(h1, 'display', QAQ.getStyle(h1, 'display') === 'block' ? 'none' : 'block')
}) </script>
<!-- 注意:如果时间足够,应该重写页面,不然两个库的相同方法在一起显得很呆 -->
3. 观察者模式
3.1观察者模式内涵
1.定义:
观察者模式,又叫发布订阅者模式,又叫消息系统,又叫消息机制,又叫自定义事件。
2.用途:
解决主体与观察者之间的耦合问题
3.模式:
观察者模式是一个行为型设计模式
4.特点:
1解决的是耦合问题(类与类之间,对象之间,类与对象之间,模块之间)
2对于任何一个观察者来说,其他观察者的改变不会影响自身
3对于任何一个对象来说,既可以是观察者,也可以是被观察者
如: jQuery中的观察者模式。
$.CallBacks()方法执行的结果得到一个观察者对象,
观察者对象有一个方法叫add,用来订阅消息的。
观察对象有一个方法叫fire,用来发布消息。
//1.创建一个消息相同
var cb = $.Callbacks();
//2.订阅消息
cb.add('qaaq', function () {
//👇4.Arguments(4) ["asd", 11, 22, true, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
})
//3.fa发布消息
cb.fire('asd', 11, 22, true)
//5.发布几次就执行几次
cb.fire('asd', 11, 22, true)
cb.fire('asd', 11, 22, true)
3.2实现观察者模式
观者者对象必须具备两个方法
on 用来注册消息
第一个参数表示消息的名称。第二个参数表示回调函数
trigger 用来触发消息
第一个参数表示消息的名称。从第二个参数开始表示传递的数据
off 用来移除消息的方法,参数同register
once 单次订阅方法,参数同register
通过闭包将接口返回,那么on和trigger对用户来说就是可访问的,闭包里面存储消息队列,对用户来说就是不可见的,因此是安全。
<h2>消息:<span>0</span></h2>
<ul><li>评论的消息</li></ul>
<textarea></textarea>
<button>提交内容</button>
<!-- 这三个模块理论上由三个人开发 -->
<script src="./自用库.js"></script> <script>
(function () {//4.1:消息模块 A
var dom = document.getElementsByTagName('h2')[0].getElementsByTagName('span')[0];
var num = 0;//4.3
_.Observer.on('addMessage', function (msg) {//4.2订阅消息
num++;dom.innerHTML = num; }) //4.4添加信息,改变消息数量
_.Observer.on('deletMessage', function () {//3.9监听 模块B 信息的消息
num--; dom.innerHTML = num; })})()</script> <script>
(function () {//3.1:内容模块 B
var dom = document.getElementsByTagName('ul')[0];
//订阅消息
_.Observer.on('addMessage', function (msg) {
var li = document.createElement('li'); //3.2创建li
var text = document.createTextNode(msg);
var close = document.createElement('span'); //3.3创建删除按钮
close.innerHTML = '×'; //3.4设置内容
//3.5设置内容
li.appendChild(text);
li.appendChild(close);
dom.appendChild(li); //3.6上树
close.onclick = function () { //3.7删除消息
dom.removeChild(li) //删除li
//3.8发布消息,通知模块A 的num -1
_.Observer.trigger('deletMessage') }})})()</script><script>
(function () {//2.1:评论模块 C
//2.2获取元素
var btn = document.getElementsByTagName('button')[0];
var textarea = document.getElementsByTagName('textarea')[0];
btn.onclick = function () {//2.3当用户点击按钮,要提交评论
var text = textarea.value; //2.4获取内容
//2.5输入内容可以提交
if (text) {//2.6将内容提交到B模块(发布消息)
.Observer.trigger('addMessage', text)
textarea.value = ''; }} })() </script> //2.7清空内容
4.组合模式(部分——整体模式)(结构型设计模式)
4.1组合模式理论
含义:
又叫部分——整体模式,将对象组装成一个树形结构来表达这个整体,不论是部分还是整体,在表现上具有一致性。 是结构型设计模式。
特点:
1.是一个拆分合并过程。为我们提供清晰的组成结构
2.通过对基对象的属性方法的继承,使成员对象间的基本表现,行为统一
3.成员对象的结构简单而又单一,这给我们带来了更多的组合方式。
组合模式实现步骤:
对整体拆分 -> 得到不同层级的个体。
对个体组装 -> 组合得到不同的整体
4.所有个体都会继承同一个基类
5.通常基类是只能被继承,不会去实例化的的(包含的是所有个体共有的属性方法)
4.2组合模式应用一一新闻模块
需求
1.整个新闻模块是一个根节点
2.每一行是一个枝干节点
3.每一行有多条新闻,每一条新闻就是一个叶子节点(因此不能包含子节点)
总结
1.是一个结构型设计模式
2.本质就是一个拆分合并的过程
3.整体与个体之间具有行为的一致性
4.对个体的不同的组装,可以是整体差异化
5.组合模式使整体结构很清晰
<script src="./自用库01面向对象优化.js"></script>
<script>
function Base() { //基类:让所有类继承
this.element = null; //当前元素
this.children = []; }//存储子类
Base.prototype.init = function () {//定义这些行为方法
throw new Error('基类不能初始化') } //基类不能初始化
Base.prototype.getelement = function () { // 获取当前实例对应的元素
return this.element; }
Base.prototype.add = function (child) { //添加子元素
this.children.push(child); //存储子对象
this.element.appendChild(child.getelement());//处理dom
return this; } //链式调用
//容器类
function Container(id, parent) {
Base.call(this); //构造函数式继承
this.id = id; //存储数据
this.parent = parent;
this.init(); }//初始化
//可以使用类式继承,也可以使用寄生式继承
// Container.prototype = new Base//类式继承
_.inherit(Container, Base);//寄生式继承
Container.prototype.init = function () {//重写方法//初始化方法
this.element = document.createElement('ul'); //创建元素
this.element.id = this.id; //添加id
this.element.className = 'container'; }//添加类
Container.prototype.show = function () {//显示容器
this.parent.appendChild(this.element);}//让容器元素上树
//每一行的类
function Item(className) {
Base.call(this) //构造函数式继承
this.className = className || 'item'//存储数据
this.init(); }//初始化
_.inherit(Item, Base); //继承
Item.prototype.init = function () {//重写方法
this.element = document.createElement('li'); //创建元素
this.element.className = this.className; } //设置属性
//没有分类的新闻
function TitleNews(text, href) {
Base.call(this) //构造函数式继承
this.text = text; //存储数据
this.href = href;
this.init(); }//初始化
_.inherit(TitleNews, Base) //继承
TitleNews.prototype.init = function () {//重写方法//初始化方法
this.element = document.createElement('a'); //定义元素
this.element.href = this.href; //设置属性
this.element.innerHTML = this.text; } //设置内容
//分类新闻
function TypeNews(text, href, type) {
Base.call(this) //构造函数式继承
this.text = text; //存储数据
this.href = href;
this.type = type;
this.init();}//初始化
_.inherit(TypeNews, Base) //继承
TypeNews.prototype.init = function () {//重写方法//初始化方法
this.element = document.createElement('a'); //创建元素
var span = document.createElement('span');
var text = document.createTextNode(this.text);
span.innerHTML = this.type + '|'; //设置内容
this.element.href = this.href; //设置属性
this.element.appendChild(span) //组装
this.element.appendChild(text) }
//使用类
new Container('sport', document.body)
.add(new Item()
.add(new TitleNews('中华人民共和国', 'www.baidu.com'))
.add(new TitleNews('今天成立了', 'www.baidu.com')))
.add( new Item()
.add(new TitleNews('今天要当一个好人', 'www.baidu.com'))).add(new Item()
.add(new TypeNews('明天当个坏人', '#demo', '明天')))
.add(new Item()
.add(new TypeNews('后天当个大好人', '#demo', '后天'))) .show(); //上树
new Container('car', document.body) //构造一个新模块
.add(new Item().add(new TitleNews('昨天不是个好人啊', '#demo')))
.add(new Item().add(new TypeNews('前天是个大好人啊', '#demo', '前天'))).show();
5.策略模式
定义:
将一组算法封装起来,使其彼此之间可以相互替换,封装的算法具有独立性,不会随着客户端的变化而变化。
类型:
行为型设计模式
特点:
1.创建的一系列算法,每组算法的业务逻辑可能是相同的,但是处理的过程以及结果可能不同。
2.算法是独立的,可以相互替换,解决了算法与使用者之间的耦合问题
3.算法的独立性,方便使其进行单元测试
应用:
1.jQuery的动画算法,就是策略模式
2.处理商品促销价格
3.表单校验等
6.命令模式
6.1内涵
定义:
将请求与实现解耦并封装成独立的对象,从而使不同的请求对客户端实现的参数化。
类型:
行为型设计模式
特点
1.将执行的命令封装,解决命令的发起者与命令的执行者之间的耦合
2.使用者不必了解每条命令的接口是如何实现的,命令是如何执行的。
3.所有命令都被存储在命令对象上。
4.命令的使用具有一致性,多数命令在一定程度上简化了操作方法的实现,
5.对命令的封装,使得每次执行时都要调用一次命令对象,增加了系统的复杂度
6.2应用(canvas)
绘制圆的过程中遇到哪些问题?
1源生的API有时候不好用
绘制一个圆写了5行代码
绘制一个圆,只需要圆心坐标,半径,颜色就够了,其他的都多余
2如果将ctx变量放在全局环境中,有风险,可能会被人更改
3如果将ctx变量保存在闭包中,外界就无法访问了
strokeCircle: function (x, y, r, color) {//描边圆
ctx.beginPath();ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.closePath(); ctx.strokeStyle = color; ctx.stroke();},
return { //执行方法 //暴露接口
exec: function (type) {//type:上面封装的名称,
//获取第二个参数开始,传递的参数
var args = Array.prototype.slice.call(arguments, 1);//第一个参数为type,不能再取了
_C[type] && _C[type].apply(null, args); //执行指令
return this; }}})()//链式调用
Command.exec('fillCircle', 100, 100, 50, 'green') //绘制圆
7.迭代器模式
定义:
在不暴露对象内部结构的同时,可以顺序的访问聚合对象内部的元素。
模式:
行为型设计模式
特点;
1.可以顺序的访问聚合对象中的每个元素
2.简化循环语句,结构清晰
3.迭代器并没有移出循环语句,而是移动到迭代器中。
4.迭代器在处理一个对象时,我们只需要提供处理方法,并不关心对象内部结构。
5.解决了使用者与对象内部结构解析之间的耦合
6.为我们提供统一的操作对象接口
function Iterator(selector) { //实现一个类式jQ中的迭代器
this.selector = selector; //存储选择器
this.elements = document.querySelectorAll(selector); //获取元素
this.index = 0; }//维护一个索引值
Iterator.prototype = {
//放在对象中定义,对象的constructor属性,不再指向Iterator
//constructor属性始终指向创建自身的构造函数
constructor: Iterator, //重写构造函数
getCurrent: function (){return this.elements[this.index]},//获取元素
first: function () { //获取第一个
this.index = 0;//更改index指向
return this.getCurrent() },//返回元素
prev: function () { //获取前一个
this.index--;
if (this.index < 0) {this.index = 0; throw new Error('已经最前面了') }
return this.getCurrent(); },
next: function () {//获取后一个
this.index++;
if (this.index >= this.elements.length) { //判断边界
this.index = this.elements.length - 1;
throw new Error('已经最后面了') }
return this.getCurrent();},
//获取第几个
eq: function (index) {this.index = index; return this.getCurrent();}}
var lis = new Iterator('li'); //获取li
console.log(lis.first());console.log(lis.eq(2));
8.遍历器
含义:
遍历聚合数据。把for循环移动到方法内部。
<ul>
<li>1</li><li>2</li><li>3</li><li>4</li>
</ul>
<!-- <script src="./jquery1.12.js"></script> -->
<script>//遍历器:遍历聚合数据
var arr = [1, 2, 3, 4, 5];
var obj = {color: 'red', msg: 'hello', num: 12 };
//一:实现遍历器(模仿ES5)(for each)
Array.prototype.each = function (callback) {
//将 循环 移到方法内部
for (var i = 0; i < this.length; i++) {
callback(this[i], i, this)
} }
arr.each(function (item, index, arr) {//打印
console.log(item, index, arr); })
function each(data, callback) {//二:实现jQ中的遍历方法(each)
/* 判断数组的四种方式(typeof只能正确判断值类型)
* 1.instanceof
* 2.constructor
* 3.Object.prototye.tostring.call(dom)*/
if (data instanceof Array) {//遍历数组用for循环
for (var i = 0; i < data.length; i++) {
callback(i, data[i]) }//执行函数
} else {
for (var key in data) { //遍历对象用for in 循环
callback(key, data[key]) }}}//执行函数
//二:遍历数据
each(arr, function (index, item) { console.log(index, item); })
//三:遍历对象
each(obj, function (key, value) {console.log(key, value);})
9.委托模式
9.1数据分发
含义:
1.多个对象接收并处理同一请求,他们将请求委托给另一个对象统一处理请求。
2.解决请求与委托者之间的耦合
3.被委托者接收到请求分发给委托者去处理
应用:
请求委托:
浏览器同时并发处理请求的个数是有限的,因此对于一个页面来说,如果请求过多的话,会导致后面的请求被延迟(堵塞),我们为了加快这些请求发出,我们将这些请求合并成一个,这样当请求结束后,我们再分发这些请求的数据。
9.2事件委托
9.2.1事件委托
背景:
对于每一个元素我们都要绑定一个事件,这样对于资源开销很大(如果有1w个元素,要绑定1w个事件)
对于每一个类元素都要for循环遍历一次,开发成本很高。所以我们可以通过事件委托来解决上面的问题
含义:
将所有元素的事件绑定委托给同一个父元素,根据事件冒泡捕获机制,可以在父元素绑定事件中,获取触发事件的这个元素,根据这个元素具有的某类特征(例如元素名称,元素id,元素类,元素属性等等)做不同的处理,实现事件从父元素到被委托的元素传递。
特点:
1.减少事件数量
2.预言未来元素:新增的元素也可以绑定事件
3.避免内存外泄:通常创建一个对象需要占用一些内存,这类占用是有意义的;有时候一些垃圾(已经被删除数据)还占用着内存,这部分内存对我们来说是没用的,就是外泄内存
//在低版本的ie浏览器下会参数内存外泄问题
//高版本浏览器垃圾回收机制是计数的
//低版本的垃圾回收机制,是引用(指向)的
//多个元素相互引用(指向)时,不能被垃圾回收机制回收
//点击按钮,box显示新内容
var btn = document.getElementById('btn');
var box = document.getElementById('box');
btn.onclick = function () { //绑定事件
//box外泄,box设置了新内容
//按钮元素被删除,但由于按钮仍被绑定了点击事件,因此无法被销毁,泄露在内存中
box.innerHTML = '新内容'} //更新box内容
//使用事件委托,避免外泄
var box = document.getElementById('box');
//给盒子绑定事件
box.onclick = function (e) {
if (e.target.tagName.toUpperCase === 'BUTTON') {//判断点击的位置
box.innerHTML = '新内容'; } }
//不会内存外泄,因为按钮元素没有绑定点击事件,绑定在盒子上了
9.2.2jQuery中的事件委托(delegate)
delegate:
第一个参数是被委托的元素 (子元素)
第二个参数是事件类型 (这里还可以传递自定数据)
第三个参数是事件回调函数
本质上是调用的on方法,只不过他的委托语义化更强
// $('#box').delegate('#btn', 'click', function () {
// $('#box').html('新内容') })
//还可以使用 on方法,但是语义化不强
$('#box').on('click', '#btn', function () {
$('#box').html('新内容') })
10.节流器
10.1定义与特点
定义:
对重复的业务逻辑进行节流控制,执行最后一次操作并取消其他操作, 以提高性能。
特点
1.延迟:通过计时器延迟程序的执行
2.异步:通过计时器,使程序异步执行,避免开销大的程序造成的堵塞
条件
3.程序可控:即是否可以继续执行
4.异步执行:即程序是否可以异步执行
10.2节流与防抖
节流器:
触发操作,让程序延迟执行
清除操作,清除还没有执行的操作
两种节流模式
按操作节流:
在规定时间内,重复执行的时候,取消前一次执行,执行当前的操作
这种节流操作有的人也叫防抖。例如:对返回顶部的优化。
按时间节流:在规定时间内,只允许执行一次。
例如:对icon浮层的优化。
var btn = document.getElementById('back');
//显隐返回顶部按钮
function goTopBtn() { console.log(11111,this,arguments);
//判断距离顶部300px,显示顶部按钮,否则隐藏
if (window.scrollY > 300) {
btn.style.display = 'block'
} else { btn.style.display = 'none'}}
window.onscroll = function (e) {
//监听页面滚动
// goTopBtn();//返回顶部方法
throttle(goTopBtn, {
time: 1000,
context: { color: 'red' },
args: [1, 2, 3]
})
/**
* 优化节流器(防抖器)
* @fn 执行的函数
* @data 传递的数据,为true时 清空定时器,(结束)
**/
function throttle(fn, data) {
clearTimeout(fn.__timebar) //清除
data = data || {};//定义data默认值//如果data不是true再启动
var params = { time: data.time || 200, //定义时间默认值
context: data.context || null,
args: data.args};
if (data !== true) {fn.__timebar = setTimeout(function () {
//执行方法
fn.apply(params.context, params.args) }, params.time) }}//启动
10.3实现jQuery
jQuery本身是一个工厂 方法
jQuery中,内置了一个寄生类,寄生在原型.上。
jQuery中,始终返回this即可实现链式调用
jQuery中,通过extend可以为jQuery添加静态方法以及原型方法
<script>
// //实现jQuery
// //jQuery本身是一个工厂方法
// function jQuery(selector) {
// //返回实例
// return new init(selector);
// }
// //工厂的原型
// jQuery.prototype = {
// //更正构造函数
// constructor: jQuery,
// //把真正的类放在工厂方法的原型对象上
// //每次访问init都要写:jQuery.prototype.init太麻烦了
// //所以,取别名jQuery.fn代表原型对象
// init: init
// }
// //真正的jQuery类
// function init() {
// }
//jQuery本身是一个工厂方法
function jQuery(selector) {
//返回实例
// return new init(selector);
//重新访问init
return new jQuery.fn.init(selector) }
//工厂的原型
//这样访问jQuery.fn相对于访问jQuery.prototype.init
jQuery.fn = jQuery.prototype = {
//更正构造函数
constructor: jQuery,
//把真正的类放在工厂方法的原型对象上
//每次访问init都要写:jQuery.prototype.init太麻烦了
//所以,取别名jQuery.fn代表原型对象
// init: init,
version: '1.0',
demo() { console.log('demoo'); },
//拓展一些方法,更像数组
push: Array.prototype.push,
splice: Array.prototype.splice, }
//真正的jQuery类
jQuery.fn.init = function (selector) {
//根据选择器,将元素获取
var dom = document.querySelectorAll(selector);
this.selector = selector; //存储选择器
this.length = 0; //定义长度
//将获取的元素,存储在自身
for (var i = 0; i < dom.length; i++) {
//每存储一个成员,更改长度一次
this[i] = dom[i];
this.length++;}}
// 让init类的实例,具有jQuery原型上的方法
jQuery.fn.init.prototype = jQuery.prototype;
jQuery.fn.init.prototype = jQuery.fn;
//jQuery中又定义了拓展方法
jQuery.extend = jQuery.fn.extend = function (obj) {
//解析对象
for (var key in obj) {
this[key] = obj[key] } }
// //拓展的静态属性
// jQuery.extend({
// color: 'red', num: 989 })
//拓展原型属性
jQuery.fn.extend({
css: function (key, value) {
if (typeof key === 'string') { //如果key是字符串,直接赋值
//给每一个元素设置样式
for (var i = 0; i < this.length; i++) {
this[i].style[key] = value; }
} else { //遍历对象设置样式
for (var k in key) {
this.css(k, key[k]) } }
return this; }, //链式调用
attr: function (key, value) { //设置属性方法
//给每一个成员设置属性
for (var i = 0; i < this.length; i++) {
this[i][key] = value; } } })
//对jQuery起别名
var $ = jQuery;
$('.app').css('color', 'red').css({
fontSize: '50px',
backgroundColor: 'pink'
}).attr('title', 'hellooo')
console.log($('.app'));
</script>