1.不属于常见23种设计模式javascript
1. 定义css
保证一个类仅有一个实例,并提供一个访问它的全局访问点html
2. 核心前端
确保只有一个实例,并提供全局访问java
3. 实现python
假设要设置一个管理员,屡次调用也仅设置一次,咱们可使用闭包缓存一个内部变量来实现这个单例jquery
function SetManager(name) { this.manager = name; } SetManager.prototype.getName = function() { console.log(this.manager); }; var SingletonSetManager = (function() { var manager = null; return function(name) { if (!manager) { manager = new SetManager(name); } return manager; } })(); SingletonSetManager('a').getName(); // a SingletonSetManager('b').getName(); // a SingletonSetManager('c').getName(); // a
这是比较简单的作法,可是假如咱们还要设置一个HR呢?就得复制一遍代码了web
因此,能够改写单例内部,实现地更通用一些面试
// 提取出通用的单例 function getSingleton(fn) { var instance = null; return function() { if (!instance) { instance = fn.apply(this, arguments); } return instance; } }
再进行调用,结果仍是同样算法
// 获取单例 var managerSingleton = getSingleton(function(name) { var manager = new SetManager(name); return manager; }); managerSingleton('a').getName(); // a managerSingleton('b').getName(); // a managerSingleton('c').getName(); // a
这时,咱们添加HR时,就不须要更改获取单例内部的实现了,仅须要实现添加HR所须要作的,再调用便可
function SetHr(name) { this.hr = name; } SetHr.prototype.getName = function() { console.log(this.hr); }; var hrSingleton = getSingleton(function(name) { var hr = new SetHr(name); return hr; }); hrSingleton('aa').getName(); // aa hrSingleton('bb').getName(); // aa hrSingleton('cc').getName(); // aa
或者,仅想要建立一个div层,不须要将对象实例化,直接调用函数
结果为页面中仅有第一个建立的div
function createPopup(html) { var div = document.createElement('div'); div.innerHTML = html; document.body.append(div); return div; } var popupSingleton = getSingleton(function() { var div = createPopup.apply(this, arguments); return div; }); console.log( popupSingleton('aaa').innerHTML, popupSingleton('bbb').innerHTML, popupSingleton('bbb').innerHTML ); // aaa aaa aaa
1. 定义
定义一系列的算法,把它们一个个封装起来,而且使它们能够相互替换。
2. 核心
将算法的使用和算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成:
第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。
第二个部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。要作到这点,说明Context 中要维持对某个策略对象的引用
3. 实现
策略模式能够用于组合一系列算法,也可用于组合一系列业务规则
假设须要经过成绩等级来计算学生的最终得分,每一个成绩等级有对应的加权值。咱们能够利用对象字面量的形式直接定义这个组策略
// 加权映射关系 var levelMap = { S: 10, A: 8, B: 6, C: 4 }; // 组策略 var scoreLevel = { basicScore: 80, S: function() { return this.basicScore + levelMap['S']; }, A: function() { return this.basicScore + levelMap['A']; }, B: function() { return this.basicScore + levelMap['B']; }, C: function() { return this.basicScore + levelMap['C']; } } // 调用 function getScore(level) { return scoreLevel[level] ? scoreLevel[level]() : 0; } console.log( getScore('S'), getScore('A'), getScore('B'), getScore('C'), getScore('D') ); // 90 88 86 84 0
在组合业务规则方面,比较经典的是表单的验证方法。这里列出比较关键的部分
// 错误提示 var errorMsgs = { default: '输入数据格式不正确', minLength: '输入数据长度不足', isNumber: '请输入数字', required: '内容不为空' }; // 规则集 var rules = { minLength: function(value, length, errorMsg) { if (value.length < length) { return errorMsg || errorMsgs['minLength'] } }, isNumber: function(value, errorMsg) { if (!/\d+/.test(value)) { return errorMsg || errorMsgs['isNumber']; } }, required: function(value, errorMsg) { if (value === '') { return errorMsg || errorMsgs['required']; } } }; // 校验器 function Validator() { this.items = []; }; Validator.prototype = { constructor: Validator, // 添加校验规则 add: function(value, rule, errorMsg) { var arg = [value]; if (rule.indexOf('minLength') !== -1) { var temp = rule.split(':'); arg.push(temp[1]); rule = temp[0]; } arg.push(errorMsg); this.items.push(function() { // 进行校验 return rules[rule].apply(this, arg); }); }, // 开始校验 start: function() { for (var i = 0; i < this.items.length; ++i) { var ret = this.items[i](); if (ret) { console.log(ret); // return ret; } } } }; // 测试数据 function testTel(val) { return val; } var validate = new Validator(); validate.add(testTel('ccc'), 'isNumber', '只能为数字'); // 只能为数字 validate.add(testTel(''), 'required'); // 内容不为空 validate.add(testTel('123'), 'minLength:5', '最少5位'); // 最少5位 validate.add(testTel('12345'), 'minLength:5', '最少5位'); var ret = validate.start(); console.log(ret);
4. 优缺点
优势
能够有效地避免多重条件语句,将一系列方法封装起来也更直观,利于维护
缺点
每每策略集会比较多,咱们须要事先就了解定义好全部的状况
1. 定义
为一个对象提供一个代用品或占位符,以便控制对它的访问
2. 核心
当客户不方便直接访问一个 对象或者不知足须要的时候,提供一个替身对象 来控制对这个对象的访问,客户实际上访问的是 替身对象。
替身对象对请求作出一些处理以后, 再把请求转交给本体对象
代理和本体的接口具备一致性,本体定义了关键功能,而代理是提供或拒绝对它的访问,或者在访问本体以前作一 些额外的事情
3. 实现
代理模式主要有三种:保护代理、虚拟代理、缓存代理
保护代理主要实现了访问主体的限制行为,以过滤字符做为简单的例子
// 主体,发送消息 function sendMsg(msg) { console.log(msg); } // 代理,对消息进行过滤 function proxySendMsg(msg) { // 无消息则直接返回 if (typeof msg === 'undefined') { console.log('deny'); return; } // 有消息则进行过滤 msg = ('' + msg).replace(/泥\s*煤/g, ''); sendMsg(msg); } sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀 proxySendMsg('泥煤呀泥 煤'); // 呀 proxySendMsg(); // deny
它的意图很明显,在访问主体以前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这数据保护代理的形式
有消息的时候对敏感字符进行了处理,这属于虚拟代理的模式
虚拟代理在控制对主体的访问时,加入了一些额外的操做
在滚动事件触发的时候,也许不须要频繁触发,咱们能够引入函数节流,这是一种虚拟代理的实现
// 函数防抖,频繁操做中不处理,直到操做完成以后(再过 delay 的时间)才一次性处理 function debounce(fn, delay) { delay = delay || 200; var timer = null; return function() { var arg = arguments; // 每次操做时,清除上次的定时器 clearTimeout(timer); timer = null; // 定义新的定时器,一段时间后进行操做 timer = setTimeout(function() { fn.apply(this, arg); }, delay); } }; var count = 0; // 主体 function scrollHandle(e) { console.log(e.type, ++count); // scroll } // 代理 var proxyScrollHandle = (function() { return debounce(scrollHandle, 500); })(); window.onscroll = proxyScrollHandle;
缓存代理能够为一些开销大的运算结果提供暂时的缓存,提高效率
来个栗子,缓存加法操做
// 主体 function add() { var arg = [].slice.call(arguments); return arg.reduce(function(a, b) { return a + b; }); } // 代理 var proxyAdd = (function() { var cache = []; return function() { var arg = [].slice.call(arguments).join(','); // 若是有,则直接从缓存返回 if (cache[arg]) { return cache[arg]; } else { var ret = add.apply(this, arguments); return ret; } }; })(); console.log( add(1, 2, 3, 4), add(1, 2, 3, 4), proxyAdd(10, 20, 30, 40), proxyAdd(10, 20, 30, 40) ); // 10 10 100 100
1. 定义
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不须要暴露该对象的内部表示。
2. 核心
在使用迭代器模式以后,即便不关心对象的内部构造,也能够按顺序访问其中的每一个元素
3. 实现
JS中数组的map forEach 已经内置了迭代器
[1, 2, 3].forEach(function(item, index, arr) { console.log(item, index, arr); });
不过对于对象的遍历,每每不能与数组同样使用同一的遍历代码
咱们能够封装一下
function each(obj, cb) { var value; if (Array.isArray(obj)) { for (var i = 0; i < obj.length; ++i) { value = cb.call(obj[i], i, obj[i]); if (value === false) { break; } } } else { for (var i in obj) { value = cb.call(obj[i], i, obj[i]); if (value === false) { break; } } } } each([1, 2, 3], function(index, value) { console.log(index, value); }); each({a: 1, b: 2}, function(index, value) { console.log(index, value); }); // 0 1 // 1 2 // 2 3 // a 1 // b 2
再来看一个例子,强行地使用迭代器,来了解一下迭代器也能够替换频繁的条件语句
虽然例子不太好,但在其余负责的分支判断状况下,也是值得考虑的
function getManager() { var year = new Date().getFullYear(); if (year <= 2000) { console.log('A'); } else if (year >= 2100) { console.log('C'); } else { console.log('B'); } } getManager(); // B
将每一个条件语句拆分出逻辑函数,放入迭代器中迭代
function year2000() { var year = new Date().getFullYear(); if (year <= 2000) { console.log('A'); } return false; } function year2100() { var year = new Date().getFullYear(); if (year >= 2100) { console.log('C'); } return false; } function year() { var year = new Date().getFullYear(); if (year > 2000 && year < 2100) { console.log('B'); } return false; } function iteratorYear() { for (var i = 0; i < arguments.length; ++i) { var ret = arguments[i](); if (ret !== false) { return ret; } } } var manager = iteratorYear(year2000, year2100, year); // B
1. 定义
也称做观察者模式,定义了对象间的一种一对多的依赖关系,当一个对象的状态发 生改变时,全部依赖于它的对象都将获得通知
2. 核心
取代对象之间硬编码的通知机制,一个对象不用再显式地调用另一个对象的某个接口。
与传统的发布-订阅模式实现方式(将订阅者自身当成引用传入发布者)不一样,在JS中一般使用注册回调函数的形式来订阅
3. 实现
JS中的事件就是经典的发布-订阅模式的实现
// 订阅 document.body.addEventListener('click', function() { console.log('click1'); }, false); document.body.addEventListener('click', function() { console.log('click2'); }, false); // 发布 document.body.click(); // click1 click2
本身实现一下
小A在公司C完成了笔试及面试,小B也在公司C完成了笔试。他们焦急地等待结果,每隔半天就电话询问公司C,致使公司C很不耐烦。
一种解决办法是 AB直接把联系方式留给C,有结果的话C天然会通知AB
这里的“询问”属于显示调用,“留给”属于订阅,“通知”属于发布
// 观察者 var observer = { // 订阅集合 subscribes: [], // 订阅 subscribe: function(type, fn) { if (!this.subscribes[type]) { this.subscribes[type] = []; } // 收集订阅者的处理 typeof fn === 'function' && this.subscribes[type].push(fn); }, // 发布 可能会携带一些信息发布出去 publish: function() { var type = [].shift.call(arguments), fns = this.subscribes[type]; // 不存在的订阅类型,以及订阅时未传入处理回调的 if (!fns || !fns.length) { return; } // 挨个处理调用 for (var i = 0; i < fns.length; ++i) { fns[i].apply(this, arguments); } }, // 删除订阅 remove: function(type, fn) { // 删除所有 if (typeof type === 'undefined') { this.subscribes = []; return; } var fns = this.subscribes[type]; // 不存在的订阅类型,以及订阅时未传入处理回调的 if (!fns || !fns.length) { return; } if (typeof fn === 'undefined') { fns.length = 0; return; } // 挨个处理删除 for (var i = 0; i < fns.length; ++i) { if (fns[i] === fn) { fns.splice(i, 1); } } } }; // 订阅岗位列表 function jobListForA(jobs) { console.log('A', jobs); } function jobListForB(jobs) { console.log('B', jobs); } // A订阅了笔试成绩 observer.subscribe('job', jobListForA); // B订阅了笔试成绩 observer.subscribe('job', jobListForB); // A订阅了笔试成绩 observer.subscribe('examinationA', function(score) { console.log(score); }); // B订阅了笔试成绩 observer.subscribe('examinationB', function(score) { console.log(score); }); // A订阅了面试结果 observer.subscribe('interviewA', function(result) { console.log(result); }); observer.publish('examinationA', 100); // 100 observer.publish('examinationB', 80); // 80 observer.publish('interviewA', '备用'); // 备用 observer.publish('job', ['前端', '后端', '测试']); // 输出A和B的岗位 // B取消订阅了笔试成绩 observer.remove('examinationB'); // A都取消订阅了岗位 observer.remove('job', jobListForA); observer.publish('examinationB', 80); // 没有可匹配的订阅,无输出 observer.publish('job', ['前端', '后端', '测试']); // 输出B的岗位
4. 优缺点
优势
一为时间上的解耦,二为对象之间的解耦。能够用在异步编程中与MV*框架中
缺点
建立订阅者自己要消耗必定的时间和内存,订阅的处理函数不必定会被执行,驻留内存有性能开销
弱化了对象之间的联系,复杂的状况下可能会致使程序难以跟踪维护和理解
1. 定义
用一种松耦合的方式来设计程序,使得请求发送者和请求接收者可以消除彼此之间的耦合关系
命令(command)指的是一个执行某些特定事情的指令
2. 核心
命令中带有execute执行、undo撤销、redo重作等相关命令方法,建议显示地指示这些方法名
3. 实现
简单的命令模式实现能够直接使用对象字面量的形式定义一个命令
var incrementCommand = { execute: function() { // something } };
不过接下来的例子是一个自增命令,提供执行、撤销、重作功能
采用对象建立处理的方式,定义这个自增
// 自增 function IncrementCommand() { // 当前值 this.val = 0; // 命令栈 this.stack = []; // 栈指针位置 this.stackPosition = -1; }; IncrementCommand.prototype = { constructor: IncrementCommand, // 执行 execute: function() { this._clearRedo(); // 定义执行的处理 var command = function() { this.val += 2; }.bind(this); // 执行并缓存起来 command(); this.stack.push(command); this.stackPosition++; this.getValue(); }, canUndo: function() { return this.stackPosition >= 0; }, canRedo: function() { return this.stackPosition < this.stack.length - 1; }, // 撤销 undo: function() { if (!this.canUndo()) { return; } this.stackPosition--; // 命令的撤销,与执行的处理相反 var command = function() { this.val -= 2; }.bind(this); // 撤销后不须要缓存 command(); this.getValue(); }, // 重作 redo: function() { if (!this.canRedo()) { return; } // 执行栈顶的命令 this.stack[++this.stackPosition](); this.getValue(); }, // 在执行时,已经撤销的部分不能再重作 _clearRedo: function() { this.stack = this.stack.slice(0, this.stackPosition + 1); }, // 获取当前值 getValue: function() { console.log(this.val); } };
再实例化进行测试,模拟执行、撤销、重作的操做
var incrementCommand = new IncrementCommand(); // 模拟事件触发,执行命令 var eventTrigger = { // 某个事件的处理中,直接调用命令的处理方法 increment: function() { incrementCommand.execute(); }, incrementUndo: function() { incrementCommand.undo(); }, incrementRedo: function() { incrementCommand.redo(); } }; eventTrigger['increment'](); // 2 eventTrigger['increment'](); // 4 eventTrigger['incrementUndo'](); // 2 eventTrigger['increment'](); // 4 eventTrigger['incrementUndo'](); // 2 eventTrigger['incrementUndo'](); // 0 eventTrigger['incrementUndo'](); // 无输出 eventTrigger['incrementRedo'](); // 2 eventTrigger['incrementRedo'](); // 4 eventTrigger['incrementRedo'](); // 无输出 eventTrigger['increment'](); // 6
此外,还能够实现简单的宏命令(一系列命令的集合)
var MacroCommand = { commands: [], add: function(command) { this.commands.push(command); return this; }, remove: function(command) { if (!command) { this.commands = []; return; } for (var i = 0; i < this.commands.length; ++i) { if (this.commands[i] === command) { this.commands.splice(i, 1); } } }, execute: function() { for (var i = 0; i < this.commands.length; ++i) { this.commands[i].execute(); } } }; var showTime = { execute: function() { console.log('time'); } }; var showName = { execute: function() { console.log('name'); } }; var showAge = { execute: function() { console.log('age'); } }; MacroCommand.add(showTime).add(showName).add(showAge); MacroCommand.remove(showName); MacroCommand.execute(); // time age
1. 定义
是用小的子对象来构建更大的 对象,而这些小的子对象自己也许是由更小 的“孙对象”构成的。
2. 核心
能够用树形结构来表示这种“部分- 总体”的层次结构。
调用组合对象 的execute方法,程序会递归调用组合对象 下面的叶对象的execute方法
但要注意的是,组合模式不是父子关系,它是一种HAS-A(聚合)的关系,将请求委托给 它所包含的全部叶对象。基于这种委托,就须要保证组合对象和叶对象拥有相同的 接口
此外,也要保证用一致的方式对待 列表中的每一个叶对象,即叶对象属于同一类,不须要过多特殊的额外操做
3. 实现
使用组合模式来实现扫描文件夹中的文件
// 文件夹 组合对象 function Folder(name) { this.name = name; this.parent = null; this.files = []; } Folder.prototype = { constructor: Folder, add: function(file) { file.parent = this; this.files.push(file); return this; }, scan: function() { // 委托给叶对象处理 for (var i = 0; i < this.files.length; ++i) { this.files[i].scan(); } }, remove: function(file) { if (typeof file === 'undefined') { this.files = []; return; } for (var i = 0; i < this.files.length; ++i) { if (this.files[i] === file) { this.files.splice(i, 1); } } } }; // 文件 叶对象 function File(name) { this.name = name; this.parent = null; } File.prototype = { constructor: File, add: function() { console.log('文件里面不能添加文件'); }, scan: function() { var name = [this.name]; var parent = this.parent; while (parent) { name.unshift(parent.name); parent = parent.parent; } console.log(name.join(' / ')); } };
构造好组合对象与叶对象的关系后,实例化,在组合对象中插入组合或叶对象
var web = new Folder('Web'); var fe = new Folder('前端'); var css = new Folder('CSS'); var js = new Folder('js'); var rd = new Folder('后端'); web.add(fe).add(rd); var file1 = new File('HTML权威指南.pdf'); var file2 = new File('CSS权威指南.pdf'); var file3 = new File('JavaScript权威指南.pdf'); var file4 = new File('MySQL基础.pdf'); var file5 = new File('Web安全.pdf'); var file6 = new File('Linux菜鸟.pdf'); css.add(file2); fe.add(file1).add(file3).add(css).add(js); rd.add(file4).add(file5); web.add(file6); rd.remove(file4); // 扫描 web.scan();
扫描结果为
4. 优缺点
优势
可 以方便地构造一棵树来表示对象的部分-总体 结构。在树的构造最终 完成以后,只须要经过请求树的最顶层对 象,便能对整棵树作统一一致的操做。
缺点
建立出来的对象长得都差很少,可能会使代码很差理解,建立太多的对象对性能也会有一些影响
1. 定义
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。
2. 核心
在抽象父类中封装子类的算法框架,它的 init方法可做为一个算法的模板,指导子类以何种顺序去执行哪些方法。
由父类分离出公共部分,要求子类重写某些父类的(易变化的)抽象方法
3. 实现
模板方法模式通常的实现方式为继承
以运动做为例子,运动有比较通用的一些处理,这部分能够抽离开来,在父类中实现。具体某项运动的特殊性则有自类来重写实现。
最终子类直接调用父类的模板函数来执行
// 体育运动 function Sport() { } Sport.prototype = { constructor: Sport, // 模板,按顺序执行 init: function() { this.stretch(); this.jog(); this.deepBreath(); this.start(); var free = this.end(); // 运动后还有空的话,就拉伸一下 if (free !== false) { this.stretch(); } }, // 拉伸 stretch: function() { console.log('拉伸'); }, // 慢跑 jog: function() { console.log('慢跑'); }, // 深呼吸 deepBreath: function() { console.log('深呼吸'); }, // 开始运动 start: function() { throw new Error('子类必须重写此方法'); }, // 结束运动 end: function() { console.log('运动结束'); } }; // 篮球 function Basketball() { } Basketball.prototype = new Sport(); // 重写相关的方法 Basketball.prototype.start = function() { console.log('先投上几个三分'); }; Basketball.prototype.end = function() { console.log('运动结束了,有事先走一步'); return false; }; // 马拉松 function Marathon() { } Marathon.prototype = new Sport(); var basketball = new Basketball(); var marathon = new Marathon(); // 子类调用,最终会按照父类定义的顺序执行 basketball.init(); marathon.init();
1. 定义
2. 核心
运用共享技术来有效支持大量细粒度的对象。
强调将对象的属性划分为内部状态(属性)与外部状态(属性)。内部状态用于对象的共享,一般不变;而外部状态则剥离开来,由具体的场景决定。
3. 实现
在程序中使用了大量的类似对象时,能够利用享元模式来优化,减小对象的数量
举个栗子,要对某个班进行身体素质测量,仅测量身高体重来评判
// 健康测量 function Fitness(name, sex, age, height, weight) { this.name = name; this.sex = sex; this.age = age; this.height = height; this.weight = weight; } // 开始评判 Fitness.prototype.judge = function() { var ret = this.name + ': '; if (this.sex === 'male') { ret += this.judgeMale(); } else { ret += this.judgeFemale(); } console.log(ret); }; // 男性评判规则 Fitness.prototype.judgeMale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8); }; // 女性评判规则 Fitness.prototype.judgeFemale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 4) : (ratio > 3); }; var a = new Fitness('A', 'male', 18, 160, 80); var b = new Fitness('B', 'male', 21, 180, 70); var c = new Fitness('C', 'female', 28, 160, 80); var d = new Fitness('D', 'male', 18, 170, 60); var e = new Fitness('E', 'female', 18, 160, 40); // 开始评判 a.judge(); // A: false b.judge(); // B: false c.judge(); // C: false d.judge(); // D: true e.judge(); // E: true
评判五我的就须要建立五个对象,一个班就几十个对象
能够将对象的公共部分(内部状态)抽离出来,与外部状态独立。将性别看作内部状态便可,其余属性都属于外部状态。
这么一来咱们只须要维护男和女两个对象(使用factory对象),而其余变化的部分则在外部维护(使用manager对象)
// 健康测量 function Fitness(sex) { this.sex = sex; } // 工厂,建立可共享的对象 var FitnessFactory = { objs: [], create: function(sex) { if (!this.objs[sex]) { this.objs[sex] = new Fitness(sex); } return this.objs[sex]; } }; // 管理器,管理非共享的部分 var FitnessManager = { fitnessData: {}, // 添加一项 add: function(name, sex, age, height, weight) { var fitness = FitnessFactory.create(sex); // 存储变化的数据 this.fitnessData[name] = { age: age, height: height, weight: weight }; return fitness; }, // 从存储的数据中获取,更新至当前正在使用的对象 updateFitnessData: function(name, obj) { var fitnessData = this.fitnessData[name]; for (var item in fitnessData) { if (fitnessData.hasOwnProperty(item)) { obj[item] = fitnessData[item]; } } } }; // 开始评判 Fitness.prototype.judge = function(name) { // 操做前先更新当前状态(从外部状态管理器中获取) FitnessManager.updateFitnessData(name, this); var ret = name + ': '; if (this.sex === 'male') { ret += this.judgeMale(); } else { ret += this.judgeFemale(); } console.log(ret); }; // 男性评判规则 Fitness.prototype.judgeMale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8); }; // 女性评判规则 Fitness.prototype.judgeFemale = function() { var ratio = this.height / this.weight; return this.age > 20 ? (ratio > 4) : (ratio > 3); }; var a = FitnessManager.add('A', 'male', 18, 160, 80); var b = FitnessManager.add('B', 'male', 21, 180, 70); var c = FitnessManager.add('C', 'female', 28, 160, 80); var d = FitnessManager.add('D', 'male', 18, 170, 60); var e = FitnessManager.add('E', 'female', 18, 160, 40); // 开始评判 a.judge('A'); // A: false b.judge('B'); // B: false c.judge('C'); // C: false d.judge('D'); // D: true e.judge('E'); // E: true
不过代码可能更复杂了,这个例子可能还不够充分,只是展现了享元模式如何实现,它节省了多个类似的对象,但多了一些操做。
factory对象有点像单例模式,只是多了一个sex的参数,若是没有内部状态,则没有参数的factory对象就更接近单例模式了
1. 定义
2. 核心
请求发送者只须要知道链中的第一个节点,弱化发送者和一组接收者之间的强联系,能够便捷地在职责链中增长或删除一个节点,一样地,指定谁是第一个节点也很便捷
3. 实现
以展现不一样类型的变量为例,设置一条职责链,能够免去多重if条件分支
// 定义链的某一项 function ChainItem(fn) { this.fn = fn; this.next = null; } ChainItem.prototype = { constructor: ChainItem, // 设置下一项 setNext: function(next) { this.next = next; return next; }, // 开始执行 start: function() { this.fn.apply(this, arguments); }, // 转到链的下一项执行 toNext: function() { if (this.next) { this.start.apply(this.next, arguments); } else { console.log('无匹配的执行项目'); } } }; // 展现数字 function showNumber(num) { if (typeof num === 'number') { console.log('number', num); } else { // 转移到下一项 this.toNext(num); } } // 展现字符串 function showString(str) { if (typeof str === 'string') { console.log('string', str); } else { this.toNext(str); } } // 展现对象 function showObject(obj) { if (typeof obj === 'object') { console.log('object', obj); } else { this.toNext(obj); } } var chainNumber = new ChainItem(showNumber); var chainString = new ChainItem(showString); var chainObject = new ChainItem(showObject); // 设置链条 chainObject.setNext(chainNumber).setNext(chainString); chainString.start('12'); // string 12 chainNumber.start({}); // 无匹配的执行项目 chainObject.start({}); // object {} chainObject.start(123); // number 123
这时想判断未定义的时候呢,直接加到链中便可
// 展现未定义 function showUndefined(obj) { if (typeof obj === 'undefined') { console.log('undefined'); } else { this.toNext(obj); } } var chainUndefined = new ChainItem(showUndefined); chainString.setNext(chainUndefined); chainNumber.start(); // undefined
由例子能够看到,使用了职责链后,由本来的条件分支换成了不少对象,虽然结构更加清晰了,但在必定程度上可能会影响到性能,因此要注意避免过长的职责链。
1. 定义
2. 核心
使网状的多对多关系变成了相对简单的一对多关系(复杂的调度处理都交给中介者)
使用中介者后
3. 实现
多个对象,指的不必定得是实例化的对象,也能够将其理解成互为独立的多个项。当这些项在处理时,须要知晓并经过其余项的数据来处理。
若是每一个项都直接处理,程序会很是复杂,修改某个地方就得在多个项内部修改
咱们将这个处理过程抽离出来,封装成中介者来处理,各项须要处理时,通知中介者便可。
var A = { score: 10, changeTo: function(score) { this.score = score; // 本身获取 this.getRank(); }, // 直接获取 getRank: function() { var scores = [this.score, B.score, C.score].sort(function(a, b) { return a < b; }); console.log(scores.indexOf(this.score) + 1); } }; var B = { score: 20, changeTo: function(score) { this.score = score; // 经过中介者获取 rankMediator(B); } }; var C = { score: 30, changeTo: function(score) { this.score = score; rankMediator(C); } }; // 中介者,计算排名 function rankMediator(person) { var scores = [A.score, B.score, C.score].sort(function(a, b) { return a < b; }); console.log(scores.indexOf(person.score) + 1); } // A经过自身来处理 A.changeTo(100); // 1 // B和C交由中介者处理 B.changeTo(200); // 1 C.changeTo(50); // 3
ABC三我的分数改变后想要知道本身的排名,在A中本身处理,而B和C使用了中介者。B和C将更为轻松,总体代码也更简洁
最后,虽然中介者作到了对模块和对象的解耦,但有时对象之间的关系并不是必定要解耦,强行使用中介者来整合,可能会使代码更为繁琐,须要注意。
1. 定义
2. 核心
是为对象动态加入行为,通过多重包装,能够造成一条装饰链
3. 实现
最简单的装饰者,就是重写对象的属性
var A = { score: 10 }; A.score = '分数:' + A.score;
可使用传统面向对象的方法来实现装饰,添加技能
function Person() {} Person.prototype.skill = function() { console.log('数学'); }; // 装饰器,还会音乐 function MusicDecorator(person) { this.person = person; } MusicDecorator.prototype.skill = function() { this.person.skill(); console.log('音乐'); }; // 装饰器,还会跑步 function RunDecorator(person) { this.person = person; } RunDecorator.prototype.skill = function() { this.person.skill(); console.log('跑步'); }; var person = new Person(); // 装饰一下 var person1 = new MusicDecorator(person); person1 = new RunDecorator(person1); person.skill(); // 数学 person1.skill(); // 数学 音乐 跑步
在JS中,函数为一等对象,因此咱们也可使用更通用的装饰函数
// 装饰器,在当前函数执行前先执行另外一个函数 function decoratorBefore(fn, beforeFn) { return function() { var ret = beforeFn.apply(this, arguments); // 在前一个函数中判断,不须要执行当前函数 if (ret !== false) { fn.apply(this, arguments); } }; } function skill() { console.log('数学'); } function skillMusic() { console.log('音乐'); } function skillRun() { console.log('跑步'); } var skillDecorator = decoratorBefore(skill, skillMusic); skillDecorator = decoratorBefore(skillDecorator, skillRun); skillDecorator(); // 跑步 音乐 数学
1. 定义
2. 核心
区分事物内部的状态,把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部
3. 实现
以一我的的工做状态做为例子,在刚醒、精神、疲倦几个状态中切换着
// 工做状态 function Work(name) { this.name = name; this.currentState = null; // 工做状态,保存为对应状态对象 this.wakeUpState = new WakeUpState(this); // 精神饱满 this.energeticState = new EnergeticState(this); // 疲倦 this.tiredState = new TiredState(this); this.init(); } Work.prototype.init = function() { this.currentState = this.wakeUpState; // 点击事件,用于触发更新状态 document.body.onclick = () => { this.currentState.behaviour(); }; }; // 更新工做状态 Work.prototype.setState = function(state) { this.currentState = state; } // 刚醒 function WakeUpState(work) { this.work = work; } // 刚醒的行为 WakeUpState.prototype.behaviour = function() { console.log(this.work.name, ':', '刚醒呢,睡个懒觉先'); // 只睡了2秒钟懒觉就精神了.. setTimeout(() => { this.work.setState(this.work.energeticState); }, 2 * 1000); } // 精神饱满 function EnergeticState(work) { this.work = work; } EnergeticState.prototype.behaviour = function() { console.log(this.work.name, ':', '超级精神的'); // 才精神1秒钟就发困了 setTimeout(() => { this.work.setState(this.work.tiredState); }, 1000); }; // 疲倦 function TiredState(work) { this.work = work; } TiredState.prototype.behaviour = function() { console.log(this.work.name, ':', '怎么肥事,好困'); // 不知不觉,又变成了刚醒着的状态... 不断循环呀 setTimeout(() => { this.work.setState(this.work.wakeUpState); }, 1000); }; var work = new Work('曹操');
点击一下页面,触发更新状态的操做
4. 优缺点
优势
状态切换的逻辑分布在状态类中,易于维护
缺点
多个状态类,对于性能来讲,也是一个缺点,这个缺点可使用享元模式来作进一步优化
将逻辑分散在状态类中,可能不会很轻易就能看出状态机的变化逻辑
1. 定义
2. 核心
解决两个已有接口之间不匹配的问题
3. 实现
好比一个简单的数据格式转换的适配器
// 渲染数据,格式限制为数组了 function renderData(data) { data.forEach(function(item) { console.log(item); }); } // 对非数组的进行转换适配 function arrayAdapter(data) { if (typeof data !== 'object') { return []; } if (Object.prototype.toString.call(data) === '[object Array]') { return data; } var temp = []; for (var item in data) { if (data.hasOwnProperty(item)) { temp.push(data[item]); } } return temp; } var data = { 0: 'A', 1: 'B', 2: 'C' }; renderData(arrayAdapter(data)); // A B C
1. 定义
2. 核心
能够经过请求外观接口来达到访问子系统,也能够选择越过外观来直接访问子系统
3. 实现
外观模式在JS中,能够认为是一组函数的集合
// 三个处理函数 function start() { console.log('start'); } function doing() { console.log('doing'); } function end() { console.log('end'); } // 外观函数,将一些处理统一块儿来,方便调用 function execute() { start(); doing(); end(); } // 调用init开始执行 function init() { // 此处直接调用了高层函数,也能够选择越过它直接调用相关的函数 execute(); } init(); // start doing end
咱们知道在js中有一个运算符能够帮助咱们判断一个值的类型,它就是typeof运算符。
1
2
3
4
5
6
7
8
|
console.log(
typeof
123);
//number
console.log(
typeof
'123'
);
//string
console.log(
typeof
true
);
//boolean
console.log(
typeof
undefined);
//undefined
console.log(
typeof
null
);
//object
console.log(
typeof
[]);
//object
console.log(
typeof
{});
//object
console.log(
typeof
function
() {});
//function
|
咱们从以上结果能够看出typeof的不足之处,它对于数值、字符串、布尔值分别返回number、string、boolean,函数返回function,undefined返回undefined,除此之外,其余状况都返回object。
因此若是返回值为object,咱们是没法得知值的类型究竟是数组仍是对象或者其余值。为了准确获得每一个值的类型,咱们必须使用js中另外一个运算符instanceof。下面简单的说一下instanceof的用法。
instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。
instanceof运算符的左边是实例对象,右边是构造函数。它会检查右边构造函数的ptototype属性,是否在左边对象的原型链上。
1
2
3
|
var
b = [];
b
instanceof
Array
//true
b
instanceof
Object
//true
|
注意,instanceof运算符只能用于对象,不适用原始类型的值。
因此咱们能够结合typeof和instanceof运算符的特性,来对一个值的类型作出较为准确的判断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//获得一个值的类型
function
getValueType(value) {
var
type =
''
;
if
(
typeof
value !=
'object'
) {
type =
typeof
value;
}
else
{
if
(value
instanceof
Array) {
type =
'array'
;
}
else
{
if
(value
instanceof
Object) {
type =
'object'
;
}
else
{
type =
'null'
;
}
}
}
return
type;
}
getValueType(123);
//number
getValueType(
'123'
);
//string
getValueType(
true
);
//boolean
getValueType(undefined);
//undefined
getValueType(
null
);
//null
getValueType([]);
//array
getValueType({});
//object
getValueType(
function
(){});
//function
|
3.关于原型对象如下说法错误的是
1、什么是原型
原型是Javascript中的继承的基础,JavaScript的继承就是基于原型的继承。
1.1 函数的原型对象
在JavaScript中,咱们建立一个函数A(就是声明一个函数), 那么浏览器就会在内存中建立一个对象B,并且每一个函数都默认会有一个属性 prototype 指向了这个对象( 即:prototype的属性的值是这个对象 )。这个对象B就是函数A的原型对象,简称函数的原型。这个原型对象B 默认会有一个属性 constructor 指向了这个函数A ( 意思就是说:constructor属性的值是函数A )。
看下面的代码:
<body>
<script type="text/javascript">
/*
声明一个函数,则这个函数默认会有一个属性叫 prototype 。并且浏览器会自动按照必定的规则
建立一个对象,这个对象就是这个函数的原型对象,prototype属性指向这个原型对象。这个原型对象
有一个属性叫constructor 执行了这个函数
注意:原型对象默认只有属性:constructor。其余都是从Object继承而来,暂且不用考虑。
*/
function Person () {
}
</script>
</body>
下面的图描述了声明一个函数以后发生的事情:
1.2 使用构造函数建立对象
当把一个函数做为构造函数 (理论上任何函数均可以做为构造函数) 使用new建立对象的时候,那么这个对象就会存在一个默认的不可见的属性,来指向了构造函数的原型对象。 这个不可见的属性咱们通常用 [[prototype]] 来表示,只是这个属性没有办法直接访问到。
看下面的代码:
<body>
<script type="text/javascript">
function Person () {
}
/*
利用构造函数建立一个对象,则这个对象会自动添加一个不可见的属性 [[prototype]], 并且这个属性
指向了构造函数的原型对象。
*/
var p1 = new Person();
</script>
</body>
观察下面的示意图:
说明:
从上面的图示中能够看到,建立p1对象虽然使用的是Person构造函数,可是对象建立出来以后,这个p1对象其实已经与Person构造函数没有任何关系了,p1对象的[[ prototype ]]属性指向的是Person构造函数的原型对象。
若是使用new Person()建立多个对象,则多个对象都会同时指向Person构造函数的原型对象。
咱们能够手动给这个原型对象添加属性和方法,那么p1,p2,p3…这些对象就会共享这些在原型中添加的属性和方法。
若是咱们访问p1中的一个属性name,若是在p1对象中找到,则直接返回。若是p1对象中没有找到,则直接去p1对象的[[prototype]]属性指向的原型对象中查找,若是查找到则返回。(若是原型中也没有找到,则继续向上找原型的原型—原型链。 后面再讲)。
若是经过p1对象添加了一个属性name,则p1对象来讲就屏蔽了原型中的属性name。 换句话说:在p1中就没有办法访问到原型的属性name了。
经过p1对象只能读取原型中的属性name的值,而不能修改原型中的属性name的值。 p1.name = “李四”; 并非修改了原型中的值,而是在p1对象中给添加了一个属性name。
看下面的代码:
<body>
<script type="text/javascript">
function Person () {
}
// 可使用Person.prototype 直接访问到原型对象
//给Person函数的原型对象中添加一个属性 name而且值是 "张三"
Person.prototype.name = "张三";
Person.prototype.age = 20;
var p1 = new Person();
/*
访问p1对象的属性name,虽然在p1对象中咱们并无明确的添加属性name,可是
p1的 [[prototype]] 属性指向的原型中有name属性,因此这个地方能够访问到属性name
就值。
注意:这个时候不能经过p1对象删除name属性,由于只能删除在p1中删除的对象。
*/
alert(p1.name); // 张三
var p2 = new Person();
alert(p2.name); // 张三 都是从原型中找到的,因此同样。
alert(p1.name === p2.name); // true
// 因为不能修改原型中的值,则这种方法就直接在p1中添加了一个新的属性name,而后在p1中没法再访问到
//原型中的属性。
p1.name = "李四";
alert("p1:" + p1.name);
// 因为p2中没有name属性,则对p2来讲仍然是访问的原型中的属性。
alert("p2:" + p2.name); // 张三
</script>
</body>
2、与原型有关的几个属性和方法
2.1 prototype属性
prototype 存在于构造函数中 (其实任意函数中都有,只是否是构造函数的时候prototype咱们不关注而已) ,他指向了这个构造函数的原型对象。
参考前面的示意图。
2.2 constructor属性
constructor属性存在于原型对象中,他指向了构造函数
看下面的代码:
<script type="text/javascript">
function Person () {
}
alert(Person.prototype.constructor === Person); // true
var p1 = new Person();
//使用instanceof 操做符能够判断一个对象的类型。
//typeof通常用来获取简单类型和函数。而引用类型通常使用instanceof,由于引用类型用typeof 老是返回object。
alert(p1 instanceof Person); // true
</script>
咱们根据须要,能够Person.prototype 属性指定新的对象,来做为Person的原型对象。
可是这个时候有个问题,新的对象的constructor属性则再也不指向Person构造函数了。
看下面的代码:
<script type="text/javascript">
function Person () {
}
//直接给Person的原型指定对象字面量。则这个对象的constructor属性再也不指向Person函数
Person.prototype = {
name:"志玲",
age:20
};
var p1 = new Person();
alert(p1.name); // 志玲
alert(p1 instanceof Person); // true
alert(Person.prototype.constructor === Person); //false
//若是constructor对你很重要,你应该在Person.prototype中添加一行这样的代码:
/*
Person.prototype = {
constructor : Person //让constructor从新指向Person函数
}
*/
</script>
2.3 __proto__ 属性(注意:左右各是2个下划线)
用构造方法建立一个新的对象以后,这个对象中默认会有一个不可访问的属性 [[prototype]] , 这个属性就指向了构造方法的原型对象。
可是在个别浏览器中,也提供了对这个属性[[prototype]]的访问(chrome浏览器和火狐浏览器。ie浏览器不支持)。访问方式:p1.__proto__
可是开发者尽可能不要用这种方式去访问,由于操做不慎会改变这个对象的继承原型链。
<script type="text/javascript">
function Person () {
}
//直接给Person的原型指定对象字面量。则这个对象的constructor属性再也不指向Person函数
Person.prototype = {
constructor : Person,
name:"志玲",
age:20
};
var p1 = new Person();
alert(p1.__proto__ === Person.prototype); //true
</script>
2.4 hasOwnProperty() 方法
你们知道,咱们用去访问一个对象的属性的时候,这个属性既有可能来自对象自己,也有可能来自这个对象的[[prototype]]属性指向的原型。
那么如何判断这个对象的来源呢?
hasOwnProperty方法,能够判断一个属性是否来自对象自己。
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
//sex属性是直接在p1属性中添加,因此是true
alert("sex属性是对象自己的:" + p1.hasOwnProperty("sex"));
// name属性是在原型中添加的,因此是false
alert("name属性是对象自己的:" + p1.hasOwnProperty("name"));
// age 属性不存在,因此也是false
alert("age属性是存在于对象自己:" + p1.hasOwnProperty("age"));
</script>
因此,经过hasOwnProperty这个方法能够判断一个对象是否在对象自己添加的,可是不能判断是否存在于原型中,由于有可能这个属性不存在。
也便是说,在原型中的属性和不存在的属性都会返回fasle。
如何判断一个属性是否存在于原型中呢?
2.5 in 操做符
in操做符用来判断一个属性是否存在于这个对象中。可是在查找这个属性时候,如今对象自己中找,若是对象找不到再去原型中找。换句话说,只要对象和原型中有一个地方存在这个属性,就返回true
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
alert("sex" in p1); // 对象自己添加的,因此true
alert("name" in p1); //原型中存在,因此true
alert("age" in p1); //对象和原型中都不存在,因此false
</script>
回到前面的问题,若是判断一个属性是否存在于原型中:
若是一个属性存在,可是没有在对象自己中,则必定存在于原型中。
<script type="text/javascript">
function Person () {
}
Person.prototype.name = "志玲";
var p1 = new Person();
p1.sex = "女";
//定义一个函数去判断原型所在的位置
function propertyLocation(obj, prop){
if(!(prop in obj)){
alert(prop + "属性不存在");
}else if(obj.hasOwnProperty(prop)){
alert(prop + "属性存在于对象中");
}else {
alert(prop + "对象存在于原型中");
}
}
propertyLocation(p1, "age");
propertyLocation(p1, "name");
propertyLocation(p1, "sex");
</script
3、组合原型模型和构造函数模型建立对象
3.1 原型模型建立对象的缺陷
原型中的全部的属性都是共享的。也就是说,用同一个构造函数建立的对象去访问原型中的属性的时候,你们都是访问的同一个对象,若是一个对象对原型的属性进行了修改,则会反映到全部的对象上面。
可是在实际使用中,每一个对象的属性通常是不一样的。张三的姓名是张三,李四的姓名是李四。
**可是,这个共享特性对 方法(属性值是函数的属性)又是很是合适的。**全部的对象共享方法是最佳状态。这种特性在c#和Java中是天生存在的。
3.2 构造函数模型建立对象的缺陷
在构造函数中添加的属性和方法,每一个对象都有本身独有的一份,你们不会共享。这个特性对属性比较合适,可是对方法又不太合适。由于对全部对象来讲,他们的方法应该是一份就够了,没有必要每人一份,形成内存的浪费和性能的低下。
<script type="text/javascript">
function Person() {
this.name = "李四";
this.age = 20;
this.eat = function() {
alert("吃完东西");
}
}
var p1 = new Person();
var p2 = new Person();
//每一个对象都会有不一样的方法
alert(p1.eat === p2.eat); //fasle
</script>
可使用下面的方法解决:
<script type="text/javascript">
function Person() {
this.name = "李四";
this.age = 20;
this.eat = eat;
}
function eat() {
alert("吃完东西");
}
var p1 = new Person();
var p2 = new Person();
//由于eat属性都是赋值的同一个函数,因此是true
alert(p1.eat === p2.eat); //true
</script>
可是上面的这种解决方法具备致命的缺陷:封装性太差。使用面向对象,目的之一就是封装代码,这个时候为了性能又要把代码抽出对象以外,这是反人类的设计。
3.3 使用组合模式解决上述两种缺陷
原型模式适合封装方法,构造函数模式适合封装属性,综合两种模式的优势就有了组合模式。
<script type="text/javascript">
//在构造方法内部封装属性
function Person(name, age) {
this.name = name;
this.age = age;
}
//在原型对象内封装方法
Person.prototype.eat = function (food) {
alert(this.name + "爱吃" + food);
}
Person.prototype.play = function (playName) {
alert(this.name + "爱玩" + playName);
}
var p1 = new Person("李四", 20);
var p2 = new Person("张三", 30);
p1.eat("苹果");
p2.eat("香蕉");
p1.play("志玲");
p2.play("凤姐");
</script>
4、动态原型模式建立对象
前面讲到的组合模式,也并不是天衣无缝,有一点也是感受不是很完美。把构造方法和原型分开写,总让人感受不舒服,应该想办法把构造方法和原型封装在一块儿,因此就有了动态原型模式。
动态原型模式把全部的属性和方法都封装在构造方法中,而仅仅在须要的时候才去在构造方法中初始化原型,又保持了同时使用构造函数和原型的优势。
看下面的代码:
<script type="text/javascript">
//构造方法内部封装属性
function Person(name, age) {
//每一个对象都添加本身的属性
this.name = name;
this.age = age;
/*
判断this.eat这个属性是否是function,若是不是function则证实是第一次建立对象,
则把这个funcion添加到原型中。
若是是function,则表明原型中已经有了这个方法,则不须要再添加。
perfect!完美解决了性能和代码的封装问题。
*/
if(typeof this.eat !== "function"){
Person.prototype.eat = function () {
alert(this.name + " 在吃");
}
}
}
var p1 = new Person("志玲", 40);
p1.eat();
</script>
说明:
组合模式和动态原型模式是JavaScript中使用比较多的两种建立对象的方式。
建议之后使用动态原型模式。他解决了组合模式的封装不完全的缺点。
原文连接:https://blog.csdn.net/u012468376/article/details/53121081
4.下面说法错误的是
引用http://blog.csdn.net/two_people/article/details/53374552
1、什么是闭包?
官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(一般是一个函数),于是这些变量也是该表达式的一部分。
相信不多有人能直接看懂这句话,由于他描述的太学术。其实这句话通俗的来讲就是:JavaScript中全部的function都是一个闭包。不过通常来讲,嵌套的function所产生的闭包更为强大,也是大部分时候咱们所谓的“闭包”。看下面这段代码:
function a() {
var i = 0;
function b() { alert(++i); }
return b;
}
var c = a();
c();
这段代码有两个特色:
一、函数b嵌套在函数a内部;
二、函数a返回函数b。
引用关系如图:
这样在执行完var c=a()后,变量c其实是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就建立了一个闭包,为何?由于函数a外的变量c引用了函数a内的函数b,就是说:
当函数a的内部函数b被函数a外的一个变量引用的时候,就建立了一个闭包。
让咱们说的更透彻一些。所谓“闭包”,就是在构造函数体内定义另外的函数做为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标 对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目 标对象的方法内却始终能引用到该变量的值,并且该值只能通这种方法来访问。即便再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新 的值,和上次那次调用的是各自独立的。
2、闭包有什么做用?
简而言之,闭包的做用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,由于a的内部函数b的执行须要依赖a中的变量。这是对闭包做用的很是直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包须要按部就班的过程。
在上面的例子中,因为闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。
那 么咱们来想象另外一种状况,若是a返回的不是函数b,状况就彻底不一样了。由于a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,所以函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)
3、闭包内的微观世界
若是要更加深刻的了解闭包以及函数a和嵌套函数b的关系,咱们须要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、做用域(scope)、做用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。
到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的做用域链包含了对函数a的活动对象的引用,也就是说b能够访问到a中定义的全部变量和函数。函数b被c引用,函数b又依赖函数a,所以函数a在返回后不会被GC回收。
当函数b执行的时候亦会像以上步骤同样。所以,执行时b的做用域链包含了3个对象:b的活动对象、a的活动对象和window对象,以下图所示:
如图所示,当在函数b中访问一个变量的时候,搜索顺序是:
小结,本段中提到了两个重要的词语:函数的定义与执行。文中提到函数的做用域是在定义函数时候就已经肯定,而不是在执行的时候肯定(参看步骤1和3)。用一段代码来讲明这个问题:
function f(x) {
var g = function () { return x; }
return g;
}
var h = f(1);
alert(h());
这段代码中变量h指向了f中的那个匿名函数(由g返回)。
若是第一种假设成立,那输出值就是undefined;若是第二种假设成立,输出值则为1。
运行结果证实了第2个假设是正确的,说明函数的做用域确实是在定义这个函数的时候就已经肯定了。
4、闭包的应用场景
保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而没法经过其余途径访问到,所以保护了i的安全性。
function Constructor(...){
var that = this;
var membername = value;
function membername(...){...}
}
以上3点是闭包最基本的应用场景,不少经典案例都源于此。
5、Javascript的垃圾回收机制
在Javascript中,若是一个对象再也不被引用,那么这个对象就会被GC回收。若是两个对象互相引用,而再也不被第3者所引用,那么这两个互相引用的对象也会被回收。由于函数a被b引用,b又被a外的c引用,这就是为何函数a执行后不会被回收的缘由。
5.jQuery中判断元素是否包含某个类名的方法是
在jquery中可使用2种方法来判断一个元素是否包含一个肯定的类(class)。两种方法有着相同的功能。2种方法以下:
1. is(‘.classname’)
2. hasClass(‘classname’)
如下是一个div元素是否包含一个redColor的例子:
$('div').is('.redColor')
$('div').hasClass('redColor')
$("#isTest").click(function () {
if($('div').is('.redColor')){
$('div').addClass('blueColor');
}
});
$("#hasClassTest").click(function () {
if($('div').hasClass('redColor')){
$('div').addClass('blueColor'); }
}
6.如下运行的结果是false的是 function Box(){this.name='zhang';} function Desk(){this.age=100;} function Table(){this.lever=1000} Desk.prototype=new Box();//经过原型链继承 var desk=new Desk(); var table=new Table();
A. 一切事物皆对象
B. Dest 继承了 Box, 因此正确
C. Desk 是 Function的实例,和Box无关
D. desk 是 Desk 的实例
7.如下关于jquery的说法正确的是
Dom原生对象和jQuery对象到底有什么联系和区别呢
联系---二者之间可互相转换
一、jQuery对象能够经过jQuery包装DOM对象后产生;
二、DOM对象也能够经过jQuery按索引取得;
区别---两个对象彻底不一样
一、jQuery选择器获得的jQuery对象和标准的 javascript中的document.getElementById()取得的dom对象是两种不一样的对象类型,二者不等价;
二、jQuery没法使用DOM对象的任何方法,同理DOM对象也不能使用jQuery里的方法,上边报错就是这样的。
______
A、DOM对象转成jQuery对象:
对于DOM对象,只需用 $() 把DOM对象包装起来,就可获得jQuery对象
var dom =document.getElementById("id"); // DOM对象
var $dom = $(dom); // jQuery对象
B、jQuery对象转成DOM对象:两种转换方式 [index] 和 .get(index)
1.jQuery对象是一个数据对象,经过 [index] 的方法
var $dom = $("#id") ; // jQuery对象
var dom = $dom [0]; // DOM对象
2.jQuery提供方法,经过 .get(index) 方法
var $dom = $("#id"); // jQuery对象
var dom = $dom.get(0); // DOM对象
转载请注明,原文连接:http://zl378837964.iteye.com/blog/2327825
DOM对象和js对象以及jQuery对象的区别
文档对象模型简称DOM,是W3C组织推荐的处理可扩展置标语言的标准编程接口。
jQuery对象实际上是一个JavaScript的数组,这个数组对象包含125个方法和4个属性
4个属性分别是
jquery对象就是经过jQuery包装DOM对象后产生的对象。jQuery对象是jQuery独有的,其可使用jQuery里的方法,可是不能使用DOM的方法;反过来Dom对象也不能使用jquery的方法
jQuery对象和js对象区别:
1.jQuery对象属于js的数组;
2.jQuery对象是经过jQuery包装的DOM对象后产生的;
3.jQuery对象不能使用DOM对象的方法和属性
4.DOM对象不能使用jQuery对象的方法和属性
1) js转jQuery对象:
$(js对象)
2)jQuery对象转js对象
示例:
var doc2=$("#idDoc2")[0];
//转换jQuery对象为DOM对象
doc2.innerHTML="这是jQuery的第一个DOM对象"
//使用jQuery对象自己提供的get函数来返回指定集合位置的DOM对象
var doc2=$("#idDoc2").get(0);
doc2.innerHTML="这是jQuery的第二个DOM对象"
8. 如下说法正确的是
类 :对一群具备相同特征的对象的集合的描述;
对象:真实存在的对象个体;
所谓的面向对象,而不是面向类。
1.一切皆对象,继承靠原型链,多态靠弱类型,封装……虽然能够靠闭包,但我我的更推崇和python同样的,下划线表明私有的风格
2.好比人类,指的是一个范围; 对象:好比某我的,指的是这个范围中具体的对象
3.Javascript中的function做为构造函数时,就是一个类,搭配上new操做符,能够返回一个对象。
固然,要生成一个对象,也能够用字面量的形式,例如var obj = {x: 1, y: function(){} };
类能够理解为一个模板,而对象就是根据这个模板造出来的具体实例。
instanceof 判断一个对象是否是属于一个类
对象 instanceof 构造函数
本身的父级 父级 。。。。
constructor 判断直接的父级
1. 前言
做为一名前端工程师,必须搞懂JS中的prototype、__proto__与constructor属性,相信不少初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助你们理清它们之间的关系并完全搞懂它们。这里说明一点,__proto__属性的两边是各由两个下划线构成(这里为了方便你们看清,在两下划线之间加入了一个空格:_ _proto_ _,读做“dunder proto”,“double underscore proto”的缩写),实际上,该属性在ES标准定义中的名字应该是[[Prototype]],具体实现是由浏览器代理本身实现,谷歌浏览器的实现就是将[[Prototype]]命名为__proto__,你们清楚这个标准定义与具体实现的区别便可(名字有所差别,功能是同样的),能够经过该方式检测引擎是否支持这个属性:Object.getPrototypeOf({__proto__: null}) === null。本文基于谷歌浏览器(版本 72.0.3626.121)的实验结果所得。
如今正式开始! 让咱们从以下一个简单的例子展开讨论,并配以相关的图帮助理解:
function Foo() {...};
let f1 = new Foo();
以上代码表示建立一个构造函数Foo(),并用new关键字实例化该构造函数获得一个实例化对象f1。这里稍微补充一下new操做符将函数做为构造器进行调用时的过程:函数被调用,而后新建立一个对象,而且成了函数的上下文(也就是此时函数内部的this是指向该新建立的对象,这意味着咱们能够在构造器函数内部经过this参数初始化值),最后返回该新对象的引用。虽然是简简单单的两行代码,然而它们背后的关系倒是错综复杂的,以下图所示:
看到这图别怕,让咱们一步步剖析,完全搞懂它们!
图的说明:右下角为图例,红色箭头表示__proto__属性指向、绿色箭头表示prototype属性的指向、棕色实线箭头表示自己具备的constructor属性的指向,棕色虚线箭头表示继承而来的constructor属性的指向;蓝色方块表示对象,浅绿色方块表示函数(这里为了更好看清,Foo()仅表明是函数,并非指执行函数Foo后获得的结果,图中的其余函数同理)。图的中间部分即为它们之间的联系,图的最左边即为例子代码。
2. _ _ proto _ _ 属性
首先,咱们须要牢记两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的。可是因为JS中函数也是一种对象,因此函数也拥有__proto__和constructor属性,这点是导致咱们产生困惑的很大缘由之一。上图有点复杂,咱们把它按照属性分别拆开,而后进行分析:
第一,这里咱们仅留下 __proto__ 属性,它是对象所独有的,能够看到__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也能够理解为父对象),那么这个属性的做用是什么呢?它的做用就是当访问一个对象的属性时,若是该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(能够理解为父对象)里找,若是父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(能够理解为爷爷对象)里找,若是还没找到,则继续往上找…直到原型链顶端null(能够理解为原始人。。。),再往上找就至关于在null上取值,会报错(能够理解为,再往上就已经不是“人”的范畴了,找不到了,到此结束,null为原型链的终点),由以上这种经过__proto__属性来链接对象直到null的一条链即为咱们所谓的原型链。
3. prototype属性
第二,接下来咱们看 prototype 属性:
prototype属性,别忘了一点,就是咱们前面提到要牢记的两点中的第二点,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实全部函数均可以做为构造函数)所建立的实例的原型对象,由此可知:f1.__proto__ === Foo.prototype,它们两个彻底同样。那prototype属性的做用又是什么呢?它的做用就是包含能够由特定类型的全部实例共享的属性和方法,也就是让该函数所实例化的对象们均可以找到公用的属性和方法。任何函数在建立的时候,其实会默认同时建立该函数的prototype对象。
4. constructor属性
最后,咱们来看一下 constructor 属性:
constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每一个对象都有构造函数(自己拥有或继承而来,继承而来的要结合__proto__属性查看会更清楚点,以下图所示),从上图中能够看出Function这个对象比较特殊,它的构造函数就是它本身(由于Function能够当作是一个函数,也能够是一个对象),全部函数和对象最终都是由Function构造函数得来,因此constructor属性的终点就是Function这个函数。
感谢网友的指出,这里解释一下上段中“每一个对象都有构造函数”这句话。这里的意思是每一个对象均可以找到其对应的constructor,由于建立对象的前提是须要有constructor,而这个constructor多是对象本身自己显式定义的或者经过__proto__在原型链中找到的。而单从constructor这个属性来说,只有prototype对象才有。每一个函数在建立的时候,JS会同时建立一个该函数对应的prototype对象,而函数建立的对象.__proto__ === 该函数.prototype,该函数.prototype.constructor===该函数自己,故经过函数建立的对象即便本身没有constructor属性,它也能经过__proto__找到对应的constructor,因此任何对象最终均可以找到其构造函数(null若是当成对象的话,将null除外)。以下:
5. 总结
总结一下:
咱们须要牢记两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的,由于函数也是一种对象,因此函数也拥有__proto__和constructor属性。
__proto__属性的做用就是当访问一个对象的属性时,若是该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就至关于在null上取值,会报错。经过__proto__属性将对象链接起来的这条链路即咱们所谓的原型链。
prototype属性的做用就是让该函数所实例化的对象们均可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype。
constructor属性的含义就是指向该对象的构造函数,全部函数(此时当作对象了)最终的构造函数都指向Function。
jQuery中提供了四种事件监听方式,分别是bind、live、delegate、on,对应的解除监听的函数分别是unbind、die、undelegate、off。
bind(type,[data],function(eventObject))
bind是使用频率较高的一种,做用就是在选择到的元素上绑定特定事件类型的监听函数,参数的含义以下:
type:事件类型,如click、change、mouseover等; data:传入监听函数的参数,经过event.data取到。可选; function:监听函数,可传入event对象,这里的event是jQuery封装的event对象,与原生的event对象有区别,使用时须要注意
bind的源码:
bind: function( types, data, fn ) { return this.on( types, null, data, fn ); } $('#myol li').bind('click',getHtml);
bind的特色就是会把监听器绑定到目标元素上,有一个绑一个,在页面上的元素不会动态添加的时候使用它没什么问题。但若是列表中动态增长一个“列表元素5”,点击它是没有反应的,必须再bind一次才行。要想不这么麻烦,咱们可使用live。
live(type, [data], fn)
live的参数和bind同样
live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }
能够看到live方法并无将监听器绑定到本身(this)身上,而是绑定到了this.context上了。这个context是什么东西呢?其实就是元素的限定范围
$('#myol li').context; //document $('#myol li','#myol').context; //document $('#myol li',$('#myol')[0]); //ol
一般状况下,咱们都不会像第三种方式那样使用选择器,因此也就认为这个context一般就是document了,即live方法把监听器绑定到了 document上了。不把监听器直接绑定在元素上,你是否是想起事件委托机制来了呢?live正是利用了事件委托机制来 完成事件的监听处理,把节点的处理委托给了document。在监听函数中,咱们能够用event.currentTarget来获取到当前捕捉到事件的 节点。下面的例子来揭晓:
$('#myol li').live('click',getHtml);
live存在那样的缺点,因此咱们就思考,既然老爷子负担那么重,可不能够别把监听器绑定在document上呢,绑定在就近的父级元素上不就行了。顺应正常逻辑,delegate诞生了。
参数多了一个selector,用来指定触发事件的目标元素,监听器将被绑定在调用此方法的元素上。看看源码:
delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }
又是调用了on,而且把selector传给了on。看来这个on真的是举足轻重的东西。照样先无论它。看看示例先:
$('#myol').delegate('li','click',getHtml);
看了这么多,你是否是火烧眉毛想看看这个on的真实面目了呢,这就来:
on(type,[selector],[data],fn)
参数与delegate差很少但仍是有细微的差异,首先type与selector换位置了,其次selector变为了可选项。交换位置的缘由很差查证,应该是为了让视觉上更舒服一些吧。
咱们先不传selector来看一个例子:
$('#myol li').on('click',getHtml);
能够看到event.currentTarget是li本身,与bind的效果同样。至于传selector进去,就是跟delegate同样的意义了,除了参数顺序不一样,其余彻底同样。