├── doc ├── design mode │ ├── 中介者模式.md │ ├── 备忘录模式.md │ ├── 解释器模式.md │ ├── 迭代器模式.md │ ├── 策略模式.md │ ├── 外观模式.md │ ├── 装饰着模式.md │ ├── 命令模式.md │ ├── 代理模式.md │ ├── 访问者模式.md │ ├── 状态模式.md │ ├── 单例模式.md │ ├── 桥接模式.md │ ├── 观察者模式.md │ ├── 简单工厂设计模式.md │ ├── 工厂方法模式.md │ ├── 建造者模式.md │ ├── 模板方法模式.md │ ├── 原型模式.md │ ├── 职责链模式.md │ ├── 适配器模式.md │ ├── 抽象工厂模式.md │ ├── 组合模式.md │ ├── 享元模式.md │ └── 面向对象.md ├── es6 │ └── let、const命令.md ├── dataStructure │ ├── stack.md │ ├── queue.md │ ├── Set.md │ ├── test.js │ ├── array.md │ ├── tree.md │ ├── Map&HashMap.md │ └── linkedList.md └── basic_js │ ├── JavaScript中的执行上下文和变量对象.md │ ├── 夯实JS系列--变量、作用域和内存问题.md │ ├── 原型和原型链.md │ ├── 谈谈闭包.md │ ├── 编写高质量代码基本要点.md │ ├── 彻底明白this指向.md │ ├── 忍者级别的操作函数.md │ ├── prototype-based.md │ └── JavaScript中的跨域总结.md ├── img ├── 171030.png ├── pay │ ├── pay.png │ └── wx.jpg ├── 171105_01.png ├── 17120401.png ├── 20151118102648527.jpg └── QQ20171204-180745@2x.png ├── .idea ├── vcs.xml ├── modules.xml └── YOU-SHOULD-KNOW-JS.iml └── README.md /doc/design mode/中介者模式.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/design mode/备忘录模式.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/design mode/解释器模式.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/design mode/迭代器模式.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/es6/let、const命令.md: -------------------------------------------------------------------------------- 1 | # let、const命令 2 | 3 | ## let命令 4 | -------------------------------------------------------------------------------- /img/171030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/YOU-SHOULD-KNOW-JS/HEAD/img/171030.png -------------------------------------------------------------------------------- /img/pay/pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/YOU-SHOULD-KNOW-JS/HEAD/img/pay/pay.png -------------------------------------------------------------------------------- /img/pay/wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/YOU-SHOULD-KNOW-JS/HEAD/img/pay/wx.jpg -------------------------------------------------------------------------------- /img/171105_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/YOU-SHOULD-KNOW-JS/HEAD/img/171105_01.png -------------------------------------------------------------------------------- /img/17120401.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/YOU-SHOULD-KNOW-JS/HEAD/img/17120401.png -------------------------------------------------------------------------------- /img/20151118102648527.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/YOU-SHOULD-KNOW-JS/HEAD/img/20151118102648527.jpg -------------------------------------------------------------------------------- /img/QQ20171204-180745@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nealyang/YOU-SHOULD-KNOW-JS/HEAD/img/QQ20171204-180745@2x.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc/design mode/策略模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之策略模式 2 | 3 | ### 概念 4 | 策略模式:将定义的一组算法封装起来,使其相互之间可以替代。封装的算法具有一定的独立性,不会随着客户端变化而变化 5 | 6 | 从结构上看,他和状态模式非常的相似,也是在内部封装一个对象,然后通过返回的借口对象实现对内部对象的调用,不同的是,策略模式不需要管理状态,状态之间没有依赖关系,策略之间可以相互替换,在策略对象内部保存的是一些相对独立的一些算法。 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/YOU-SHOULD-KNOW-JS.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /doc/design mode/外观模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之外观模式 2 | 3 | ### 概念 4 | 外观模式:为一组复杂子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统访问更加的容易。 5 | 6 | ### 代码演示 7 | 8 | ```javascript 1.6 9 | // 使用外观模式注册事件监听 10 | function addEvent(dom,type,fn) { 11 | if(dom.addEventListener){ 12 | dom.addEventListener(type,fn,false); 13 | }else if(dom.attachEvent){ 14 | dom.attachEvent('on'+type,fn); 15 | }else{ 16 | dom['on'+type] = fn; 17 | } 18 | } 19 | // 使用外观模式获取事件对象 20 | 21 | var getEvent = function(event) { 22 | return event || window.event; 23 | } 24 | ``` 25 | 26 | 通过对接口的二次封装,使其简单易用,隐藏起内部的复杂度,外观模式就是对接口的外层包装,以供上层代码调用。因此外观模式封装的接口方法不需要接口的具体实现,只需要按照接口的使用规则使用即可。 -------------------------------------------------------------------------------- /doc/design mode/装饰着模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之装饰着模式 2 | 3 | ### 概念 4 | 5 | 装饰着模式,在不改变源对象的基础上,通过对其进行包装拓展使原有对象可以满足用户的更复杂需求 6 | 7 | ### 代码演示 8 | 9 | 这里我拿给输入框添加事件举例 10 | 11 | ```javascript 1.6 12 | var decorator = function(input ,fn) { 13 | //获取时间源 14 | var input = document.getElementById(input); 15 | if(typeof input.onclick === 'function'){ 16 | //缓存事件源原有的回调函数 17 | var oldClickFn = input.onclick; 18 | input.onclick = function (ev) { 19 | oldClickFn(); 20 | fn(); 21 | } 22 | }else{ 23 | input.onclick = fn; 24 | } 25 | } 26 | ``` 27 | 28 | 装饰着模式很简单,就是对原有对象的属性和方法的添加。相比于之前说的适配器模式是对原有对象的适配,添加的方法和原有的方法功能上大致相似。但是装饰着提供的方法和原有方法功能项则有一定的区别,且不需要去了解原有对象的功能。只要原封不动的去使用就行。不需要知道具体的实现细节。 -------------------------------------------------------------------------------- /doc/design mode/命令模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之命令模式 2 | 3 | ### 概念 4 | 5 | 命令模式:用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行。也就是说该模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一些列的处理。他也可以用来消除调用操作的对象和实现操作的对象之间的耦合。这为各种具体的类的更换带来了极大的灵活性。 6 | 7 | ### 代码演示 8 | 9 | ```javascript 1.6 10 | 11 | //1.一个连有炮兵和步兵,司令可以下命令调动军队打仗 12 | var lian = {}; 13 | lian.paobing = function(pao_num){ 14 | console.log(pao_num+"门炮准备战斗"); 15 | } 16 | lian.bubing = function(bubing_num){ 17 | console.log(bubing_num+"人准备战斗"); 18 | } 19 | lian.lianzhang = function(mingling){ 20 | lian[mingling.type](mingling.num); 21 | } 22 | 23 | //司令下命令 24 | lian.lianzhang({ 25 | type:"paobing", 26 | num:10 27 | }); 28 | lian.lianzhang({ 29 | type:"bubing", 30 | num:100 31 | }); 32 | 33 | ``` -------------------------------------------------------------------------------- /doc/design mode/代理模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之代理模式 2 | 3 | ### 概念 4 | 代理模式:由于一个对象不能直接引用另一个对象,所以需要代理对象在这两个对象之间起到中介的作用 5 | 6 | ### 代码演示 7 | ```javascript 1.6 8 | // 先声明美女对象 9 | var girl = function (name) { 10 | this.name = name; 11 | }; 12 | 13 | // 这是dudu 14 | var dudu = function (girl) { 15 | this.girl = girl; 16 | this.sendGift = function (gift) { 17 | alert("Hi " + girl.name + ", dudu送你一个礼物:" + gift); 18 | } 19 | }; 20 | 21 | // 大叔是代理 22 | var proxyTom = function (girl) { 23 | this.girl = girl; 24 | this.sendGift = function (gift) { 25 | (new dudu(girl)).sendGift(gift); // 替dudu送花咯 26 | } 27 | }; 28 | 29 | var proxy = new proxyTom(new girl("酸奶小妹")); 30 | proxy.sendGift("999朵玫瑰"); 31 | ``` 32 | 假如dudu要送酸奶小妹玫瑰花,却不知道她的联系方式或者不好意思,想委托大叔去送这些玫瑰,那大叔就是个代理 33 | 34 | 其实在日常开发中,我们遇到很多这种情况,比如跨域,之前总结过跨域的所有东西,其中的jsonp,window.name还是location.hash都是通过代理模式来实现的。 35 | 36 | 代理模式具体的从我的另一篇文章,JavaScript中的跨域总结去体会哈 37 | -------------------------------------------------------------------------------- /doc/design mode/访问者模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之访问者模式 2 | 3 | ### 概念 4 | 访问者模式:针对于对象结构中的元素,定义在不改变对象的前提下访问结构中元素的方法 5 | 6 | 在访问者模式中,主要包括下面几个角色 7 | 8 | 1、抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。 9 | 10 | 2、访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。 11 | 12 | 3、抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。 13 | 14 | 4、元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。 15 | 16 | 5、结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。 17 | 18 | 19 | ### 代码演示 20 | 21 | ```javascript 1.6 22 | // 访问者 23 | function Visitor() { 24 | this.visit = function( concreteElement ) { 25 | concreteElement.doSomething(); 26 | } 27 | } 28 | // 元素类 29 | function ConceteElement() { 30 | this.doSomething = function() { 31 | console.log("这是一个具体元素"); 32 | } 33 | this.accept = function( visitor ) { 34 | visitor.visit(this); 35 | } 36 | } 37 | // Client 38 | var ele = new ConceteElement(); 39 | var v = new Visitor(); 40 | ele.accept( v ); 41 | ``` -------------------------------------------------------------------------------- /doc/design mode/状态模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之状态模式 2 | 3 | ### 概念 4 | 状态模式:当一个对象内部状态发生变化的时候,会导致起行为变化,看起来就像改变了对象 5 | 6 | 状态模式定义了一个对象,这个对象可以通过管理其内部状态从而是其行为发生变化。状态模式是一个非常出色的设计模式,主要由两个角色构成 7 | 8 | - 环境类:拥有一个状态成员,可以修改其状态并做出反应 9 | - 状态类:表示一种状态,包含相应的处理方法 10 | 11 | ### 代码演示 12 | 对于一个简单的例子,我们可以将不同的判断结构封装在一个状态对象内,然后该状态对象返回一个可被调用的状态方法,用于调用对象内部某个方法。 13 | 14 | ```javascript 1.6 15 | var ResultState = function() { 16 | var States = { 17 | state0:function() { 18 | console.log('这是第一种结果'); 19 | }, 20 | state1:function() { 21 | console.log('这是第二种结果'); 22 | }, 23 | state2:function() { 24 | console.log('这是第三种结果'); 25 | }, 26 | state3:function() { 27 | console.log('这是第四种结果'); 28 | } 29 | }; 30 | //获取某一种状态并执行相应的方法 31 | function show(result) { 32 | States['state'+result]&&States['state'+result](); 33 | } 34 | 35 | return { 36 | show:show 37 | } 38 | }() 39 | 40 | ResultState.show(4); 41 | ``` 42 | 43 | 上面代码只是一个雏形,对于状态模式主要目的就是将条件判断的不同结构,转化为状态对象的内部状态,既然是状态对象的内部状态,所以一般是作为状态对象的私有变量,然后提供一个能够调用状态对象内部状态的接口方法对象 44 | 45 | -------------------------------------------------------------------------------- /doc/design mode/单例模式.md: -------------------------------------------------------------------------------- 1 | # JavaScript设计模式之单例模式 2 | 3 | > 学习一个东西,一定要明白这个东西的概念是什么,这个东西被提出来的目的是什么 4 | 5 | ## 概念 6 | 单例模式又称为单体模式,其实就是只允许实例化一个对象,有时我们也可以用一个对象类规划命名空间,仅仅有条的管理对象的属性和方法. 7 | 8 | 9 | 单例模式比较常规常见,比较简单,直接看代码 10 | 11 | ## 代码演示 12 | ```javascript 1.6 13 | //命名空间管理 14 | var Neal = { 15 | g:function(id) { 16 | return document.getElementById(id) 17 | }, 18 | css:function(id,key,value) { 19 | this.g(id).style[key] = value; 20 | }, 21 | //... 22 | } 23 | //模块分明 24 | var A = { 25 | Util:{ 26 | util_method1:function() { 27 | 28 | }, 29 | util_method2:function() { 30 | 31 | } 32 | }, 33 | Tool:{ 34 | tool_method1:function(){}, 35 | tool_method2:function(){}, 36 | } 37 | //... 38 | } 39 | //惰性单例 40 | var LazySingle = (function() { 41 | //单例实例引用 42 | var _instance = null; 43 | //单例 44 | function Single() { 45 | return{ 46 | publicMethod:function() { 47 | 48 | }, 49 | publicProperty:'1.0' 50 | } 51 | } 52 | //获取单例接口 53 | return function() { 54 | if(!_instance){ 55 | _instance = Single(); 56 | } 57 | //返回单例 58 | return _instance; 59 | } 60 | })() 61 | ``` -------------------------------------------------------------------------------- /doc/dataStructure/stack.md: -------------------------------------------------------------------------------- 1 | # JavaScript数据结构与算法--栈 2 | 3 | ## 定义 4 | 栈是一种遵从先进后出LIFO原则的有序集合。新添加的或待删除的元素都保存在栈的 末尾,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。 5 | 6 | 而栈主要是在编程语言的编译器里用来保存变量和方法调用等。 7 | 8 | ## 创建栈 9 | ```javascript 10 | function Stack() { 11 | var _item = []; 12 | this.push = function(element) { 13 | this._item.push(element) 14 | }; 15 | this.pop = function() { 16 | return _item.pop(); 17 | }; 18 | this.peek = function() { 19 | return _item[_item.length-1] 20 | }; 21 | this.isEmpty = function() { 22 | return _item.length === 0; 23 | }; 24 | this.size = function() { 25 | return _item.length; 26 | }; 27 | this.clear = function() { 28 | _item = []; 29 | }; 30 | this.print = function() { 31 | console.log(_item.toString()) 32 | } 33 | } 34 | ``` 35 | 36 | ### 实际应用场景 37 | 38 | 进制转换 39 | 40 | ```javascript 41 | function baseConverter(decNumber,base) { 42 | var remStack = new Stack(), 43 | rem, 44 | baseString = '', 45 | digits = '0123456789ABCDEF'; 46 | 47 | while (decNumber>0){ 48 | rem = Math.floor(decNumber % base); 49 | remStack.push(rem); 50 | decNumber = Math.floor(decNumber/base); 51 | } 52 | while(!remStack.isEmpty()){ 53 | baseString+=digits[remStack.pop()]; 54 | } 55 | return baseString; 56 | } 57 | ``` -------------------------------------------------------------------------------- /doc/design mode/桥接模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之桥接模式 2 | 3 | ### 概念 4 | 桥接模式:在系统沿着多个维度变化的时候,不增加起复杂度已达到解耦的目的 5 | 6 | ### 应用场景 7 | 8 | 在我们日常开发中,需要对相同的逻辑做抽象的处理。桥接模式就是为了解决这类的需求。 9 | 10 | 桥接模式最主要的特点就是将实现层和抽象层解耦分离,是两部分可以独立变化 11 | 12 | 比如我们写一个跑步游戏,对于游戏中的人和精灵都是动作单元。而他们的动作也是非常的统一。比如人和精灵和球运动都是x,y坐标的改变,球的颜色和精灵的颜色绘制方式也非常的类似。 13 | 我们就可以将这些方法给抽象出来。 14 | 15 | ### 代码演示 16 | 17 | ```javascript 1.6 18 | //运动单元 19 | function Speed(x,y) { 20 | this.x = x; 21 | this.y = y; 22 | } 23 | Speed.prototype.run = function() { 24 | console.log('动起来'); 25 | } 26 | // 着色单元 27 | function Color(cl) { 28 | this.color = cl; 29 | } 30 | Color.prototype.draw = function() { 31 | console.log('绘制色彩') 32 | } 33 | 34 | // 变形单元 35 | function Shape(ap) { 36 | this.shape = ap; 37 | } 38 | Shape.prototype.change = function() { 39 | console.log('改变形状'); 40 | } 41 | //说话单元 42 | function Speak(wd) { 43 | this.word = wd; 44 | } 45 | Speak.prototype.say = function() { 46 | console.log('请开始你的表演') 47 | } 48 | 49 | 50 | //创建球类,并且它可以运动可以着色 51 | function Ball(x,y,c) { 52 | this.speed = new Speed(x,y); 53 | this.color = new Color(c); 54 | } 55 | Ball.prototype.init = function() { 56 | //实现运动和着色 57 | this.speed.run(); 58 | this.color.draw(); 59 | } 60 | 61 | function People(x,y,f) { 62 | this.speed = new Speed(x,y); 63 | this.speak = new Speak(f); 64 | } 65 | 66 | People.prototype.init = function() { 67 | this.speed.run(); 68 | this.speak.say(); 69 | } 70 | //... 71 | 72 | 73 | //当我们实例化一个人物对象的时候,他就可以有对应的方法实现了 74 | 75 | var p =new People(10,12,'我是一个人'); 76 | p.init(); 77 | ``` -------------------------------------------------------------------------------- /doc/design mode/观察者模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之观察者模式 2 | 3 | ### 概念 4 | 观察者模式,又被称为发布订阅模式,或者消息机制。定义了一种依赖关系,解决了主体对象和观察者之间功能的耦合 5 | 6 | 其实他就是定义了一种一对多的关系,让多个观察者对象同时监听某一个主体对象,这个主体对象的状态发生变化时,就会通知所有的观察者对象,使得他们能够自动更新自己。 7 | 8 | 观察者对象的好处: 9 | - 支持简单的广播通信,自动通知所有已经订阅过的对象。 10 | - 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。 11 | - 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。 12 | 13 | ### 代码演示 14 | js通过对观察者模式的实现是通过回调函数的方式 15 | 16 | 我们来先定义一个pubsub对象,其内部包含了3个方法:订阅、退订、发布 17 | 18 | ```javascript 19 | // 创建一个观察者 20 | var Observe = (function() { 21 | var _message = {}; 22 | return { 23 | register:function(type,fn) { 24 | //注册信息接口 25 | if(typeof _message[type] === 'undefined'){ 26 | _message[type] = [fn]; 27 | }else{ 28 | _message[type].push(fn); 29 | } 30 | }, 31 | fire:function(type,args) { 32 | //发布消息接口 33 | if(!_message[type]) return; 34 | //消息信息 35 | var event = { 36 | type:type, 37 | args:args||{} 38 | }, 39 | i = 0, 40 | len = _message[type].length; 41 | for(;i-0;i--){ 51 | _message[type][i] === fn && _message[type].splice(i,1); 52 | } 53 | } 54 | } 55 | } 56 | })(); 57 | 58 | 59 | Observe.register('test',function(e) { 60 | console.log(e.type,e.args.msg); 61 | }); 62 | Observe.register('test',function(e) { 63 | console.log(e.type,'Nealyang'); 64 | }); 65 | 66 | Observe.fire('test',{msg:'test test'}); 67 | ``` 68 | -------------------------------------------------------------------------------- /doc/design mode/简单工厂设计模式.md: -------------------------------------------------------------------------------- 1 | # JavaScript设计模式之简单工厂设计模式 2 | 3 | > 作为创建性设计模式中的第一个介绍的,这个模式必然比较简单,所以不做过多介绍 4 | 5 | ## 使用场景 6 | 7 | 简单工厂模式的概念就是创建对象,不仅如此,简单工厂模式还可以用来创建相似的对象。 8 | 9 | 举个例子来说,如果你想创建一些书,那么这些书都有一些相似的地方,比如目录、页码等。也有很多不同的地方, 10 | 如书名、出版时间、书的类型等,对于创建的对象相似的属性当然好处理,对于不同的属性就要有针对的修改处理了。 11 | 12 | 13 | ## 代码 14 | ```javascript 15 | //工厂模式 16 | function createBook(name,time,type) { 17 | //创建一个对象,并且对对象拓展属性和方法 18 | var o = new Object(); 19 | o.name = name; 20 | o.time = time; 21 | o.type = type; 22 | o.getName = function() { 23 | console.log(this.name); 24 | } 25 | 26 | //将对象返回 27 | return o; 28 | } 29 | 30 | var book1 = createBook('js book','2017/11/16','JS'); 31 | var book2 = createBook('css book','2017/11/13','CSS'); 32 | 33 | book1.getName(); 34 | book2.getName(); 35 | ``` 36 | 37 | ```javascript 38 | var Basketball = function() { 39 | this.info='美国篮球' 40 | } 41 | Basketball.prototype = { 42 | constructor:Basketball, 43 | getMember:function() { 44 | console.log('每队需要5个成员'); 45 | }, 46 | getBallSize:function() { 47 | console.log('这个篮球还是很大的'); 48 | } 49 | } 50 | var Football = function() { 51 | this.info = '这是足球' 52 | } 53 | Football.prototype = { 54 | constructor:Football, 55 | getMember:function() { 56 | console.log('足球每队需要十一个人') 57 | }, 58 | getBallSize:function() { 59 | console.log('足球我不喜欢'); 60 | } 61 | } 62 | var Tennis = function() { 63 | this.info = '网球貌似现在还是蛮流行的'; 64 | } 65 | Tennis.prototype = { 66 | constructor:Tennis, 67 | getMember:function() { 68 | console.log('一对一,二对二咯'); 69 | }, 70 | getBallSize:function() { 71 | console.log('网球还是比较小的'); 72 | } 73 | } 74 | 75 | //球类工厂 76 | var sportsFactory = function(name) { 77 | switch (name){ 78 | case 'NBA': 79 | return new Basketball(); 80 | break; 81 | case 'wordCup': 82 | return new Football(); 83 | break; 84 | default : 85 | return new Tennis(); 86 | } 87 | } 88 | ``` 89 | 90 | ## 说明 91 | 92 | 这种简单工厂模式非常的像寄生式继承,只不过这里o没有继承任何对象和类。 93 | 94 | 简单工厂的用途就是创建对象,或者创建相似的对象。比较简单,比较常规,这里不再赘述 -------------------------------------------------------------------------------- /doc/design mode/工厂方法模式.md: -------------------------------------------------------------------------------- 1 | # JavaScript设计模式之工厂方法模式 2 | 3 | > 学习一个东西,一定要明白这个东西的概念是什么,这个东西被提出来的目的是什么 4 | 5 | ## 前言 6 | 在之前,我们介绍过简单工厂设计模式,简单工厂设计模式存在唯一的工厂类,它的优点是所有产品类的实例化集中管理,便于理解。当产品数量较少,且不会经常发生变化时,我们当然可以直接使用简单工厂模式,但是有的时候,需求是在时刻变化的,产品类也可能随之增加,如果使用简单工厂模式,就避免不了去修改工厂类的代码。要解决这个问题,就得使用今天所讲的,工厂方法模式。 7 | 8 | 工厂方法模式本意是将实际创建对象的工作推迟到子类当中。这样核心类就成为了抽象类。 9 | 10 | ## 基本概念 11 | 工厂方法模式:不再有一个唯一的工厂类就创建产品,而是将不同的产品交给对应的工厂子类去实现。每个产品由负责生产的子工厂来创造。如果添加新的产品,需要做的是添加新的子工厂和产品,而不需要修改其他的工厂代码。 12 | 13 | 工厂方法模式主要有三种类组成: 14 | - 抽象工厂类:负责定义创建产品的公共接口 15 | - 产品子工厂:继承抽象工厂类,实现抽象工厂类提供的接口 16 | - 每一种产品各自的产品类 17 | 18 | ## 安全模式类 19 | 安全模式类就是可以屏蔽对类的错误使用而造成的后果。说白了,就是在构造函数开始时先判断当前对象this指向是不是类。 20 | ```javascript 21 | var Demo = function() { 22 | if(!(this instanceof Demo)){ 23 | return new Demo(); 24 | } 25 | } 26 | Demo.prototype.show = function() { 27 | console.log('show') 28 | } 29 | 30 | var d = Demo(); 31 | d.show(); 32 | ``` 33 | ## 工厂方法模式 34 | 简单工厂模式仅仅适用于创建比较少的对象,如果需要创建多个类,并且会经常修改,像我们之前说的简单工厂的方法就不是很实用了,因为如果我要多添加一个类,就需要修改两个地方,所以这里我们采用工厂方法模式 35 | 36 | ```javascript 37 | var Factory = function(type,content) { 38 | if(this instanceof Factory){ 39 | var temp = new this[type](content); 40 | }else{ 41 | return new Factory(type,content); 42 | } 43 | } 44 | //在工厂原型中设置创建所有类型数据对象的基类 45 | Factory.prototype = { 46 | constructor:Factory, 47 | Java:function(content) { 48 | //... 49 | }, 50 | JavaScript:function(content) { 51 | //... 52 | }, 53 | UI:function(content) { 54 | this.content = content; 55 | (function(content) { 56 | var div = document.createElement('div'); 57 | div.innerHTML = content; 58 | div.style.border = '1px solid red'; 59 | document.getElementById(content).appendChild(div); 60 | 61 | })(content) 62 | } 63 | } 64 | ``` 65 | 66 | 如上,我们就可以创建多个类了 67 | 68 | ```javascript 69 | var data = [ 70 | {type:'JavaScript',content:'Javascript还是很重要的'}, 71 | {type:'Java',content:'Java培训哪家强'}, 72 | {type:'UI',content:'UI...'} 73 | ]; 74 | 75 | for(var i = 0,length=data.length;i++;i 学习一个东西,一定要明白这个东西的概念是什么,这个东西被提出来的目的是什么 4 | 5 | ## 概念 6 | 建造者模式:将一个复杂对象的构建层和表示层相分离,同样的构建过程可以采用不同的表示。 7 | 8 | ## 应用场景 9 | 10 | 工厂模式主要是用来创建对象的实例(简单工厂模式,工厂方法模式)或者是类簇(抽象工厂模式),关心的是最终的产出是什么,所以工厂模式我们得到的是对象的实例或者对象的类簇。然而建造者模式在创建对象的过程中则更为复杂一些。虽然目的也是为了创建对象,但是更关心的创建对象的整个过程或者说是每一个细节。 11 | 12 | 比如创建一个人,我们创建的结果不仅仅是得到一个人的实例,还要关注创建人的时候,这个人是男是女,穿什么衣服带什么帽子等等。 13 | 14 | ## 代码演示 15 | ```javascript 16 | var Human = function(param) { 17 | this.skill = param && param.skill || '保密'; 18 | this.hobby = param && param.hobby || '保密'; 19 | } 20 | Human.prototype = { 21 | constructor:Human, 22 | getSill:function() { 23 | return this.skill; 24 | }, 25 | getHobby:function() { 26 | return this.hobby; 27 | } 28 | } 29 | var Name = function(name) { 30 | var that = this; 31 | (function(name,that) { 32 | this.wholeName = name; 33 | if(name.indexOf(' ')>-1){ 34 | that.firstName = name.slice(0,name.indexOf(' ')); 35 | that.secondName = name.slice(name.indexOf(' ')); 36 | } 37 | })(name,that) 38 | } 39 | var Work = function(work) { 40 | var that = this; 41 | (function(work,that) { 42 | switch (work){ 43 | case 'code' : 44 | that.work = '工程师'; 45 | that.wordDesc = '代码是我快乐'; 46 | break; 47 | case 'UE' : 48 | that.work = '设计师'; 49 | that.wordDesc = '设计更似艺术'; 50 | break; 51 | default : 52 | that.work = work; 53 | that.wordDesc = '对不起,我们还不清楚你所选择职位的相关描述'; 54 | } 55 | })(work,that); 56 | } 57 | 58 | //更换期望职位以及描述 59 | Work.prototype.changeWork = function(work) { 60 | this.work = work; 61 | } 62 | 63 | Work.prototype.changeDesc = function(desc) { 64 | this.wordDesc = desc; 65 | } 66 | 67 | //创建一个应聘者 68 | var Person = function(name,work) { 69 | var _person = new Human(); 70 | _person.name = new Name(name); 71 | _person.work = new Work(work); 72 | return _person; 73 | } 74 | 75 | var person = new Person('Neal yang','code'); 76 | console.log(person.skill); 77 | console.log(person.hobby); 78 | console.info(person.work); 79 | person.work.changeDesc('一撸代码就疯狂'); 80 | console.info(person.work); 81 | ``` -------------------------------------------------------------------------------- /doc/dataStructure/queue.md: -------------------------------------------------------------------------------- 1 | # JavaScript数据结构与算法--队列 2 | 3 | ## 定义 4 | 队列是遵循FIFO原则的有序的项。队列在尾部添加新元素,并在顶部移除元素。最新添加的元素必须排列在队列的尾部 5 | 6 | ## 代码实现 7 | 8 | 普通队列 9 | ```javascript 10 | function Queue() { 11 | var items = []; 12 | this.enqueue = function(element) { 13 | items.push(element) 14 | }; 15 | this.dequeue = function() { 16 | return items.shift() 17 | }; 18 | this.front = function() { 19 | return items[0] 20 | }; 21 | this.isEmpty =function() { 22 | return items.length === 0 23 | }; 24 | this.clear = function() { 25 | items = []; 26 | }; 27 | this.size = function() { 28 | return items.length; 29 | }; 30 | this.print = function() { 31 | console.log(items.toString()) 32 | } 33 | } 34 | ``` 35 | 优先队列 36 | ```javascript 37 | function PriorityQueue() { 38 | var items = []; 39 | function QueueElement (element, priority){ 40 | this.element = element; 41 | this.priority = priority; 42 | }; 43 | this.enqueue = function(element, priority){ 44 | var queueElement = new QueueElement(element, priority); 45 | if (this.isEmpty()){ 46 | items.push(queueElement); // {2} 47 | } else { 48 | var added = false; 49 | for (var i=0; i 1){ 74 | for (var i=0; i otherSet.size()){ 68 | return false; 69 | } else { 70 | var values = this.values(); 71 | for (var i=0; i 学习一个东西,一定要明白这个东西的概念是什么,这个东西被提出来的目的是什么 4 | 5 | ## 概念 6 | 7 | 原型模式:用原型实例指向创建对象的类,适用于创建新的对象的类共享原型对象的属性和方法。 8 | 9 | 这种继承是一种基于对属性和方法的共享而不是复制。 10 | 11 | ## 应用场景 12 | 13 | 在创建的类中,存在基类,起定义的方法和属性能够被子类所继承和使用。 14 | 15 | 原型模式就是将可复用的、可共享的、消耗大的从基类中提取出来然后放到原型中,然后子类通过组合继承或者寄生组合式继承将方法和属性继承下来。子类中对于那些需要重写的方法进行重写,这样,子类 16 | 创建的对象既有子类的属性和方法也共享着基类的属性和方法。 17 | 18 | ## 代码演示 19 | 20 | 拿网页中轮播图距离。大概的样子就是轮播图,但是有的是渐变效果,有的是滚动,有的带有箭头。。。所以这里我们可以创建一个基类,轮播图,然后再根据不同的需求再去修改。 21 | 22 | 23 | 24 | ```javascript 1.6 25 | //图片轮播图类 26 | var LoopImages = function(imgArr,container) { 27 | this.imageArr = imgArr;//轮播图片数组 28 | this.container = container;//轮播图片容器 29 | this.createImage = function() { 30 | 31 | };//创建轮播图 32 | this.changeImage = function() { 33 | 34 | }//轮播图切换 35 | } 36 | //上下切换 37 | var SlideLoopImage = function(imgArr,container) { 38 | //构造函数继承图片轮播类 39 | LoopImages.call(this,imgArr,container); 40 | 41 | //重写继承的图片切换方法 42 | this.changeImage = function() { 43 | console.log('上下切换的方式') 44 | } 45 | } 46 | //渐隐切换 47 | var FadeLoopImg = function(imgArr,container,arrow) { 48 | LoopImages.call(this,imgArr,container); 49 | //切换箭头私有变量 50 | this.arrow = arrow; 51 | this.changeImage = function() { 52 | console.log('渐隐的切换方式'); 53 | } 54 | } 55 | 56 | //实例化一个 57 | var fadeImg = new FadeLoopImg(['01.jpg','02.jpg','03.jpg'],'slide',['left.jpg','right.jpg']); 58 | ``` 59 | 60 | 但是如上的写法 ,其实还有一种更加优化的方法。首先看基类,作为基类是要被子类继承的,那么此时将属性和方法都写到基类的构造函数里会有一些问题。 61 | 比如每一次创建子类继承都要创建一次父类,如果父类的构造函数的创建过程中存在很多耗时较长的逻辑,这样的话性能消耗还是蛮大的。为了提高性能,我们可以使用共享机制。 62 | 63 | 对于每一次创建的一些简单而又差异化的属性我们可以放到构造函数中,而把一些消耗资源比较大的方法放到基类的原型中。这样避免很多不必要的消耗。 64 | 65 | ```javascript 1.6 66 | var LoopImages = function(imgArr,container) { 67 | this.imageArr = imgArr; 68 | this.container = container; 69 | } 70 | LoopImages.prototype = { 71 | crateImage : function() { 72 | console.log('创建轮播图方法'); 73 | }, 74 | changeImage:function() { 75 | console.log('图片切换方法'); 76 | } 77 | } 78 | //上下切换 79 | var SlideloopImg = function(imgArr,container) { 80 | LoopImages.call(this,imgArr,container); 81 | } 82 | SlideloopImg.prototype = new LoopImages(); 83 | 84 | SlideloopImg.prototype.changeImage = function() { 85 | console.log('上下切换的方式'); 86 | } 87 | 88 | var FadeLoopImg = function(imgArr,container,arrow) { 89 | this.arrow = arrow; 90 | LoopImages.call(this,imgArr,container); 91 | } 92 | FadeLoopImg.prototype = new LoopImages(); 93 | 94 | FadeLoopImg.prototype.changeImage = function() { 95 | console.log('渐隐切换方式'); 96 | } 97 | ``` -------------------------------------------------------------------------------- /doc/design mode/职责链模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之职责链模式 2 | 3 | ### 概念 4 | 职责链模式:解决请求发送者和接受者之间的耦合,通过职责链上多个对象分解请求流程,实现请求在多个对象之间的传递,直到最后一个对象完成请求的处理。 5 | 6 | 职责链模式的优点是:请求发送者只需要直到链中的第一个节点,从而解耦了发送者和一组接收者之间的强联系。此外,使用了职责链模式之后,链中的节点对象可以灵活地拆分重组,增加或者删除 一个节点,以及改变节点在链中的位置都是轻而易举的。 7 | 8 | 职责链模式的缺点是:首先不能保证某个请求一定会被链中的某个节点处理,这种情况系下可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求。其次,职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分的节点并没有起到实质性的作用,从性能的角度考虑,应当避免过长的职责链带来的性能损耗。 9 | 10 | ### 代码演示 11 | 假设有这么一种场景:一个售卖手机的电商网站,经过分别交纳500元定金和200元定金的两轮预订之后(订单在此时已经生成),现在进入了正式购买阶段。 12 | 13 | 公司针对支付过定金的客户有一定的优惠,正式购买之后,已经支付过500元定金的用户会收到100元优惠券,200元定金的用户可以收到50元优惠券,没有支付过定金的只能进入普通购买方式,也就是没有优惠券。相关的字段有这么几种: 14 | 15 | oederType:订单类型,为1代表500元定金用户,2代表200元定金用户,3为普通购买用户; 16 | 17 | ```javascript 1.6 18 | //职责链模式 19 | var order500=function(orderType,pay,stock){ 20 | if(orderType==1&&pay===true){ 21 | console.log('500元定金预约,得到100元优惠券'); 22 | }else{ 23 | return 'nextSuccessor'; 24 | } 25 | }; 26 | var order200=function(orderType,pay,stock){ 27 | if(orderType===2&&pay===true){ 28 | console.log('200元定金预约,得到50优惠券'); 29 | }else{ 30 | return 'nextSuccessor'; 31 | } 32 | }; 33 | 34 | var orderNormal=function(orderType,pay,stock){ 35 | if(stock>0){ 36 | console.log('普通购买,无优惠券'); 37 | }else{ 38 | return 'nextSuccessor'; 39 | } 40 | }; 41 | ``` 42 | 43 | 接下来需要把函数包装进职责链节点: 44 | 45 | ```javascript 1.6 46 | //职责链包装 47 | 48 | var Chain=function(fn){ 49 | this.fn=fn; 50 | this.nextSuccessor=null; 51 | }; 52 | 53 | Chain.prototype.setNextSuccessor=function(successor){ 54 | return this.nextSuccessor=successor; 55 | }; 56 | 57 | Chain.prototype.passRequest=function(){ 58 | var ret=this.fn.apply(this,arguments); 59 | 60 | if(ret=='nextSuccessor'){ 61 | //console.log(this.nextSuccessor.fn.name); 62 | return this.nextSuccessor&&this.nextSuccessor.passRequest.apply(this.nextSuccessor,arguments);//启动这一步启动递归了 63 | } 64 | return ret; 65 | }; 66 | 67 | 68 | //测试 69 | var chainOrder500=new Chain(order500); 70 | var chainOrder200=new Chain(order200); 71 | var chainOrderNormal=new Chain(orderNormal); 72 | 73 | chainOrder500.setNextSuccessor(chainOrder200); 74 | chainOrder200.setNextSuccessor(chainOrderNormal); 75 | 76 | 77 | //将请求传递给第一个节点即可 78 | chainOrder500.passRequest(1,true,100);//输出 500元定金,得到100元优惠券 79 | chainOrder500.passRequest(2,true,100);//输出200元定金,得到50元优惠券 80 | chainOrder500.passRequest(1,false,0); //输出 手机库存不足 81 | ``` 82 | -------------------------------------------------------------------------------- /doc/design mode/适配器模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之适配器模式 2 | 3 | ### 概念 4 | 适配器模式:将一个类的接口转换为另外一个类的接口以满足用户的需求,使类之间的接口不兼容问题通过适配器得以解决。 5 | 6 | ### 代码演示 7 | 书中这里说的比价没意思,这里我拿汤姆大叔的例子来说下 8 | 9 | 我们来举一个例子,鸭子(Dock)有飞(fly)和嘎嘎叫(quack)的行为,而火鸡虽然也有飞(fly)的行为,但是其叫声是咯咯的(gobble)。如果你非要火鸡也要实现嘎嘎叫(quack)这个动作,那我们可以复用鸭子的quack方法,但是具体的叫还应该是咯咯的,此时,我们就可以创建一个火鸡的适配器,以便让火鸡也支持quack方法,其内部还是要调用gobble。 10 | 11 | 12 | 首先要先定义鸭子和火鸡的抽象行为,也就是各自的方法函数: 13 | ```javascript 1.6 14 | //鸭子 15 | var Duck = function(){ 16 | 17 | }; 18 | Duck.prototype.fly = function(){ 19 | throw new Error("该方法必须被重写!"); 20 | }; 21 | Duck.prototype.quack = function(){ 22 | throw new Error("该方法必须被重写!"); 23 | } 24 | 25 | //火鸡 26 | var Turkey = function(){ 27 | 28 | }; 29 | Turkey.prototype.fly = function(){ 30 | throw new Error(" 该方法必须被重写 !"); 31 | }; 32 | Turkey.prototype.gobble = function(){ 33 | throw new Error(" 该方法必须被重写 !"); 34 | }; 35 | 36 | 37 | //鸭子 38 | var MallardDuck = function () { 39 | Duck.apply(this); 40 | }; 41 | MallardDuck.prototype = new Duck(); //原型是Duck 42 | MallardDuck.prototype.fly = function () { 43 | console.log("可以飞翔很长的距离!"); 44 | }; 45 | MallardDuck.prototype.quack = function () { 46 | console.log("嘎嘎!嘎嘎!"); 47 | }; 48 | 49 | //火鸡 50 | var WildTurkey = function () { 51 | Turkey.apply(this); 52 | }; 53 | WildTurkey.prototype = new Turkey(); //原型是Turkey 54 | WildTurkey.prototype.fly = function () { 55 | console.log("飞翔的距离貌似有点短!"); 56 | }; 57 | WildTurkey.prototype.gobble = function () { 58 | console.log("咯咯!咯咯!"); 59 | }; 60 | ``` 61 | 62 | 为了让火鸡也支持quack方法,我们创建了一个新的火鸡适配器TurkeyAdapter: 63 | 64 | ```javascript 1.6 65 | var TurkeyAdapter = function(oTurkey){ 66 | Duck.apply(this); 67 | this.oTurkey = oTurkey; 68 | }; 69 | TurkeyAdapter.prototype = new Duck(); 70 | TurkeyAdapter.prototype.quack = function(){ 71 | this.oTurkey.gobble(); 72 | }; 73 | TurkeyAdapter.prototype.fly = function(){ 74 | var nFly = 0; 75 | var nLenFly = 5; 76 | for(; nFly < nLenFly;){ 77 | this.oTurkey.fly(); 78 | nFly = nFly + 1; 79 | } 80 | }; 81 | ``` 82 | 83 | 该构造函数接受一个火鸡的实例对象,然后使用Duck进行apply,其适配器原型是Duck,然后要重新修改其原型的quack方法,以便内部调用oTurkey.gobble()方法。其fly方法也做了一些改变,让火鸡连续飞5次(内部也是调用自身的oTurkey.fly()方法)。 84 | 85 | ```javascript 1.6 86 | var oMallardDuck = new MallardDuck(); 87 | var oWildTurkey = new WildTurkey(); 88 | var oTurkeyAdapter = new TurkeyAdapter(oWildTurkey); 89 | 90 | //原有的鸭子行为 91 | oMallardDuck.fly(); 92 | oMallardDuck.quack(); 93 | 94 | //原有的火鸡行为 95 | oWildTurkey.fly(); 96 | oWildTurkey.gobble(); 97 | 98 | //适配器火鸡的行为(火鸡调用鸭子的方法名称) 99 | oTurkeyAdapter.fly(); 100 | oTurkeyAdapter.quack(); 101 | ``` -------------------------------------------------------------------------------- /doc/design mode/抽象工厂模式.md: -------------------------------------------------------------------------------- 1 | # JavaScript设计模式之抽象工厂模式 2 | > 学习一个东西,一定要明白这个东西的概念是什么,这个东西被提出来的目的是什么 3 | 4 | ## 概念 5 | 通过对类的工厂抽象使其业务用于对产品类簇的创建,而不负责创建某一类产品的实例。也就是说我们要创建一个抽象类。这也是面向对象的开发语言中一种很常见的开发模式。 6 | 7 | ## 应用场景 8 | ```javascript 9 | var Car = function() {}; 10 | Car.prototype.getPrice = function() { 11 | return new Error('抽象方法不能调用,需自行实现'); 12 | }; 13 | Car.prototype.getSpeed = function() { 14 | return new Error('抽象方法不能调用,需自行实现'); 15 | } 16 | ``` 17 | 18 | 由于JavaScript在没有abstract的具体实现,所以我们需要如上手动实现,也即是在创建这个类的时候,要求使用这些方法,我们需要手动去重写它,而不能继承使用。因为 19 | 在大型的应用中,总有一些子类去继承一些父类,这些父类经常会定义一些必要的方法,但是不会具体的去实现,会去要求子类自行实现。如果子类没有重写这些方法而去调用他,就会报错。 20 | 21 | 22 | ## 代码演示 23 | ```javascript 24 | // 抽象工厂方法 25 | var VehicleFactory = function(subType,superType) { 26 | //判断抽象工厂中是否有该抽象类 27 | if(typeof VehicleFactory[superType] === 'function'){ 28 | //缓存类 29 | function F() {}; 30 | //继承父类属性和方法 31 | F.prototype = new VehicleFactory[superType](); 32 | //将子类的constructor指向子类 33 | subType.constructor = subType; 34 | //子类原型继承父类 35 | subType.prototype = new F(); 36 | }else{ 37 | return new Error('未创建该抽象类'); 38 | } 39 | } 40 | 41 | //小汽车抽象类 42 | VehicleFactory.Car = function() { 43 | this.type = 'car'; 44 | } 45 | VehicleFactory.Car.prototype = { 46 | getPrice:function() { 47 | return new Error('抽象方法不能调用'); 48 | }, 49 | getSpeed:function() { 50 | return new Error('抽象方法不能调用'); 51 | } 52 | }; 53 | //公共汽车抽象类 54 | VehicleFactory.Bus = function() { 55 | this.type = 'Bus'; 56 | } 57 | VehicleFactory.Bus.prototype = { 58 | getPrice:function() { 59 | return new Error('抽象方法不能调用'); 60 | }, 61 | getSpeed:function() { 62 | return new Error('抽象方法不能调用'); 63 | } 64 | }; 65 | //大卡车抽象类 66 | VehicleFactory.Trunk = function() { 67 | this.type = 'Trunk'; 68 | } 69 | VehicleFactory.Trunk.prototype = { 70 | getPrice:function() { 71 | return new Error('抽象方法不能调用'); 72 | }, 73 | getSpeed:function() { 74 | return new Error('抽象方法不能调用'); 75 | } 76 | }; 77 | //子类 78 | var BMW = function(price,speed) { 79 | this.price = price; 80 | this.speed = speed; 81 | } 82 | VehicleFactory(BMW,'Car'); 83 | BMW.prototype.getPrice = function() { 84 | return this.price; 85 | } 86 | BMW.prototype.getSpeed = function() { 87 | return this.speed; 88 | } 89 | 90 | //... 91 | 92 | var three = new BMW('35w','200'); 93 | three.getSpeed(); 94 | console.log(three.getPrice()) 95 | ``` 96 | 97 | 所以从上,我们可以看出,抽象工厂其实是实现子类继承父类的方法,在这个方法里,我们需要传递子类以及需要被继承的父类的名称,并且在抽象工厂方法中,又增加了一次对抽象类存在性的一次判断,然后通过寄生式继承,在继承中我们是通过new关键字复制了父类的一个实例,因为我们不仅仅需要继承父类原型上的方法,还需要继承父类的属性。所以通过new关键字将父类的构造函数执行一遍来复制父类构造函数中的属性和方法。 98 | 99 | 通过抽象工厂,我们就能知道每一个子类到底是哪一种类别了。 100 | 101 | 同时注意,抽象类中定义的方法这是显示定义一些功能,但是没有具体的实现,而一个对象是应该具备一套完整的功能的。所以用抽象类创建的对象当然也是抽象的。所以我们还不能直接用它来创建类的实例。 -------------------------------------------------------------------------------- /doc/basic_js/JavaScript中的执行上下文和变量对象.md: -------------------------------------------------------------------------------- 1 | # JavaScript中的执行上下文和变量对象 2 | 3 | > 最近在整理JavaScript中的一些基础知识,文章总结与各种书籍、博客。ps:汤姆大叔的深入系列非常好,非常推荐。真大神 4 | 5 | ## EC执行上下文 6 | 7 | ### 定义 8 | 每次当控制器转到ECMAScript可执行代码的时候,即会进入到一个执行上下文。执行上下文(简称-EC)是ECMA-262标准里的一个抽象概念,用于同可执行代码(executable code)概念进行区分。 9 | 10 | 标准规范没有从技术实现的角度定义EC的准确类型和结构,这应该是具体实现ECMAScript引擎时要考虑的问题。 11 | 12 | 13 | 活动的执行上下文组在逻辑上组成一个堆栈。堆栈底部永远都是全局上下文(global context),而顶部就是当前(活动的)执行上下文。堆栈在EC类型进入和退出上下文的时候被修改(推入或弹出)。 14 | 15 | JavaScript中,EC分为三种 16 | - 全局级别的代码 –– 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境 17 | - 函数级别的代码 ––当执行一个函数时,运行函数体中的代码。 18 | - Eval的代码 –– 在Eval函数内运行的代码。 19 | 20 | EC建立分为两个阶段:进入执行上下文(创建阶段)和执行阶段(激活/执行代码) 21 | 22 | #### 进入上下文阶段 23 | 发生在函数调用时,但是在执行具体代码之前(比如,对函数参数进行具体化之前) 24 | 25 | 创建作用域链(Scope Chain) 26 | 27 | 创建变量,函数和参数。 28 | 29 | 求”this“的值。 30 | 31 | #### 执行代码阶段 32 | 变量赋值 33 | 34 | 函数引用 35 | 36 | 解释/执行其他代码。 37 | 38 | 我们可以将EC看做是一个对象。 39 | ```javascript 40 | EC={ 41 | VO:{/*变量对象, 函数中的arguments对象, 参数, 内部的变量以及函数声明 */}, 42 | this:{}, 43 | Scope:{ /* VO以及所有父执行上下文中的VO */} 44 | } 45 | ``` 46 | ![](../../img/20151118102648527.jpg) 47 | 48 | ### 全局代码 49 | 这种类型的代码是在"程序"级处理的:例如加载外部的js文件或者本地标签内的代码。全局代码不包括任何function体内的代码。 50 | 51 | 在初始化(程序启动)阶段,ECStack是这样的: 52 | ```javascript 53 | ECStack = [ 54 | globalContext 55 | ]; 56 | ``` 57 | ### 函数代码 58 | 当进入funtion函数代码(所有类型的funtions)的时候,ECStack被压入新元素。需要注意的是,具体的函数代码不包括内部函数(inner functions)代码。如下所示,我们使函数自己调自己的方式递归一次: 59 | ```javascript 60 | (function foo(bar) { 61 | if (bar) { 62 | return; 63 | } 64 | foo(true); 65 | })(); 66 | ``` 67 | 那么,ECStack以如下方式被改变: 68 | ```javascript 69 | // 第一次foo的激活调用 70 | ECStack = [ 71 | functionContext 72 | globalContext 73 | ]; 74 | 75 | // foo的递归激活调用 76 | ECStack = [ 77 | functionContext – recursively 78 | functionContext 79 | globalContext 80 | ]; 81 | ``` 82 | 每次return的时候,都会退出当前执行上下文的,相应地ECStack就会弹出,栈指针会自动移动位置,这是一个典型的堆栈实现方式。一个抛出的异常如果没被截获的话也有可能从一个或多个执行上下文退出。相关代码执行完以后,ECStack只会包含全局上下文(global context),一直到整个应用程序结束。 83 | ### Eval代码 84 | 直接看代码 85 | ```javascript 86 | eval('var x = 10'); 87 | 88 | (function foo() { 89 | eval('var y = 20'); 90 | })(); 91 | 92 | alert(x); // 10 93 | alert(y); // "y" 提示没有声明 94 | ``` 95 | ECStack的变化过程: 96 | ```javascript 97 | ECStack = [ 98 | globalContext 99 | ]; 100 | 101 | // eval('var x = 10'); 102 | ECStack.push( 103 | evalContext, 104 | callingContext: globalContext 105 | ); 106 | 107 | // eval exited context 108 | ECStack.pop(); 109 | 110 | // foo funciton call 111 | ECStack.push( functionContext); 112 | 113 | // eval('var y = 20'); 114 | ECStack.push( 115 | evalContext, 116 | callingContext: functionContext 117 | ); 118 | 119 | // return from eval 120 | ECStack.pop(); 121 | 122 | // return from foo 123 | ECStack.pop(); 124 | ``` 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /doc/design mode/组合模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之组合模式 2 | 3 | ### 概念 4 | 组合模式:又称部分-整体模式,将对象组合成树形结构以表示成“部分整体”的层次结构。组合模式使得用户对单个对象以及组合对象的使用具有一致性 5 | 6 | ### 使用场景 7 | 我们平时开发过程中,一定会遇到这种情况:同时处理简单对象和由简单对象组成的复杂对象,这些简单对象和复杂对象会组合成树形结构,在客户端对其处理的时候要保持一致性。比如电商网站中的产品订单,每一张产品订单可能有多个子订单组合,比如操作系统的文件夹,每个文件夹有多个子文件夹或文件,我们作为用户对其进行复制,删除等操作时,不管是文件夹还是文件,对我们操作者来说是一样的。在这种场景下,就非常适合使用组合模式来实现。 8 | 9 | 组合模式主要有三个角色: 10 | 11 | (1)抽象组件(Component):抽象类,主要定义了参与组合的对象的公共接口 12 | 13 | (2)子对象(Leaf):组成组合对象的最基本对象 14 | 15 | (3)组合对象(Composite):由子对象组合起来的复杂对象 16 | 17 | 理解组合模式的关键是要理解组合模式对单个对象和组合对象使用的一致性,我们接下来说说组合模式的实现加深理解。 18 | 19 | ### 代码演示 20 | ```javascript 1.6 21 | // 抽象一个虚拟父类 22 | var News = function() { 23 | this.children = []; 24 | this.element = null; 25 | } 26 | 27 | News.prototype = { 28 | init:function() { 29 | throw new Error('请重写你的方法'); 30 | }, 31 | add:function() { 32 | throw new Error('请重写你的方法'); 33 | }, 34 | getElement:function() { 35 | throw new Error('请重写你的方法'); 36 | }, 37 | } 38 | 39 | function iniheritObject(o) { 40 | function F() {} 41 | F.prototype = o; 42 | return new F(); 43 | } 44 | 45 | function inheritPrototype(subClass,superClass) { 46 | var p = iniheritObject(superClass.prototype); 47 | p.constructor = subClass; 48 | subClass.prototype = p; 49 | } 50 | //容器类 51 | var Container = function(id,parent) { 52 | News.call(this); 53 | this.id = id; 54 | this.parent = parent; 55 | this.init(); 56 | } 57 | 58 | //寄生式继承父类原型方法 59 | inheritPrototype(Container,News); 60 | 61 | Container.prototype.init = function() { 62 | this.element = document.createElement('ul'); 63 | this.element.id = this.id; 64 | this.element.className = 'new-container'; 65 | } 66 | 67 | Container.prototype.add = function(child) { 68 | this.children.push(child); 69 | this.element.appendChild(child.getElement()); 70 | return this; 71 | } 72 | 73 | Container.prototype.getElement = function() { 74 | return this.element; 75 | } 76 | 77 | Container.prototype.show = function() { 78 | this.parent.appendChild(this.element) 79 | } 80 | //同样下一层极的行成员集合类以及后面新闻组合体类 81 | var Item = function(classname) { 82 | News.call(this); 83 | this.classname = classname; 84 | this.init(); 85 | } 86 | inheritPrototype(Item,News); 87 | Item.prototype.init = function() { 88 | this.element = document.createElement('li'); 89 | this.element.className = this.classname; 90 | } 91 | Item.prototype.add = function(child) { 92 | this.children.push(child); 93 | this.element.appendChild(child.getElement()); 94 | return this; 95 | } 96 | Item.prototype.getElement = function() { 97 | return this.element; 98 | } 99 | 100 | var NewsGroup = function(className) { 101 | News.call(this); 102 | this.classname = classname|| ''; 103 | this.init(); 104 | } 105 | inheritPrototype(NewsGroup,News); 106 | NewsGroup.prototype.init = function() { 107 | this.element = document.createElement('div'); 108 | this.element.className = this.classname; 109 | } 110 | NewsGroup.prototype.add = function(child) { 111 | this.children.push(child); 112 | this.element.appendChild(child.getElement()); 113 | return this; 114 | } 115 | NewsGroup.prototype.getElement = function() { 116 | return this.element; 117 | } 118 | 119 | ``` 120 | 121 | 所以后面我们在使用的时候,创建新闻类,利用之前定义的组合元素去组合就可以了。 -------------------------------------------------------------------------------- /doc/design mode/享元模式.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式之享元模式 2 | 3 | ### 概念 4 | 享元模式:运用共享技术有效的支持大量细粒度对象,避免对象之间拥有相同内容造成的不必要开销 5 | 6 | 主要用来优化程序的性能,适合解决大量类似的对象产生的性能问题。享元模式通过分析应用程序的对象,将其解析为内在数据和外在数据,减少对象数量,从而提高程序的性能。 7 | 8 | 9 | ### 基础知识 10 | 享元模式通过共享大量的细粒度的对象,减少对象的数量,从而减少对象的内存,提高应用程序的性能。其基本思想就是分解现有类似对象的组成,将其展开为可以共享的内在数据和不可共享的外在数据,我们称内在数据的对象为享元对象。通常还需要一个工厂类来维护内在数据。 11 | 12 | 在JS中,享元模式主要有下面几个角色组成: 13 | - 客户端:用来调用享元工厂来获取内在数据的类,通常是应用程序所需的对象 14 | - 享元工厂:用来维护享元数据的类 15 | - 享元类:保持内在数据的类 16 | 17 | 18 | ### 基本实现 19 | 我们举个例子进行说明:苹果公司批量生产iphone,iphone的大部分数据比如型号,屏幕都是一样,少数部分数据比如内存有分16G,32G等。未使用享元模式前,我们写代码如下: 20 | 21 | function Iphone(model, screen, memory, SN) { 22 | this. model = model; 23 | this.screen = screen; 24 | this.memory = memory; 25 | this.SN = SN; 26 | } 27 | var phones = []; 28 | for (var i = 0; i < 1000000; i++) { 29 | var memory = i % 2 == 0 ? 16 : 32; 30 | phones.push(new Iphone("iphone6s", 5.0, memory, i)); 31 | } 32 | 33 | 这段代码中,创建了一百万个iphone,每个iphone都独立申请一个内存。但是我们仔细观察可以看到,大部分iphone都是类似的,只是内存和序列号不一样,如果是一个对性能要求比较高的程序,我们就要考虑去优化它。 34 | 大量相似对象的程序,我们就可以考虑用享元模式去优化它,我们分析出大部分的iphone的型号,屏幕,内存都是一样的,那这部分数据就可以公用,就是享元模式中的内在数据,定义享元类如下: 35 | 36 | function IphoneFlyweight(model, screen, memory) { 37 | this.model = model; 38 | this.screen = screen; 39 | this.memory = memory; 40 | } 41 | 42 | 我们定义了iphone的享元类,其中包含型号,屏幕和内存三个数据。我们还需要一个享元工厂来维护这些数据: 43 | 44 | var flyweightFactory = (function () { 45 | var iphones = {}; 46 | return { 47 | get: function (model, screen, memory) { 48 | var key = model + screen + memory; 49 | if (!iphones[key]) { 50 | iphones[key] = new IphoneFlyweight(model, screen, memory); 51 | } 52 | return iphones[key]; 53 | } 54 | }; 55 | })(); 56 | 57 | 在这个工厂中,我们定义了一个字典来保存享元对象,提供一个方法根据参数来获取享元对象,如果字典中有则直接返回,没有则创建一个返回。 58 | 接着我们创建一个客户端类,这个客户端类就是修改自iphone类: 59 | 60 | function Iphone(model, screen, memory, SN) { 61 | this.flyweight = flyweightFactory.get(model, screen, memory); 62 | this.SN = SN; 63 | } 64 | 65 | 然后我们依旧像之间那样生成多个iphone 66 | 67 | var phones = []; 68 | for (var i = 0; i < 1000000; i++) { 69 | var memory = i % 2 == 0 ? 16 : 32; 70 | phones.push(new Iphone("iphone6s", 5.0, memory, i)); 71 | } 72 | console.log(phones); 73 | 74 | 这里的关键就在于Iphone构造函数里面的this.flyweight = flyweightFactory.get(model, screen, memory)。这句代码通过享元工厂去获取享元数据,而在享元工厂里面,如果已经存在相同数据的对象则会直接返回对象,多个iphone对象共享这部分相同的数据,所以原本类似的数据已经大大减少,减少的内存的占用。 75 | 76 | ### 在DOM中的使用 77 | 78 | 86 | 87 | 点击菜单项,进行相应的操作,我们通过jQuery来绑定事件,一般会这么做: 88 | 89 | $(".item").on("click", function () { 90 | console.log($(this).text()); 91 | }) 92 | 93 | 给每个列表项绑定事件,点击输出相应的文本。这样看暂时没有什么问题,但是如果是一个很长的列表,尤其是在移动端特别长的列表时,就会有性能问题,因为每个项都绑定了事件,都占用了内存。但是这些事件处理程序其实都是很类似的,我们就要对其优化。 94 | 95 | $(".menu").on("click", ".item", function () { 96 | console.log($(this).text()); 97 | }) 98 | 99 | 通过这种方式进行事件绑定,可以减少事件处理程序的数量,这种方式叫做事件委托,也是运用了享元模式的原理。事件处理程序是公用的内在部分,每个菜单项各自的文本就是外在部分。我们简单说下事件委托的原理:点击菜单项,事件会从li元素冒泡到ul元素,我们绑定事件到ul上,实际上就绑定了一个事件,然后通过事件参数event里面的target来判断点击的具体是哪一个元素,比如低级第一个li元素,event.target就是li,这样就能拿到具体的点击元素了,就可以根据不同元素进行不同的处理。 100 | 101 | 102 | > 参考地址:http://luopq.com/2015/11/20/design-pattern-flyweight/ -------------------------------------------------------------------------------- /doc/basic_js/夯实JS系列--变量、作用域和内存问题.md: -------------------------------------------------------------------------------- 1 | # 夯实JS系列--变量、作用域和内存问题 2 | >最近在忙于写一个react+node的全栈博客demo,没有时间更新文章。但是还是觉得这样一忙起来不更新是不应该的。正好在空闲上下班地铁上都会再去细读js原生知识。所以打算整理、总结、系统性的分享给大家。 3 | 4 | ## 基本类型和引用类型 5 | 在ECMAScript中,变量分为基本类型和引用类型两种。 6 | 基本类型就是存储简单的数据段。而引用类型指的是那些可能由多个值构成的对象。 7 | 在ECMAScript中,基本类型包括:Undefined、Null、Boolean、Number和String。 8 | 这些基本类型的对象都是按值访问的。所以js中我们可以直接操作他们。 9 | 但是引用类型如Object等,是按照引用来操作的。并非直接操作其值。 10 | 并且我们可以动态的为引用类型变量添加属性和方法。而基本类型则不可以。 11 | 12 | ## 变量赋值和传参 13 | 这里其实对于基本类型来说没有什么需要重点说明的。这里就重点说下引用类型吧 14 | 15 | 对于赋值 16 | ```javascript 17 | function setName(obj) { 18 | obj.name = "Neal"; 19 | obj = new Object(); 20 | obj.name = "yang"; 21 | } 22 | var person = new Object(); 23 | setName(person); 24 | console.log(person.name); 25 | ``` 26 | 如上代码,最后console出来的是Neal。 27 | 28 | 这段代码说明两点: 29 | - 引用类型在传参的时候,是按照引用传递的,不然不可能person.name为Neal 30 | - 即使在函数内部修改了参数的值。原始的引用依然不变。实际上,在重写obj的时候,这个变量的引用已经是一个局部变量了。只是在这儿函数运行完,这个对象被销毁了。 31 | 32 | 所以说到这,对于对象的赋值,一句以概之:引用的赋值。 33 | 34 | ## 执行环境及其作用域 35 | > 这大概是一个非常基础也是重要的部分,后续会在进阶里面详细展开。 36 | 37 | 执行环境定义了变量或者函数有权访问的其他数据,决定了他们的行为。每一个执行环境都有一个与之关联的变量对象(如global、window)。环境中定义的所有变量和函数都保存在这个对象中。 38 | 39 | 某一个执行环境执行完毕后,该环境会被销毁。其中的所有的变量和函数也将随之销毁。全局执行环境知道应用程序退出才被销毁(如关闭网页等) 40 | 41 | 当代码在一个环境中执行的时候,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的变量和函数的有序访问。作用域链的前端,始终是当前执行的代码所在的 42 | 环境的变量对象。全局执行环境始终是作用域链的最后一个对象。 43 | 44 | 标识符的解析也就是沿着作用域链一级一级的搜索的过程。搜索过程从作用域链的前端开始,然后逐级向后回溯。知道找到标识符为止。 45 | 46 | ```javascript 47 | var color = 'red'; 48 | function changeColor() { 49 | var anotherColor = 'blue'; 50 | function swapColors() { 51 | var tempColor = anotherColor; 52 | anotherColor = color; 53 | color = tempColor; 54 | //这个执行环境中可以访问到 tempColor color antherColor 55 | } 56 | //这里只能访问anotherColor color 57 | swapColors(); 58 | } 59 | changeColor();//这里只能访问color 60 | ``` 61 | 62 | 所以从上面代码我们可以感受到:内部环境可以通过作用域链访问到外部环境的变量。反之不可。这些环境之间的联系都是线性、有次序的。 63 | 64 | ### 延长作用域链 65 | 虽然执行环境的类型只有两种。局部的和全局的。但是还有一种方法可以延长作用域链。 66 | 67 | 这是因为有些语句可以在作用域链的前端临时添加一个变量对象,改变量对象会在代码执行后被移除。 68 | 69 | - try-catch 语句中的catch 70 | - with语句 71 | 72 | 对于width语句而言,会将指定的对象添加到作用域链中。对于catch语句而言,会创建一个新的变量对象,其中包含被抛出的错误对象的申明。 73 | 74 | 关于作用域、环境之类的话题后续会再细说。这里作为基础篇,就先介绍到这里。 75 | 76 | ## 垃圾收集 77 | 78 | 很开心~js不需要你来收拾垃圾!好~此篇完结! 79 | 80 | 好吧~虽然我们不收拾垃圾,但是也是要稍微了解下js是如何收拾垃圾的。 81 | 82 | 首先什么是垃圾:哪些不再被继续使用的变量都是垃圾。什么叫收拾?释放起垃圾所占用的空间即为释放。 83 | 84 | 局部变量只在函数执行过过程中存在。而在这个过程中,会为局部变量在栈或者堆中分配相应的内存空间(存值呗)。然后函数执行啦,用了这些变量,执行完啦。完啦!则这些变量就没有用了。没用了,则为垃圾,既需清理。 85 | 86 | 但是并非所有的情况下都这么容易的得出结论。垃圾收集器必须跟踪哪个变量用了哪个变量没用。对于不在利用的打上标记,已被将来收回其所占用的内存。 87 | 88 | ### 标记清除 89 | 这是最为常用一种清除方式。当一个变量进入到环境的时候,标记为‘进入环境’,这个基本是不会被清除的,因为执行流进入到相应的环境的时候可能会用到。当变量离开环境的时候,标记为‘离开环境’。 90 | 91 | 可以使用任何方式来标记。我们要知道是如何标记不重要,重要的是采用什么策略。 92 | 93 | 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。他会去掉环境中的变量以及被环境中的变量所引用的变量的标记。剩下的,则视为嫌疑人,准备删除。因为环境中的变量已经无法访问到这些变量了。目前IE、ff 、 opera 、 chrome都是这种标记清除方式 94 | 95 | ### 引用计数 96 | > 因为不常用,简单说下 97 | 98 | 引用计数的意思就是跟踪记录每一个值被引用的次数。当一个引用类型的变量复制给一个变量的时候,这个引用次数则+1,如果有别复制给另一个变量,则再+1,如果包含对这个值的引用的变量又被赋值了别的值。则这个值-1. 99 | 100 | 当引用次数为0的时候,为垃圾~回收! 101 | 102 | 为什么不常用呢?看着也很清晰啊! 103 | 104 | look code: 105 | ```javascript 106 | function test() { 107 | var objectA = new Object(); 108 | var objectB = new Object(); 109 | 110 | objectA.someOtherObject = objectB; 111 | objectB.someOtherObject = objectA; 112 | } 113 | ``` 114 | 如上,对象A和对象B的属性互相引用。也就是说,这两个对象的引用次数永远都是2.哪怕这个函数执行完咯,也没法清理的。对的,这就是bug~ 115 | 116 | ## 节制点~你懂得 117 | 虽然垃圾回收机制帮我们做了很多事,但是电脑分配给浏览器的可用内存通常要比桌面应用的内存要小的多,毕竟是为了防止运行js的网页耗尽所有的内存而导致系统崩溃的问题发生。 118 | 119 | 所以我们确保用最少的内存可以让页面获取最好的性能,最佳的执行方案就是执行中的代码都是有必要的数据。就好比用最低的经济拿最多的人头一样,一旦经济不够,技术弥补!一旦数据不要用了,自己主动扫除。 120 | 121 | ```javascript 122 | function createPerson(name) { 123 | var localPerson = new Object(); 124 | localPerson.name = name; 125 | return localPerson; 126 | } 127 | var neal = createPerson('Neal'); 128 | 129 | //主动清理垃圾 130 | createPerson = null; 131 | ``` 132 | 133 | 这里讲createPerson设置为null,并没有就把他给清除了,只是释放了他的引用。让其脱离其执行环境,以便于垃圾收集器更快的将其回收。 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YOU-SHOULD-KNOW-JS 2 | something about js that you should know or want to know 3 | 4 | # 移步隔壁:[Personal blog](https://github.com/Nealyang/PersonalBlog) 5 | 6 | 7 | #### | javascript重点总结 8 | 9 | - you-should-know:[编写高质量代码基本要点](./doc/basic_js/编写高质量代码基本要点.md) 10 | - you-should-know:[变量、作用域和内存问题](./doc/basic_js/夯实JS系列--变量、作用域和内存问题.md) 11 | - you-should-know:[面向对象最全总结](./doc/basic_js/prototype-based.md) 12 | - you-should-know:[忍者级别的操作函数](./doc/basic_js/忍者级别的操作函数.md) 13 | - you-should-know:[谈谈闭包](./doc/basic_js/谈谈闭包.md) 14 | - you-should-know:[This is this](./doc/basic_js/彻底明白this指向.md) 15 | - you-should-know:[原型与原型链](./doc/basic_js/原型和原型链.md) 16 | - you-should-know:[JavaScript中的执行上下文和变量对象](./doc/basic_js/JavaScript中的执行上下文和变量对象.md) 17 | - you-should-know:[JavaScript中的跨域总结](./doc/basic_js/JavaScript中的跨域总结.md) 18 | - you-should-know:JavaScript中的不老事件 19 | - you-should-know:关于DOM操作 20 | - you-should-know:解析JavaScript中的对象 21 | - you-should-know:解析JavaScript中的行为委托 22 | - you-should-know:别忘记了AJAX 23 | 24 | #### | javascript数据结构和算法 25 | - [x] [数组](doc/dataStructure/array.md) 26 | - [x] [栈](doc/dataStructure/stack.md) 27 | - [x] [队列](doc/dataStructure/queue.md) 28 | - [x] [链表](doc/dataStructure/linkedList.md) 29 | - [x] [集合](doc/dataStructure/Set.md) 30 | - [x] [字典和散列表](doc/dataStructure/Map&HashMap.md) 31 | - [x] [树](doc/dataStructure/tree.md) 32 | - [ ] 图 33 | 34 | - [ ] 排序和搜索算法 35 | 36 | 37 | #### | javascript设计模式 38 | 39 | - [x] [面向对象](./doc/design%20mode/面向对象.md) 40 | ###### | 创建型设计模式 41 | - [x] [简单工厂模式](./doc/design%20mode/简单工厂设计模式.md) 42 | - [x] [工厂方法模式](./doc/design%20mode/工厂方法模式.md) 43 | - [x] [抽象工厂模式](./doc/design%20mode/抽象工厂模式.md) 44 | - [x] [建造者模式](./doc/design%20mode/建造者模式.md) 45 | - [x] [原型模式](./doc/design%20mode/原型模式.md) 46 | - [x] [单例模式](./doc/design%20mode/单例模式.md) 47 | ###### | 结构型设计模式 48 | - [x] [外观模式](./doc/design%20mode/外观模式.md) 49 | - [x] [适配器模式](./doc/design%20mode/适配器模式.md) 50 | - [x] [代理模式](./doc/design%20mode/代理模式.md) 51 | - [x] [装饰者模式](./doc/design%20mode/装饰着模式.md) 52 | - [x] [桥接模式](./doc/design%20mode/桥接模式.md) 53 | - [x] [组合模式](./doc/design%20mode/组合模式.md) 54 | - [x] [享元模式](./doc/design%20mode/享元模式.md) 55 | ###### | 行为型设计模式 56 | - [x] [模板方法模式](./doc/design%20mode/模板方法模式.md) 57 | - [x] [观察者模式](./doc/design%20mode/观察者模式.md) 58 | - [x] [状态模式](./doc/design%20mode/状态模式.md) 59 | - [x] [策略模式](./doc/design%20mode/策略模式.md) 60 | - [x] [职责链模式](./doc/design%20mode/职责链模式.md) 61 | - [x] [命令模式](./doc/design%20mode/命令模式.md) 62 | - [x] [访问者模式](./doc/design%20mode/访问者模式.md) 63 | - [x] [中介者模式](./doc/design%20mode/中介者模式.md) 64 | - [x] [备忘录模式](./doc/design%20mode/备忘录模式.md) 65 | - [x] [迭代器模式](./doc/design%20mode/迭代器模式.md) 66 | - [x] [解释器模式](./doc/design%20mode/解释器模式.md) 67 | ###### | 技巧型设计模式 68 | - [ ] 链模式 69 | - [ ] 委托模式 70 | - [ ] 数据访问对象模式 71 | - [ ] 节流模式 72 | - [ ] 简单模板模式 73 | - [ ] 惰性模式 74 | - [ ] 参与者模式 75 | - [ ] 等待者模式 76 | ###### | 架构型设计模式 77 | - [ ] 同步模块模式 78 | - [ ] 异步模块模式 79 | - [ ] Widget模式 80 | - [ ] MVC模式 81 | - [ ] MVP模式 82 | - [ ] MVVM模式 83 | 84 | 85 | 86 | #### | 是的,我也搞了ES6 87 | 88 | - [x] [let、const命令](https://github.com/Nealyang/ES6_pratice) 89 | - [x] [变量的解构赋值](https://github.com/Nealyang/ES6_pratice) 90 | - [x] [字符串的扩展](https://github.com/Nealyang/ES6_pratice) 91 | - [x] [数值的扩展](https://github.com/Nealyang/ES6_pratice) 92 | - [x] [数组的扩展](https://github.com/Nealyang/ES6_pratice) 93 | - [x] [函数的扩展](https://github.com/Nealyang/ES6_pratice) 94 | - [x] [对象的扩展](https://github.com/Nealyang/ES6_pratice) 95 | - [x] [.Symbol](https://github.com/Nealyang/ES6_pratice) 96 | - [x] [.Proxy 和 Reflect](https://github.com/Nealyang/ES6_pratice) 97 | - [x] [二进制数组](https://github.com/Nealyang/ES6_pratice) 98 | - [x] [.Set 和 Map 数据结构](https://github.com/Nealyang/ES6_pratice) 99 | - [x] [.Iterator和for...of循环](https://github.com/Nealyang/ES6_pratice) 100 | - [x] [.Promise对象](https://github.com/Nealyang/ES6_pratice) 101 | - [x] [.异步操作和Async函数](https://github.com/Nealyang/ES6_pratice) 102 | - [x] [.Class](https://github.com/Nealyang/ES6_pratice) 103 | - [x] [.修饰器](https://github.com/Nealyang/ES6_pratice) 104 | - [x] [.Module](https://github.com/Nealyang/ES6_pratice) 105 | - [x] [.编程风格](https://github.com/Nealyang/ES6_pratice) 106 | - [x] [读懂 ECMAScript 规格](https://github.com/Nealyang/ES6_pratice) 107 | 108 | 109 | ## 交流(QQ群) 110 | 111 | 前端技术杂谈: 604953717 112 | 113 | React技术栈:398240621 114 | 115 | nodejs技术1群:209530601 116 | 117 | -------------------------------------------------------------------------------- /doc/basic_js/原型和原型链.md: -------------------------------------------------------------------------------- 1 | 2 | # 原型与原型链 3 | 4 | 5 | ## 原型 6 | 原型其实是一个比较简单比较容易理解的东西。如果你看过我之前写的面向对象的文章的话,我想你对原型肯定有了一个比价清晰的认识。 7 | 但是这里,我们还是拿出来说一下。 8 | 9 | 原型其实就是一个对象,其他的对象可以通过它来实现属性的继承。而且,任何一个对象,都可以成为原型。而且所有的对象,默认情况下都有一个原型。 10 | 毕竟原型本身也就是对象,所以每一个原型自身也有其自身的原型。出了一个例外,那就是原型链上顶端的男人。也就是null。 11 | 12 | 一个对象的真正原型是被对象内部[[Prototype]]所持有,ECMA引入了标准对象原型访问器,Object.getPropertyOf(object) 13 | ```javascript 14 | var proto = {}; 15 | var obj = Object.create(proto); 16 | Object.getPrototypeOf(obj) === proto; // true 17 | ``` 18 | 当然,我们也可以通过非标准的访问器__proto__来获取实例的原型。 19 | 20 | 原型的真正魅力在于多个实例之间公用一个通用原型的时候,原型对象的属性一旦被定义,就可以被多个引用他的实例所继承。这种操作在性能和维护方面的意义也是不言而喻的。 21 | 22 | ### 不使用原型的例子 23 | ```javascript 24 | var decimalDigits = 2, 25 | tax = 5; 26 | 27 | function add(x, y) { 28 | return x + y; 29 | } 30 | 31 | function subtract(x, y) { 32 | return x - y; 33 | } 34 | 35 | //alert(add(1, 3)); 36 | ``` 37 | 下面我们来通过原型美化一下 38 | 39 | ### 原型使用方式一 40 | ```javascript 41 | var Calculator = function (decimalDigits, tax) { 42 | this.decimalDigits = decimalDigits; 43 | this.tax = tax; 44 | }; 45 | Calculator.prototype = { 46 | add: function (x, y) { 47 | return x + y; 48 | }, 49 | 50 | subtract: function (x, y) { 51 | return x - y; 52 | } 53 | }; 54 | //alert((new Calculator()).add(1, 3)); 55 | ``` 56 | ### 原型使用方式二 57 | ```javascript 58 | Calculator.prototype = function () { 59 | add = function (x, y) { 60 | return x + y; 61 | }, 62 | 63 | subtract = function (x, y) { 64 | return x - y; 65 | } 66 | return { 67 | add: add, 68 | subtract: subtract 69 | } 70 | } (); 71 | 72 | //alert((new Calculator()).add(11, 3)); 73 | ``` 74 | 很明显方式二的写法更好一点,因为它封装了function,通过return形式暴露出简单的使用名称,打到了private/public的效果 75 | 76 | 当然,我们也可以分步声明。其实说白了,原型式对象,所以我们当然可以像给对象添加属性的方式那样来添加原型上的方法 77 | 78 | ```javascript 79 | var BaseCalculator = function () { 80 | //为每个实例都声明一个小数位数 81 | this.decimalDigits = 2; 82 | }; 83 | 84 | //使用原型给BaseCalculator扩展2个对象方法 85 | BaseCalculator.prototype.add = function (x, y) { 86 | return x + y; 87 | }; 88 | 89 | BaseCalculator.prototype.subtract = function (x, y) { 90 | return x - y; 91 | }; 92 | ``` 93 | ### 重写原型 94 | 在使用第三方JS类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或function,我们可以通过继续声明的同样的add代码的形式来达到覆盖重写前面的add功能,代码如下: 95 | ```javascript 96 | //覆盖前面Calculator的add() function 97 | Calculator.prototype.add = function (x, y) { 98 | return x + y + this.tax; 99 | }; 100 | 101 | var calc = new Calculator(); 102 | alert(calc.add(1, 1)); 103 | ``` 104 | 但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码。 105 | 106 | ## 原型链 107 | ```javascript 108 | function Foo() { 109 | this.value = [1,2,4]; 110 | } 111 | Foo.prototype = { 112 | method: function() {} 113 | }; 114 | 115 | function Bar() {} 116 | 117 | // 设置Bar的prototype属性为Foo的实例对象 118 | Bar.prototype = new Foo(); 119 | Bar.prototype.foo = 'Hello World'; 120 | 121 | // 修正Bar.prototype.constructor为Bar本身 122 | Bar.prototype.constructor = Bar; 123 | 124 | var test = new Bar() // 创建Bar的一个新实例 125 | 126 | // 原型链 127 | test [Bar的实例] 128 | Bar.prototype [Foo的实例] 129 | { foo: 'Hello World' } 130 | Foo.prototype 131 | {method: ...}; 132 | Object.prototype 133 | {toString: ... /* etc. */}; 134 | ``` 135 | 上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此,它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。 136 | 137 | ### hasOwnProperty 138 | 139 | hasOwnProperty是Object.prototype的一个方法,它可是个好东西,他能判断一个对象是否包含自定义属性而不是原型链上的属性,因为hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。 140 | ```javascript 141 | // 修改Object.prototype 142 | Object.prototype.bar = 1; 143 | var foo = {goo: undefined}; 144 | 145 | foo.bar; // 1 146 | 'bar' in foo; // true 147 | 148 | foo.hasOwnProperty('bar'); // false 149 | foo.hasOwnProperty('goo'); // true 150 | ``` 151 | 152 | 只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 没有其它方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。 153 | 154 | 但有个恶心的地方是:JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性,就需要使用外部的 hasOwnProperty 函数来获取正确的结果。 155 | ```javascript 156 | var foo = { 157 | hasOwnProperty: function() { 158 | return false; 159 | }, 160 | bar: 'Here be dragons' 161 | }; 162 | 163 | foo.hasOwnProperty('bar'); // 总是返回 false 164 | 165 | // 使用{}对象的 hasOwnProperty,并将其上下为设置为foo 166 | {}.hasOwnProperty.call(foo, 'bar'); // true 167 | 168 | ``` 169 | 170 | 当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法,这将会避免原型对象扩展带来的干扰。例子比较多,这里就不举例了。 -------------------------------------------------------------------------------- /doc/dataStructure/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @authors Nealyang(nealyang231@gmail.com) 4 | * @date 2017/12/26 5 | * @version 1.0.0 6 | */ 7 | 8 | function BinarySearchTree() { 9 | var Node = function (key) { 10 | this.key = key; 11 | this.left = null; 12 | this.right = null; 13 | }; 14 | 15 | function insertNode(node, newNode) { 16 | if (newNode.key < node.key) { 17 | if (node.left) { 18 | node.left = newNode; 19 | } else { 20 | insertNode(node.left, newNode); 21 | } 22 | } else { 23 | node.right ? insertNode(node.right, newNode) : node.right = newNode; 24 | } 25 | } 26 | 27 | 28 | var root = null; 29 | 30 | this.insert = function (key) { 31 | var newNode = new Node(key); 32 | if (root == null) { 33 | root = newNode; 34 | } else { 35 | insertNode(root, newNode); 36 | } 37 | }; 38 | 39 | //遍历二叉树,中序遍历(inOrderTraverse) 先序遍历(preOrderTraverse) 后序遍历(postOrderTraverse) 40 | function inOrderTraverseNode(root, callBack) { 41 | if (!root) { 42 | inOrderTraverseNode(root.left, callBack); 43 | callBack(root.key); 44 | inOrderTraverseNode(root.right, callBack); 45 | } 46 | }; 47 | 48 | function preOrderTraverseNode(root, callBack) { 49 | if (!root) { 50 | callBack(root.key); 51 | inOrderTraverseNode(root.left, callBack); 52 | inOrderTraverseNode(root.right, callBack); 53 | } 54 | }; 55 | 56 | function postOrderTraverseNode(root, callBack) { 57 | if (!root) { 58 | inOrderTraverseNode(root.left, callBack); 59 | inOrderTraverseNode(root.right, callBack); 60 | callBack(root.key); 61 | } 62 | }; 63 | this.inOrderTraverse = function (callBack) { 64 | inOrderTraverseNode(root, callBack); 65 | }; 66 | 67 | function preOrderTraverse(callBack) { 68 | preOrderTraverseNode(root, callBack); 69 | }; 70 | 71 | function postOrderTraverse(callBack) { 72 | postOrderTraverseNode(root, callBack); 73 | }; 74 | 75 | var minNode = function (node) { 76 | if (node) { 77 | while (node && node.left !== null) { 78 | node = node.left; 79 | return node.key; 80 | } 81 | return null; 82 | } 83 | }; 84 | var findMinNode = function (node) { 85 | if (node) { 86 | while (node && node.left !== null) { 87 | node = node.left; 88 | return node; 89 | } 90 | return null; 91 | } 92 | }; 93 | this.min = function () { 94 | return minNode(root); 95 | }; 96 | var maxNode = function (node) { 97 | if (node) { 98 | while (node && node.right !== null) { 99 | node = node.right; 100 | } 101 | return node.key; 102 | } 103 | return null; 104 | }; 105 | this.max = function () { 106 | return maxNode(root); 107 | }; 108 | var searchNode = function (node, key) { 109 | if (node === null) { 110 | return false; 111 | } 112 | if (key < node.key) { 113 | return searchNode(node.left, key); 114 | } else if (key > node.key) { 115 | return searchNode(node.right, key); 116 | } else { 117 | return true; 118 | } 119 | }; 120 | this.search = function (key) { 121 | return searchNode(root, key); 122 | }; 123 | 124 | this.remove = function (key) { 125 | root = removeNode(root, key); 126 | }; 127 | var removeNode = function (node, key) { 128 | if (node) { 129 | if (key < node.key) { 130 | node.left = removeNode(node.left, key); 131 | return node; 132 | } else if (key > node.right) { 133 | node.right = removeNode(node.right, key); 134 | return node; 135 | }else { 136 | //叶节点 137 | if(node.left === null && node.right === null){ 138 | node = null; 139 | return node; 140 | }else if(node.left === null){ 141 | node = node.right; 142 | return node; 143 | }else if(node.right == null){ 144 | node = node.left; 145 | return node; 146 | } 147 | //有两个子节点 148 | var aux = findMinNode(node.right); 149 | node.key = aux.key; 150 | node.right = removeNode(node.right, aux.key); 151 | return node; 152 | } 153 | 154 | } else { 155 | return null; 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /doc/dataStructure/array.md: -------------------------------------------------------------------------------- 1 | # JavaScript数据结构与算法--数组 2 | ## 创建和初始化数组 3 | ```javascript 4 | var daysOfWeek = new Array(); //{1} 5 | var daysOfWeek = new Array(7); //{2} 6 | var daysOfWeek = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); //{3} 7 | var daysOfWeek = []; 8 | var daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 9 | 'Thursday', 'Friday', 'Saturday']; 10 | ``` 11 | 因为数组这东西都用烂了,所以简单直接过例子。求斐波拉契数列前二十个数字 12 | ```javascript 13 | var fibonacci = []; //{1} 14 | fibonacci[1] = 1; //{2} 15 | fibonacci[2] = 1; //{3} 16 | for(var i = 3; i < 20; i++){ 17 | fibonacci[i] = fibonacci[i-1] + fibonacci[i-2]; ////{4} 18 | } 19 | for(var i = 1; i b.age){ 159 | return 1 160 | } 161 | return 0; 162 | } 163 | console.log(friends.sort(comparePerson)); 164 | 165 | ``` 166 | ## 结束语 167 | 168 | 数组这块比较简单,都是大家常用的东西,这里对于方法就不做过多演示了。 169 | 170 | 171 | -------------------------------------------------------------------------------- /doc/dataStructure/tree.md: -------------------------------------------------------------------------------- 1 | ## JavaScript设计模式与算法--树 2 | 3 | ### 概念 4 | 非顺序数据结构我们之前学习的有散列表,现在,我们接着学习另一个非顺序数据结构,树。树是一种分层数据的抽象模型。现实生活中最常见的树的例子是家谱,或是公司的组织架构图 5 | 6 | 位于树顶部的节点叫做根节点,它没有父节点。节点分为内部节点和外部节点,至少有一个子节点的节点成为内部节点,一个子节点也没有的成为外部节点也叫做叶节点。 7 | 8 | 一个节点可以有祖先和后代,一个节点(除了根节点)的祖先包括父节点、祖父节点、曾祖 父节点等。一个节点的后代包括子节点、孙子节点、曾孙节点等。 9 | 10 | 节点的一个属性是深度,节点的深度取决于它的祖先节点的数量。树的高度取决于所有节点深度的最大值。 11 | 12 | ### 二叉树和二叉搜索树 13 | 二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。这些定 义有助于我们写出更高效的向/从树中插入、查找和删除节点的算法。二叉树在计算机科学中的 应用非常广泛。 14 | 15 | 二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值, 在右侧节点存储(比父节点)大(或者等于)的值。 16 | 17 | 而我们今天主要研究的就是二叉搜索树 18 | 19 | ### 代码实现 20 | 21 | ```javascript 22 | function BinarySearchTree() { 23 | var Node = function (key) { 24 | this.key = key; 25 | this.left = null; 26 | this.right = null; 27 | }; 28 | 29 | function insertNode(node, newNode) { 30 | if (newNode.key < node.key) { 31 | if (node.left) { 32 | node.left = newNode; 33 | } else { 34 | insertNode(node.left, newNode); 35 | } 36 | } else { 37 | node.right ? insertNode(node.right, newNode) : node.right = newNode; 38 | } 39 | } 40 | 41 | 42 | var root = null; 43 | 44 | this.insert = function (key) { 45 | var newNode = new Node(key); 46 | if (root == null) { 47 | root = newNode; 48 | } else { 49 | insertNode(root, newNode); 50 | } 51 | }; 52 | 53 | //遍历二叉树,中序遍历(inOrderTraverse) 先序遍历(preOrderTraverse) 后序遍历(postOrderTraverse) 54 | function inOrderTraverseNode(root, callBack) { 55 | if (!root) { 56 | inOrderTraverseNode(root.left, callBack); 57 | callBack(root.key); 58 | inOrderTraverseNode(root.right, callBack); 59 | } 60 | }; 61 | 62 | function preOrderTraverseNode(root, callBack) { 63 | if (!root) { 64 | callBack(root.key); 65 | inOrderTraverseNode(root.left, callBack); 66 | inOrderTraverseNode(root.right, callBack); 67 | } 68 | }; 69 | 70 | function postOrderTraverseNode(root, callBack) { 71 | if (!root) { 72 | inOrderTraverseNode(root.left, callBack); 73 | inOrderTraverseNode(root.right, callBack); 74 | callBack(root.key); 75 | } 76 | }; 77 | this.inOrderTraverse = function (callBack) { 78 | inOrderTraverseNode(root, callBack); 79 | }; 80 | 81 | function preOrderTraverse(callBack) { 82 | preOrderTraverseNode(root, callBack); 83 | }; 84 | 85 | function postOrderTraverse(callBack) { 86 | postOrderTraverseNode(root, callBack); 87 | }; 88 | 89 | var minNode = function (node) { 90 | if (node) { 91 | while (node && node.left !== null) { 92 | node = node.left; 93 | return node.key; 94 | } 95 | return null; 96 | } 97 | }; 98 | var findMinNode = function (node) { 99 | if (node) { 100 | while (node && node.left !== null) { 101 | node = node.left; 102 | return node; 103 | } 104 | return null; 105 | } 106 | }; 107 | this.min = function () { 108 | return minNode(root); 109 | }; 110 | var maxNode = function (node) { 111 | if (node) { 112 | while (node && node.right !== null) { 113 | node = node.right; 114 | } 115 | return node.key; 116 | } 117 | return null; 118 | }; 119 | this.max = function () { 120 | return maxNode(root); 121 | }; 122 | var searchNode = function (node, key) { 123 | if (node === null) { 124 | return false; 125 | } 126 | if (key < node.key) { 127 | return searchNode(node.left, key); 128 | } else if (key > node.key) { 129 | return searchNode(node.right, key); 130 | } else { 131 | return true; 132 | } 133 | }; 134 | this.search = function (key) { 135 | return searchNode(root, key); 136 | }; 137 | 138 | this.remove = function (key) { 139 | root = removeNode(root, key); 140 | }; 141 | var removeNode = function (node, key) { 142 | if (node) { 143 | if (key < node.key) { 144 | node.left = removeNode(node.left, key); 145 | return node; 146 | } else if (key > node.right) { 147 | node.right = removeNode(node.right, key); 148 | return node; 149 | }else { 150 | //叶节点 151 | if(node.left === null && node.right === null){ 152 | node = null; 153 | return node; 154 | }else if(node.left === null){ 155 | node = node.right; 156 | return node; 157 | }else if(node.right == null){ 158 | node = node.left; 159 | return node; 160 | } 161 | //有两个子节点 162 | var aux = findMinNode(node.right); 163 | node.key = aux.key; 164 | node.right = removeNode(node.right, aux.key); 165 | return node; 166 | } 167 | 168 | } else { 169 | return null; 170 | } 171 | } 172 | } 173 | ``` 174 | 175 | -------------------------------------------------------------------------------- /doc/basic_js/谈谈闭包.md: -------------------------------------------------------------------------------- 1 | # 妈妈再也不用担心面试官问我闭包了 2 | 3 | > 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScript上卷》 4 | 5 | > 安利个人react技术栈+express+mongoose实战个人博客教程 [React-Express-Blog-Demo](https://github.com/Nealyang/React-Express-Blog-Demo) 6 | 7 | ## 前言 8 | 为什么我们需要理解并且掌握闭包,且不说大道理,就问你要不要成为JavaScript高手?不要?那你要不要面试找工作嘛。。。 9 | 10 | 再者,对于任何一个前端er或者JavaScript开发者来说,理解闭包可以看做是另一种意义上的重生。闭包是纯函数编程语言的一个特性,因为他大大简化复杂的操作,所以很容易在一些JavaScript库以及其他高级代码中找到闭包的使用。 11 | 12 | 一言以蔽之,闭包,你就得掌握。 13 | 14 | ## 谈谈闭包之前,我们先说说作用域 15 | 这里我们要说的作用域值得是词法作用域。词法作用域即为定义在词法阶段的作用域。换句话说,就是你写代码时将变量和块作用域写在哪里所决定的。因此在词法解析的时会保持作用域不变。(JavaScript引擎在运行JavaScript代码的时候大致经过分词/词法分析、解析/语法分析、代码生成三个步骤)。 16 | 17 | 老规矩,看代码(就是代码多~~) 18 | 19 | ```javascript 20 | function foo(a) { 21 | var b = a*2; 22 | function bar(c) { 23 | console.log(a,b,c); 24 | } 25 | bar(b*3); 26 | } 27 | foo(2);//2 4 6 28 | ``` 29 | 这个例子中有三个逐级嵌套的作用域,如图: 30 | ![](../../img/171105_01.png) 31 | ***截图来自《你不知道的JavaScript》*** 32 | 33 | 部分一包含整个作用域也就是全局作用域。其中包含标识符:foo 34 | 35 | 部分二包含foo所创建的作用域,其中包含:a,bar和b 36 | 37 | 部分三包含bar所创建的作用域,其中包含:c 38 | 39 | 这些作用域气泡的包含关系给引擎提供了足够多的位置信息。在上面的代码中,引擎执行console.log的时候,并查找a,b,c。他首先在最里面的作用域,也就是bar(...)函数的作用域。引擎无法在这一层作用域中找到变量a,因此引擎会去上一级嵌套作用域foo(...)中查找,如果找到了,则即使用。 40 | 41 | 如果a,c 都存在作用域bar(...),foo(...)作用域中,console.log(...)即不需要到foo的外部作用域中去查找变量。 42 | 43 | 无论函数在哪里被调用,且无论他们如何被调用,他的词法作用域都只由函数被声明的位置决定的。词法作用域查找只会查找一级标识符,比如a,b和c。 44 | 45 | 简单理解词法作用域的概念,其实也就是我们常说的作用域,关于JavaScript中欺骗词法以及更多关于词法作用域的介绍,请翻阅《你不知道的JavaScript》。 46 | 47 | ## 闭包的概念 48 | 49 | 说到闭包的概念,这里还真的比较模糊,我们且看下各种经典书籍给出的概念 50 | 51 | ### 《JavaScript权威指南》中的概念 52 | 函数对象可以通过作用域链互相关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学中成为闭包 53 | 54 | ### 《JavaScript权威指南》中的概念 55 | 闭包是指有权访问另一个函数作用域中的变量的函数。 56 | 57 | ### 《JavaScript忍者秘籍》中的概念 58 | 闭包是一个函数在创建时允许该自身函数访问并操作该自身函数以外的变量时所创建的作用域。 59 | 60 | ### 《你不知道的JavaScript》中的概念 61 | 闭包是基于词法作用域书写代码时所产生的自然结果。当函数记住并访问所在的词法作用域,闭包就产生了。 62 | 63 | ### 个人理解 64 | 闭包就是一个函数,一个可以访问并操作其他函数内部变量的函数。也可以说是一个定义在函数内部的函数。因为JavaScript没有动态作用域,而闭包的本质是静态作用域(静态作用域规则查找一个变量声明时依赖的是源程序中块之间的静态关系),所以函数访问的都是我们定义时候的作用域,也就是词法作用域。所以闭包才会得以实现。 65 | 66 | 我们常见的闭包形式就是a 函数套 b 函数,然后 a 函数返回 b 函数,这样 b 函数在 a 函数以外的地方执行时,依然能访问 a 函数的作用域。其中“b 函数在 a 函数以外的地方执行时”这一点,才体现了闭包的真正的强大之处。 67 | 68 | 69 | 70 | ## 实质性的问题 71 | 72 | ```javascript 73 | function outer() { 74 | var a = 2; 75 | function inner() { 76 | console.log(a);//2 77 | } 78 | inner(); 79 | } 80 | outer(); 81 | ``` 82 | 基于词法作用域和查找规则,inner函数是可以访问到outer内部定义的变量a的。从技术上讲,这就是闭包。但是也可以说不是,因为用来解释inner对a的引用方法是词法作用域的查找规则,而这些规则只是闭包中的一部分而已。 83 | 84 | 下面我们将上面的代码修改下,让我们能够清晰的看到闭包 85 | ```javascript 86 | function outer() { 87 | var a = 2; 88 | function inner() { 89 | console.log(a); 90 | } 91 | return inner; 92 | } 93 | var neal = outer(); 94 | neal();//2 95 | ``` 96 | 可能是所有讲解闭包的博客中都用烂了的例子了。这里inner函数被正常调用执行,并且可以访问到outer函数里定义的变量a。讲道理,在outer函数运行后,通常函数整个内部作用域都会被销毁。 97 | 98 | 而闭包的神奇之处正是如此可以阻止垃圾回收这种事情的发生,事实上,内部作用域已然存在且拿着a变量,所以没有被回收。inner函数拥有outer函数内部作用域的闭包,使得该作用域能够一直存活,以供inner函数在之后的任何时间可以访问。 99 | 100 | inner()已然持有对该作用域的引用,而这个引用就被叫做闭包。 101 | 102 | 函数在定义时的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的词法作用域。 103 | 104 | ***无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包*** 105 | ```javascript 106 | var fn; 107 | function foo() { 108 | var a = 2; 109 | function baz() { 110 | console.log(a); 111 | } 112 | fn = baz; 113 | } 114 | 115 | function bar() { 116 | fn(); 117 | } 118 | foo(); 119 | bar(); 120 | ``` 121 | 122 | 上面的代码不做过多解释,挺简单,通过下面的代码,我们再说下闭包的三个有趣的概念 123 | 124 | ```javascript 125 | var outerValue = 'ninja'; 126 | var later; 127 | function outFunction() { 128 | var innerValue = 'Neal'; 129 | function innerFunction(param){ 130 | console.log(outerValue,innerValue,param,tooLate); 131 | } 132 | later = innerFunction; 133 | } 134 | console.log('tooLate is',tooLate); 135 | outFunction(); 136 | later('Nealyang'); 137 | var tooLate = 'Haha'; 138 | later('Neal_yang'); 139 | 140 | //tooLate is undefined 141 | //ninja Neal Nealyang undefined 142 | //ninja Neal Neal_yang Haha 143 | ``` 144 | 上面代码运行结果大家可以自行尝试。总之,从上面的代码中,我们可以看到闭包的有趣的三个概念 145 | - 内部函数的参数包含在闭包中 146 | - 作用域之外的所有变量、即便是函数声明之后的那些声明,也都包含在闭包中. 147 | - 相同作用域内,尚未声明的变量,不能进行提前引用 148 | 149 | ## 代码处处有闭包 150 | 151 | ```javascript 152 | function wait(message) { 153 | setTimeout( function timer() { 154 | console.log( message ); }, 1000 ); } 155 | wait( "Hello, closure!" ); 156 | ``` 157 | 如上的代码,一个很常见的定时器,但是timer函数具有涵盖wait作用域的闭包,因为此还保留对变量Message的引用。 158 | 159 | wait执行1s后,他的内部作用域并不会消失,timer函数依然保持有wait作用域的闭包。 160 | 161 | 深入到引擎内部原理中,内置的g工具函数setTimeout持有对一个参数的引用,引擎调用这个函数,在例子中就是内部的timer函数,而词法作用域在这个过程中保持完整。这就是闭包。 162 | 163 | 无论何时何地,如果将函数作为第一级值类型并到处传递,你就会看到闭包在这些函数中的使用。在定时器、事件监听、Ajax请求、跨窗口通信或者其他异步任务中,只要使用回调函数,就在使用闭包。 164 | 165 | ## 在经典的for循环中使用闭包 166 | 167 | ```javascript 168 | for (var i=1; i<=5; i++) { 169 | setTimeout( function timer() { 170 | console.log( i ); }, i*1000 ); } 171 | ``` 172 | 173 | 如上for循环,大家都知道输出6,毕竟这个作用域中,我们只有一个i,所有的回调函数都是在这个for循环结束以后才执行的。 174 | 175 | 如果我们试图假设循环中的每一个迭代在运行时都会给自己捕获一个i的副本,但是根据作用域的工作原理,尽管循环中五个函数是在各个迭代中分别定义,但是他们都被封闭在共享的作用域中,因此还是只有一个i。 176 | 177 | 所以回到正题,我们需要使用闭包,在每一个循环中每一个迭代都让他产生一个闭包作用域。 178 | 179 | 所以我们代码修改如下: 180 | ```javascript 181 | for (var i=1; i<=5; i++) { (function() { 182 | setTimeout( function timer() { 183 | console.log( i ); }, i*1000 ); })(); } 184 | ``` 185 | 186 | but!!!你也发现了,这样并不姓,不是IIFE会产生一个闭包的么?是的没错,但是如果这个IIFE产生的闭包作用域是可空的,那么将它封装起来又有什么意义呢?所以它需要点实质性的东西,让我们去使用。 187 | 188 | ```javascript 189 | for (var i=1; i<=5; i++) { (function(j) { 190 | setTimeout( function timer() { 191 | console.log( j ); }, j*1000 ); })( i ); } 192 | ``` 193 | 194 | 当然,如上问题我们可以使用es6中的let来解决。但是这里就不做过多说明了。大家可以自行Google。 195 | 196 | ## 模块 197 | 这个部分比较简单好理解,因为闭包可以很好形成块级作用域,对内部变量有很好的隐藏。所以自然我们可以将其作为模块开发的手段。撇开如今的export、import不谈 198 | 199 | 直接看例子就好,操作比较常规 200 | 201 | ````javascript 202 | function foo() { 203 | var something = "cool"; 204 | var another = [1, 2, 3]; 205 | function doSomething() { 206 | console.log( something ); 207 | } 208 | function doAnother() { 209 | console.log( another.join( " ! " ) ); 210 | } 211 | return { 212 | doSomething:doSomething, 213 | doAnother:doAnother 214 | } 215 | } 216 | ```` 217 | 218 | 简单说明下,doSomething和doAnother函数具有涵盖模块实例内部作用域的闭包。当通过返回一个含有属性引用的对象的方式来将函数传递到词法作用域外部,我们已经创造了可以观察和实践的 闭包条件。 219 | 220 | - 必须有外部的封闭函数,该函数必须至少被调用一次 221 | - 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态。 222 | 223 | 当然,上面的代码我们还可以写成IIFE的形式。但是毕竟市场上讲解闭包的好文是在太多,这里我们就点到为止。 224 | 225 | -------------------------------------------------------------------------------- /doc/dataStructure/Map&HashMap.md: -------------------------------------------------------------------------------- 1 | ## JavaScript数据结构与算法--字典和散列表 2 | 3 | ### 字典 4 | 5 | 集合表示一组互不相同的元素(不重复的元素)。在字典中,存储的是[键,值] 对,其中键名是用来查询特定元素的。字典和集合很相似,集合以[值,值]的形式存储元素,字 典则是以[键,值]的形式来存储元素。字典也称作映射。 6 | 7 | 当我们Set内部使用对象去实现的时候,其实和字典也就非常的类似了。 8 | 9 | ### 代码演示 10 | ```javascript 11 | function Dictionary() { 12 | var items = {}; 13 | this.has = function (key) { 14 | return items.hasOwnProperty(key); 15 | }; 16 | this.set = function (key,value) { 17 | if(this.has(key)) return false; //确保唯一性 18 | items[key] = value; 19 | return true 20 | }; 21 | this.remove = function (key) { 22 | if(this.has(key)){ 23 | delete items[key]; 24 | return true; 25 | }else{ 26 | return false 27 | } 28 | }; 29 | function get(key) { 30 | return this.has(key) ? items[key] : undefined; 31 | } 32 | this.clear = function(){ 33 | items = {}; 34 | }; 35 | this.size = function(){ 36 | return Object.keys(items).length; 37 | }; 38 | this.values = function(){ 39 | var values = []; 40 | for (var k in items) { 41 | if (this.has(k)) { 42 | values.push(items[k]); 43 | } 44 | } 45 | return values; 46 | }; 47 | } 48 | ``` 49 | 50 | ### 散列表 51 | 52 | 散列算法的作用是尽可能快地在数据结构中找到一个值。在之前的章节中,你已经知道如果 要在数据结构中获得一个值(使用get方法),需要遍历整个数据结构来找到它。如果使用散列 函数,就知道值的具体位置,因此能够快速检索到该值。散列函数的作用是给定一个键值,然后 返回值在表中的地址。 53 | 54 | #### 代码演示 55 | 56 | ```javascript 57 | function HashTable() { 58 | var table = []; 59 | var loseloseHashCode = function (key) { 60 | var hash = 0; 61 | for(var i = 0,length = key.lenght;i-1 && position<=length){ 37 | var node = new Node(element), 38 | current = head, 39 | previous, 40 | index = 0; 41 | if(position === 0){ 42 | head = node; 43 | node.next = head; 44 | }else{ 45 | while (index++ < position){ 46 | previous = current; 47 | current = current.next; 48 | } 49 | node.next = current; 50 | previous.next = node; 51 | } 52 | }else{ 53 | return false; 54 | } 55 | }; 56 | this.removeAt = function (position) { 57 | if(position>-1 && position-1 && position<=length){ 144 | var node = new Node(element), 145 | current = head, 146 | previous, 147 | index = 0; 148 | if(position === 0){ 149 | if(!head){ 150 | head = node; 151 | tail = node; 152 | }else{ 153 | node.next = current; 154 | current.prev = node; 155 | head = node; 156 | } 157 | }else if(position === length){ 158 | current = tail; 159 | current.next = node; 160 | node.prev = current; 161 | tail = node; 162 | }else{ 163 | while (index++-1 && position 汤姆大叔真大牛 4 | 5 | 在我们日常编码中,编写可维护代码是非常重要的,编写可维护代码,意味着: 6 | - 可读性 7 | - 一致性 8 | - 可预测性 9 | - 团队代码风格一致 10 | 11 | ## 最小全局变量 12 | JavaScript是通过函数来管理作用域的。在函数内部申明的变量只能在函数内部使用,在函数外部不能使用。每一个JavaScript都有一个全局对象,当你在任意的函数外部访问this的时候可以访问到,你创建的每一个全局变量都成为这个全局对象的属性。浏览器中,这个全局对象便是window。 13 | 14 | ```javascript 15 | myglobal = 'Hello'; 16 | console.log(myglobal); 17 | console.log(window.myglobal); 18 | console.log(window['myglobal']); 19 | console.log(this.myglobal); 20 | ``` 21 | ## 全局变量的问题 22 | 全局变量最大的问题就是变量名冲突,造成不可以预计的后果。而在web页面中包含不是开发者缩写的代码也很常见 23 | - 第三方JavaScript库 24 | - 广告或者统计脚本 25 | - 不同类型的组件 26 | 27 | 所以一旦出现明明冲突了,那么这就gg了。所以尽可能的少用全局变量是非常有必要的。当然,我们有很多减少全局变量的策略,但是我觉得最主要的就是使用使用var来声明变量。当然,es6中的let和const出来后,var基本可以拜拜了,但是这里我们还是要讨论下var的 28 | 29 | 由于JavaScript的两个特征,不自觉的创建变量是出乎意料的容易,首先,你可以甚至不需要声明就可以使用变量;第二,JavaScript有隐含的全局概念,意味着你不声明的任何变量都会成为一个全局对象属性。 30 | ```javascript 31 | function sum(x, y) { 32 | // 不推荐写法: 隐式全局变量 33 | result = x + y; 34 | return result; 35 | } 36 | ``` 37 | 38 | 另一个创建隐式全局变量的反例就是使用任务链进行部分var声明。下面的片段中,a是本地变量但是b确实全局变量,这可能不是你希望发生的 39 | 40 | ```javascript 41 | // 反例,勿使用 42 | function foo() { 43 | var a = b = 0; 44 | // ... 45 | } 46 | ``` 47 | 48 | ## 忘记var的副作用 49 | 隐式全局变量和明确定义的全局变量间有些小的差异,就是通过delete操作符让变量未定义的能力。 50 | - 通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的 51 | - 无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的 52 | 53 | 这表明,在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete操作符删除的,而变量是不能的 54 | 55 | ## 单var形式 56 | 在函数顶部使用单var语句是比较有用的一种形式,其好处在于 57 | 58 | - 提供了一个单一的地方去寻找功能所需要的所有局部变量 59 | - 防止变量在定义之前使用的逻辑错误 60 | - 帮助你记住声明的全局变量,因此较少了全局变量 61 | - 少代码(类型啊传值啊单线完成) 62 | 63 | ```javascript 64 | function func() { 65 | var a = 1, 66 | b = 2, 67 | sum = a + b, 68 | myobject = {}, 69 | i, 70 | j; 71 | // function body... 72 | } 73 | ``` 74 | 75 | ## 预解析:var散布的问题 76 | JavaScript中,你可以在函数的任何位置声明多个var语句,并且它们就好像是在函数顶部声明一样发挥作用,这种行为称为 hoisting。当你使用了一个变量,然后不久在函数中又重新声明的话,就可能产生逻辑错误。对于JavaScript,只 要你的变量是在同一个作用域中(同一函数),它都被当做是声明的,即使是它在var声明前使用的时候。 77 | 78 | ```javascript 79 | // 反例 80 | myname = "global"; // 全局变量 81 | function func() { 82 | alert(myname); // "undefined" 83 | var myname = "local"; 84 | alert(myname); // "local" 85 | } 86 | func(); 87 | ``` 88 | 毕竟由于函数申明提升,也就是预解析。为了避免这种混 乱,最好是预先声明你想使用的全部变量。 89 | >为了完整,我们再提一提执行层面的稍微复杂点的东西。代码处理分两个阶段,第一阶段是变量,函数声明,以及正常格式的参数创建,这是一个解析和进入上下文 的阶段。第二个阶段是代码执行,函数表达式和不合格的标识符(为声明的变量)被创建。但是,出于实用的目的,我们就采用了”hoisting”这个概念, 这种ECMAScript标准中并未定义,通常用来描述行为。 90 | 91 | ## for循环 92 | 推荐写法 93 | 94 | ```javascript 95 | for (var i = 0, max = myarray.length; i < max; i++) { 96 | // 使用myarray[i]做点什么 97 | } 98 | ``` 99 | 伴随着单var形式,你可以把变量从循环中提出来,就像下面这样: 100 | ```javascript 101 | function looper() { 102 | var i = 0, 103 | max, 104 | myarray = []; 105 | // ... 106 | for (i = 0, max = myarray.length; i < max; i++) { 107 | // 使用myarray[i]做点什么 108 | } 109 | } 110 | ``` 111 | 最后一个需要对循环进行调整的是使用下面表达式之一来替换i++。 112 | ```javascript 113 | i = i + 1 114 | i += 1 115 | ``` 116 | 原因是++和–-促进了“过分棘手(excessive trickiness)”。 117 | 118 | 还有两种变化的形式,其又有了些微改进,因为: 119 | - 少了一个变量(无max) 120 | - 向下数到0,通常更快,因为和0做比较要比和数组长度或是其他不是0的东西作比较更有效率 121 | 122 | ```javascript 123 | //第一种变化的形式: 124 | 125 | var i, myarray = []; 126 | for (i = myarray.length; i–-;) { 127 | // 使用myarray[i]做点什么 128 | } 129 | 130 | //第二种使用while循环: 131 | 132 | var myarray = [], 133 | i = myarray.length; 134 | while (i–-) { 135 | // 使用myarray[i]做点什么 136 | } 137 | ``` 138 | 139 | ## for-in Loops 140 | for-in循环应该用在非数组对象的遍历上,使用for-in进行循环也被称为“枚举” 141 | 142 | 从技术上将,你可以使用for-in循环数组(因为JavaScript中数组也是对象),但这是不推荐的。因为如果数组对象已被自定义的功能增强,就可能发生逻辑错误。另外,在for-in中,属性列表的顺序(序列)是不能保证的。所以最好数组使用正常的for循环,对象使用for-in循环。 143 | 144 | 有个很重要的hasOwnProperty()方法,当遍历对象属性的时候可以过滤掉从原型链上下来的属性。 145 | 146 | ```javascript 147 | // 对象 148 | var man = { 149 | hands: 2, 150 | legs: 2, 151 | heads: 1 152 | }; 153 | 154 | // 在代码的某个地方 155 | // 一个方法添加给了所有对象 156 | if (typeof Object.prototype.clone === "undefined") { 157 | Object.prototype.clone = function () {}; 158 | } 159 | ``` 160 | 在这个例子中,我们有一个使用对象字面量定义的名叫man的对象。在man定义完成后的某个地方,在对象原型上增加了一个很有用的名叫 clone()的方法。此原型链是实时的,这就意味着所有的对象自动可以访问新的方法。为了避免枚举man的时候出现clone()方法,你需要应用hasOwnProperty()方法过滤原型属性。如果不做过滤,会导致clone()函数显示出来,在大多数情况下这是不希望出现的。 161 | 162 | ```javascript 163 | // 1. 164 | // for-in 循环 165 | for (var i in man) { 166 | if (man.hasOwnProperty(i)) { // 过滤 167 | console.log(i, ":", man[i]); 168 | } 169 | } 170 | /* 控制台显示结果 171 | hands : 2 172 | legs : 2 173 | heads : 1 174 | */ 175 | // 2. 176 | // 反面例子: 177 | // for-in loop without checking hasOwnProperty() 178 | for (var i in man) { 179 | console.log(i, ":", man[i]); 180 | } 181 | /* 182 | 控制台显示结果 183 | hands : 2 184 | legs : 2 185 | heads : 1 186 | clone: function() 187 | */ 188 | ``` 189 | 另外一种使用hasOwnProperty()的形式是取消Object.prototype上的方法。像是: 190 | ```javascript 191 | for (var i in man) { 192 | if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤 193 | console.log(i, ":", man[i]); 194 | } 195 | } 196 | ``` 197 | 其好处在于在man对象重新定义hasOwnProperty情况下避免命名冲突。也避免了长属性查找对象的所有方法,你可以使用局部变量“缓存”它。 198 | ```javascript 199 | var i, hasOwn = Object.prototype.hasOwnProperty; 200 | for (i in man) { 201 | if (hasOwn.call(man, i)) { // 过滤 202 | console.log(i, ":", man[i]); 203 | } 204 | } 205 | ``` 206 | ## (不)扩展内置原型 207 | 增加内置的构造函数原型(如Object(), Array(), 或Function())挺诱人的,但是这严重降低了可维护性,因为它让你的代码变得难以预测。使用你代码的其他开发人员很可能更期望使用内置的 JavaScript方法来持续不断地工作,而不是你另加的方法。 208 | 209 | ## switch模式 210 | ```javascript 211 | var inspect_me = 0, 212 | result = ''; 213 | switch (inspect_me) { 214 | case 0: 215 | result = "zero"; 216 | break; 217 | case 1: 218 | result = "one"; 219 | break; 220 | default: 221 | result = "unknown"; 222 | } 223 | ``` 224 | - 每个case和switch对齐(花括号缩进规则除外) 225 | - 每个case中代码缩进 226 | - 每个case以break清除结束 227 | - 避免贯穿(故意忽略break)。如果你非常确信贯穿是最好的方法,务必记录此情况,因为对于有些阅读人而言,它们可能看起来是错误的 228 | - 以default结束switch:确保总有健全的结果,即使无情况匹配。 229 | 230 | ## 避免隐式类型转换 231 | JavaScript的变量在比较的时候会隐式类型转换。这就是为什么一些诸如:false == 0 或 “” == 0 返回的结果是true。为避免引起混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用===和!==操作符。 232 | 233 | ## 避免 eval() 234 | 如果代码是在运行时动态生成,有一个更好的方式不使用eval而达到同样的目 标。例如,用方括号表示法来访问动态属性会更好更简单: 235 | ```javascript 236 | // 反面示例 237 | var property = "name"; 238 | alert(eval("obj." + property)); 239 | 240 | // 更好的 241 | var property = "name"; 242 | alert(obj[property]); 243 | ``` 244 | 245 | 使用eval()也带来了安全隐患,因为被执行的代码(例如从网络来)可能已被篡改。这是个很常见的反面教材,当处理Ajax请求得到的JSON 相应的时候。在这些情况下,最好使用JavaScript内置方法来解析JSON相应,以确保安全和有效。若浏览器不支持JSON.parse(),你可 以使用来自JSON.org的库。 246 | 247 | 同样重要的是要记住,给setInterval(), setTimeout()和Function()构造函数传递字符串,大部分情况下,与使用eval()是类似的,因此要避免。在幕后,JavaScript仍需要评估和执行你给程序传递的字符串: 248 | ```javascript 249 | // 反面示例 250 | setTimeout("myFunc()", 1000); 251 | setTimeout("myFunc(1, 2, 3)", 1000); 252 | 253 | // 更好的 254 | setTimeout(myFunc, 1000); 255 | setTimeout(function () { 256 | myFunc(1, 2, 3); 257 | }, 1000); 258 | ``` 259 | 如果你绝对必须使用eval(),你 可以考虑使用new Function()代替。有一个小的潜在好处,因为在新Function()中作代码评估是在局部函数作用域中运行,所以代码中任何被评估的通过var 定义的变量都不会自动变成全局变量。另一种方法来阻止自动全局变量是封装eval()调用到一个即时函数中。 260 | ```javascript 261 | console.log(typeof un); // "undefined" 262 | console.log(typeof deux); // "undefined" 263 | console.log(typeof trois); // "undefined" 264 | 265 | var jsstring = "var un = 1; console.log(un);"; 266 | eval(jsstring); // logs "1" 267 | 268 | jsstring = "var deux = 2; console.log(deux);"; 269 | new Function(jsstring)(); // logs "2" 270 | 271 | jsstring = "var trois = 3; console.log(trois);"; 272 | (function () { 273 | eval(jsstring); 274 | }()); // logs "3" 275 | 276 | console.log(typeof un); // number 277 | console.log(typeof deux); // "undefined" 278 | console.log(typeof trois); // "undefined" 279 | ``` 280 | 另一间eval()和Function构造不同的是eval()可以干扰作用域链,而Function()更安分守己些。不管你在哪里执行 Function(),它只看到全局作用域。所以其能很好的避免本地变量污染。 281 | 282 | ## parseInt()下的数值转换 283 | 使用parseInt()你可以从字符串中获取数值,该方法接受另一个基数参数,这经常省略,但不应该。当字符串以”0″开头的时候就有可能会出问 题,例如,部分时间进入表单域,在ECMAScript 3中,开头为”0″的字符串被当做8进制处理了,但这已在ECMAScript 5中改变了。为了避免矛盾和意外的结果,总是指定基数参数。 284 | 285 | ## 空格 286 | 空格的使用同样有助于改善代码的可读性和一致性。在写英文句子的时候,在逗号和句号后面会使用间隔。在JavaScript中,你可以按照同样的逻辑在列表模样表达式(相当于逗号)和结束语句(相对于完成了“想法”)后面添加间隔。 287 | 288 | 适合使用空格的地方包括: 289 | 290 | - for循环分号分开后的的部分:如for (var i = 0; i < 10; i += 1) {...} 291 | - for循环中初始化的多变量(i和max):for (var i = 0, max = 10; i < max; i += 1) {...} 292 | - 分隔数组项的逗号的后面:var a = [1, 2, 3]; 293 | - 对象属性逗号的后面以及分隔属性名和属性值的冒号的后面:var o = {a: 1, b: 2}; 294 | - 限定函数参数:myFunc(a, b, c) 295 | - 函数声明的花括号的前面:function myFunc() {} 296 | - 匿名函数表达式function的后面:var myFunc = function () {}; 297 | 298 | 使用空格分开所有的操作符和操作对象是另一个不错的使用,这意味着在+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=等前后都需要空格。 299 | 300 | ## 注释、 驼峰命名 301 | 不再多说 302 | -------------------------------------------------------------------------------- /doc/basic_js/彻底明白this指向.md: -------------------------------------------------------------------------------- 1 | # this 其实很简单 2 | 3 | > 此文主要总结于《你不知道的JavaScript 上卷》,虽然讲解this的文章已经烂大街了,但是依旧希望,这篇文章可以帮助到那些还是对this有些疑惑的哥们 4 | 5 | ## 前言 6 | this关键字是JavaScript中最复杂的机制之一,它不是一个特殊的关键字,被自动定义在所有的函数作用域中。 7 | 8 | 老规矩,我们直接看例子: 9 | ```javascript 10 | function identify(){ 11 | console.log(this.name) 12 | return this.name.toUpperCase(); 13 | } 14 | 15 | function speak() { 16 | var gretting = 'Hello I am '+identify.call(this) 17 | console.log(gretting); 18 | } 19 | 20 | var me = { 21 | name:'Neal' 22 | } 23 | 24 | var you = { 25 | name:'Nealyang' 26 | } 27 | identify.call(me); 28 | identify.call(you); 29 | 30 | speak.call(me); 31 | speak.call(you); 32 | ``` 33 | 关于运行结果大家可以自行运行查看,如果对于this是如何工作的这里我们还是存在疑惑,那么别急,我们后面当然会继续深入探讨下,这里,先说下关于this 34 | 的一些误解的地方 35 | 36 | ## 关于this的误解 37 | ### this 值得是它自己 38 | 通常新手都会认为this就是指向函数本身,至于为什么在函数中引用他自己呢,可能就是因为递归这种情况的存在吧。但是这里,我想说,this并不是指向函数本身的 39 | ```javascript 40 | function foo(num) { 41 | console.log("foo:"+num); 42 | this.count++; 43 | } 44 | 45 | foo.count = 0; 46 | 47 | for(var i = 0;i<10;i++){ 48 | foo(i); 49 | } 50 | 51 | console.log(foo.count); 52 | ``` 53 | 通过运行上面的代码我们可以看到,foo函数的确是被调用了十次,但是this.count似乎并没有加到foo.count上。也就是说,函数中的this.count并不是foo.count。 54 | 55 | 所以,这里我们一定要记住一个,就是函数中的this并不是指向函数本身的。上面的代码修改如下: 56 | 57 | ```javascript 58 | function foo(num) { 59 | console.log("foo:"+num); 60 | this.count++; 61 | } 62 | 63 | foo.count = 0; 64 | 65 | for(var i = 0;i<10;i++){ 66 | foo.call(foo,i); 67 | } 68 | 69 | console.log(foo.count); 70 | ``` 71 | 运行如上代码,此时我们就可以看到foo函数中的count的确已经变成10了 72 | 73 | ### this值得是他的作用域 74 | 另一种对this的误解是它不知怎么的指向函数的作用域,其实从某种意义上来说他是正确的,但是从另一种意义上来说,这的确是一种误解。 75 | 76 | 明确的说,this不会以任何方式指向函数的词法作用域,作用域好像是一个将所有可用标识符作为属性的对象,这从内部来说他是对的,但是JavaScript代码不能访问这个作用域“对象”,因为它是引擎内部的实现。 77 | 78 | ```javascript 79 | function foo() { 80 | var a = 2; 81 | this.bar(); 82 | } 83 | 84 | function bar() { 85 | console.log( this.a ); 86 | } 87 | 88 | foo(); //undefined 89 | ``` 90 | 上面的代码不止一处错误,这里不做讨论,仅仅用于看代码,首先,视图this.bar()来视图访问bar函数,的确他做到了。虽然只是碰巧而已。然而,写下这段代码的开发者视图使用this在foo和bar的词法作用域中建立一座桥,是的bar可以访问foo内部变量作用域a。当然,这是不可能的,不可能使用this引用在词法作用域中查找东西。 91 | 92 | ## 什么是this 93 | 所以说了这么coder对this的误解,那么究竟什么是this呢。记住,this不是在编写时候绑定的,而是在运行时候绑定的上下文执行环境。this绑定和函数申明无关,反而和函数被调用的方式有关系。 94 | 95 | 当一个函数被调用的时候,会建立一个活动记录,也成为执行环境。这个记录包含函数是从何处(call-stack)被调用的,函数是 如何 被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的this引用。 96 | 97 | ## 彻底明白this到底值得什么鬼 98 | 99 | ### 调用点 100 | 为了彻底弄明白this的指向问题,我们还必须明白什么是调用点,即一个函数被调用的位置。考虑调用栈(即使我们到达当前执行位置而被d调用的所有方法堆栈)是非常重要的,我们关心的调用点就是当前执行函数的之前的调用 101 | 102 | ```javascript 103 | function baz() { 104 | // 调用栈是: `baz` 105 | // 我们的调用点是global scope(全局作用域) 106 | 107 | console.log( "baz" ); 108 | bar(); // <-- `bar`的调用点 109 | } 110 | 111 | function bar() { 112 | // 调用栈是: `baz` -> `bar` 113 | // 我们的调用点位于`baz` 114 | 115 | console.log( "bar" ); 116 | foo(); // <-- `foo`的call-site 117 | } 118 | 119 | function foo() { 120 | // 调用栈是: `baz` -> `bar` -> `foo` 121 | // 我们的调用点位于`bar` 122 | 123 | console.log( "foo" ); 124 | } 125 | 126 | baz(); // <-- `baz`的调用点 127 | ``` 128 | 上面代码大家简单感受下什么是调用栈和调用点,比较简单的东西。 129 | 130 | ### 来点规则,有规可寻 131 | 我们必须考察调用点,来判断下面即将要说的四中规则哪一种适用。先独立解释下四中规则的每一种,然后再来说明下如果多种规则适用调用点时他们的优先级。 132 | 133 | #### 默认绑定 134 | 所谓的默认绑定,就是独立函数的调用形式。 135 | 136 | ```javascript 137 | function foo() { 138 | console.log( this.a ); 139 | } 140 | 141 | var a = 2; 142 | 143 | foo(); // 2 144 | ``` 145 | 为什么会是2呢,因为在调用foo的时候,JavaScript对this实施了默认绑定,所以this就指向了全局对象。 146 | 147 | 我们怎么知道这里适用 默认绑定 ?我们考察调用点来看看foo()是如何被调用的。在我们的代码段中,foo()是被一个直白的,毫无修饰的函数引用调用的。没有其他的我们将要展示的规则适用于这里,所以 默认绑定 在这里适用。 148 | 149 | 需要注意的是,对于严格模式来说,默认绑定全局对象是不合法的,this被置为undefined。但是一个很微妙的事情是,即便是所有的this绑定规则都是基于调用点的,如果foo的内容没有严格模式下,默认绑定也是合法的。 150 | 151 | #### 隐含绑定 152 | 调用点是否有一个环境对象,也成为拥有者和容器对象。 153 | ```javascript 154 | function foo() { 155 | console.log( this.a ); 156 | } 157 | 158 | var obj = { 159 | a: 2, 160 | foo: foo 161 | }; 162 | 163 | obj.foo(); // 2 164 | ``` 165 | foo被申明,然后被obj添加到其属性上,无论foo()是否一开始就在obj上被声明,还是后来作为引用添加(如上面代码所示),都是这个 函数 被obj所“拥有”或“包含”。 166 | 167 | 这里需要注意的是,只有对象属性引用链的最后一层才影响调用点 168 | 169 | ```javascript 170 | function foo() { 171 | console.log( this.a ); 172 | } 173 | 174 | var obj2 = { 175 | a: 42, 176 | foo: foo 177 | }; 178 | 179 | var obj1 = { 180 | a: 2, 181 | obj2: obj2 182 | }; 183 | 184 | obj1.obj2.foo(); // 42 185 | ``` 186 | ##### 隐含绑定丢死 187 | this绑定最让人头疼的地方就是隐含绑定丢失了他的绑定,其实明确了调用位置,这个也不是难点。直接看代码 188 | ```javascript 189 | function foo() { 190 | console.log( this.a ); 191 | } 192 | 193 | var obj = { 194 | a: 2, 195 | foo: foo 196 | }; 197 | 198 | var bar = obj.foo; // 函数引用! 199 | 200 | var a = "oops, global"; // `a`也是一个全局对象的属性 201 | 202 | bar(); // "oops, global" 203 | ``` 204 | 所以如上的调用模式,我们又退回到了默认绑定模式。 205 | 206 | 还能hold住,那么接着看代码: 207 | ```javascript 208 | function foo() { 209 | console.log( this.a ); 210 | } 211 | 212 | function doFoo(fn) { 213 | // `fn` 只不过`foo`的另一个引用 214 | 215 | fn(); // <-- 调用点! 216 | } 217 | 218 | var obj = { 219 | a: 2, 220 | foo: foo 221 | }; 222 | 223 | var a = "oops, global"; // `a`也是一个全局对象的属性 224 | 225 | doFoo( obj.foo ); // "oops, global" 226 | ``` 227 | 参数传递,仅仅是一种隐含的赋值,而且因为我们是传递一个函数,他是一个隐含的引用赋值,所以最终结果和我们前一段代码一样。 228 | 229 | 所以,在回调函数中丢失this绑定是一件很常见的事情,但是还有另一种情况,接受我们回调的函数故意改变this的值。那些很受欢迎的事件处理JavaScript包就十分喜欢强制你的回调的this指向触发事件的DOM元素。 230 | 231 | 不管哪一种意外改变this的方式,你都不能真正地控制你的回调函数引用将如何被执行,所以你(还)没有办法控制调用点给你一个故意的绑定。我们很快就会看到一个方法,通过 固定 this来解决这个问题。 232 | 233 | ***如上,我们一定要清除的是引用和调用。记住,找this,我们只看调用,别被引用所迷惑*** 234 | 235 | #### 明确绑定 236 | 237 | 在JavaScript中,我们可以强制制定一个函数在运行时候的this值。是的,call和apply,他们的作用就是扩充函数赖以生存的作用域。 238 | 239 | ```javascript 240 | function foo() { 241 | console.log( this.a ); 242 | } 243 | 244 | var obj = { 245 | a: 2 246 | }; 247 | 248 | foo.call( obj ); // 2 249 | ``` 250 | 上面代码,我们使用foo,强制将foo的this指定为obj 251 | 252 | 如果你传递一个简单原始类型值(string,boolean,或 number类型)作为this绑定,那么这个原始类型值会被包装在它的对象类型中(分别是new String(..),new Boolean(..),或new Number(..))。这通常称为“boxing(封箱)”。 253 | 254 | 但是,单独的依靠明确绑定仍然不能为我们先前提到的问题,提供很好的解决方案,也就是函数丢失自己原本的this绑定。 255 | 256 | ##### 硬性绑定 257 | ```javascript 258 | function foo() { 259 | console.log( this.a ); 260 | } 261 | 262 | var obj = { 263 | a: 2 264 | }; 265 | 266 | var bar = function() { 267 | foo.call( obj ); 268 | }; 269 | 270 | bar(); // 2 271 | setTimeout( bar, 100 ); // 2 272 | 273 | // `bar`将`foo`的`this`硬绑定到`obj` 274 | // 所以它不可以被覆盖 275 | bar.call( window ); // 2 276 | ``` 277 | 我们创建了一个函数bar(),在它的内部手动调用foo.call(obj),由此强制this绑定到obj并调用foo。无论你过后怎样调用函数bar,它总是手动使用obj调用foo。这种绑定即明确又坚定,所以我们称之为 硬绑定(hard binding) 278 | 279 | #### new 绑定 280 | 281 | 这个比较简单,当函数前面加入new关键字调用的时候,其实就是当做构造函数调用的。其内部其实完成了如下事情: 282 | - 一个新的对象会被创建 283 | - 这个新创建的对象会被接入原型链 284 | - 这个新创建的对象会被设置为函数调用的this绑定 285 | - 除非函数返回一个他自己的其他对象,这个被new调用的函数将自动返回一个新创建的对象 286 | 287 | ### 总结性来一波 288 | - 函数是否在new中调用,如果是的话this绑定的是新创建的对象 289 | ```javascript 290 | var bar = new Foo(); 291 | ``` 292 | - 函数是否通过call、apply或者其他硬性调用,如果是的话,this绑定的是指定的对象 293 | ```javascript 294 | var bar = foo.call(obj); 295 | ``` 296 | - 函数是否在某一个上下文对象中调用,如果是的话,this绑定的是那个上下文对象 297 | ```javascript 298 | var bar = obj.foo(); 299 | ``` 300 | - 如果都不是的话,使用默认绑定,如果在严格模式下,就绑定到undefined,注意这里是方法里面的严格声明。否则绑定到全局对象 301 | ```javascript 302 | var bar = foo(); 303 | ``` 304 | 305 | ## 绑定例外 306 | 第一种情况就是将null和undefined传给call、apply、bind等函数,然后此时this采用的绑定规则是默认绑定 307 | 308 | 第二种情况这里举个例子,也是面试中常常会出现的例子 309 | ```javascript 310 | function foo() { 311 | console.log(this.a); 312 | } 313 | var a = 2; 314 | var o = { 315 | a:3, 316 | foo:foo 317 | } 318 | var p = {a:4}; 319 | (p.foo = o.foo)(); 320 | ``` 321 | 如上调用,其实foo采用的也是默认绑定,这里我们需要知道的是,p.foo = o.foo的返回值是目标函数的引用,所以最后一句其实就是foo() 322 | 323 | ### es6中的箭头函数 324 | 325 | es6中的箭头函数比较简单,由于箭头函数并不是function关键字定义的,所以箭头函数不适用this的这四中规则,而是根据外层函数或者全局作用域来决定this 326 | ```javascript 327 | function foo() { 328 | // 返回一个arrow function 329 | return (a) => { 330 | // 这里的`this`是词法上从`foo()`采用 331 | console.log( this.a ); 332 | }; 333 | } 334 | 335 | var obj1 = { 336 | a: 2 337 | }; 338 | 339 | var obj2 = { 340 | a: 3 341 | }; 342 | 343 | var bar = foo.call( obj1 ); 344 | bar.call( obj2 ); // 2, 不是3! 345 | ``` 346 | 这里foo内部创建的箭头函数会自动获取foo的this。 347 | 348 | ### 来一道经典面试题吧 349 | - 第一题 350 | ```javascript 351 | var a=10; 352 | var foo={ 353 | a:20, 354 | bar:function(){ 355 | var a=30; 356 | console.log(this) 357 | return this.a; 358 | } 359 | }; 360 | foo.bar() 361 | (foo.bar)() 362 | (foo.bar=foo.bar)() 363 | (foo.bar,foo.bar)() 364 | ``` 365 | - 第二题 366 | ```javascript 367 | function t(){ 368 | this.x=2; 369 | } 370 | t(); 371 | console.log(window.x); 372 | ``` 373 | 374 | - 第三题 375 | ```javascript 376 | var obj = { 377 | x: 1, 378 | y: 2, 379 | t: function() { 380 | console.log(this.x) 381 | } 382 | } 383 | obj.t(); 384 | 385 | var dog={x:11}; 386 | dog.t=obj.t; 387 | dog.t(); 388 | 389 | 390 | show=function(){ 391 | console.log('show'+this.x); 392 | 393 | } 394 | 395 | dog.t=show; 396 | dog.t(); 397 | ``` 398 | - 第四题 399 | ```javascript 400 | name = 'this is window'; 401 | var obj1 = { 402 | name: 'php', 403 | t: function() { 404 | console.log(this.name) 405 | } 406 | }; 407 | var dog1 = { 408 | name: 'huzi' 409 | }; 410 | 411 | obj1.t(); 412 | 413 | dog1.t = obj1.t; 414 | 415 | var tmp = dog1.t; 416 | tmp(); //this本来指向window 417 | 418 | (dog1.t = obj1.t)(); 419 | dog1.t.call(obj1); 420 | ``` 421 | - 第五题 422 | 423 | ```javascript 424 | var number=2; 425 | var obj={ 426 | number:4, 427 | /*匿名函数自调*/ 428 | fn1:(function(){ 429 | var number; 430 | this.number*=2;//4 431 | 432 | number=number*2;//NaN 433 | number=3; 434 | return function(){ 435 | var num=this.number; 436 | this.number*=2;//6 437 | console.log(num); 438 | number*=3;//9 439 | alert(number); 440 | } 441 | })(), 442 | 443 | db2:function(){ 444 | this.number*=2; 445 | } 446 | } 447 | 448 | var fn1=obj.fn1; 449 | 450 | alert(number); 451 | 452 | fn1(); 453 | 454 | obj.fn1(); 455 | 456 | alert(window.number); 457 | 458 | alert(obj.number); 459 | ``` 460 | 461 | ## 交流 462 | 463 | ***扫码关注我的个人微信公众号,分享更多原创文章。点击交流学习加我微信、qq群。一起学习,一起进步。共同交流上面的题目吧*** 464 | 465 | ![wx](../../img/pay/wx.jpg) 466 | 467 | --- 468 | 469 | 欢迎兄弟们加入: 470 | 471 | Node.js技术交流群:209530601 472 | 473 | React技术栈:398240621 474 | 475 | 前端技术杂谈:604953717 (新建) 476 | 477 | --- -------------------------------------------------------------------------------- /doc/basic_js/忍者级别的操作函数.md: -------------------------------------------------------------------------------- 1 | # 忍者级别的操作JavaScript函数 2 | > 从名字即可看书,此篇博客总结与《JavaScript忍者秘籍》。对于JavaScript来说,函数为第一类型对象。所以这里,我们主要是介绍JavaScript中函数的运用。 3 | 4 | ## 匿名函数 5 | 6 | 对于什么是匿名函数,这里就不做过多介绍了。我们需要知道的是,对于JavaScript而言,匿名函数是一个很重要且具有逻辑性的特性。通常,匿名函数的使用情况是:创建一个供以后使用的函数。 7 | 8 | 简单的举个例子如下: 9 | 10 | ```javascript 11 | window.onload = function() { 12 | alert('hello'); 13 | } 14 | var templateObj = { 15 | shout:function() { 16 | alert('作为方法的匿名函数') 17 | } 18 | } 19 | templateObj.shout(); 20 | 21 | setTimeout(function() { 22 | alert('这也是一个匿名函数'); 23 | },1000) 24 | ``` 25 | 26 | 上面的一个代码片段我就不做过多无用解释了,比较常规。 27 | 28 | ## 递归 29 | 30 | 递归,说白了,就是自己调用自己,或者调用另外一个函数,但是这个函数的调用树的某一个地方又调用了自己。所以递归,就产生了。 31 | 32 | ### 普通命名函数的递归 33 | 34 | 拿普通命名函数的递归最好的举例就是用最简单的递归需求:检测回文。 35 | 36 | 回文的定义如下:一个短语,不管从哪一个方向读,都是一样的。检测的工作当然方法多样,我们可以创建一个函数,用待检测的回文字符逆序生成出一个字符,然后检测二者是否相同,如果相同,则为回文字符。 37 | 38 | 但是这种方法并不是很有逼格,确切的说,代价比较大,因为我们需要分配并创建新的字符。 39 | 40 | 所以,我们可以整理出如下简洁的办法: 41 | - 单个和零个字符都是回文 42 | - 如果字符串的第一个字符和最后一个字符相同,并且除了两个字符以外,别的字符也满足该要求,那么我们就可以检测出来了这个是回文了 43 | 44 | ```javascript 45 | function isPalindrome(txt) { 46 | if(txt.length<=1){ 47 | return true; 48 | } 49 | if(txt.charAt(0)!= txt.charAt(txt.length-1)) return false; 50 | return isPalindrome(txt.substr(1,txt.length-2)); 51 | } 52 | ``` 53 | 54 | 上面的代码我们并没有做txt的一些类型检测,undefined、null等。 55 | 56 | ### 方法中的递归 57 | 58 | 所谓的方法,自然离不开对象,直接看例子: 59 | 60 | ```javascript 61 | var ninja = { 62 | chirp:function(n) { 63 | return n>1?ninja.chirp(n-1)+'-chirp':'chirp'; 64 | } 65 | } 66 | console.log(ninja.chirp(3))//chirp-chirp-chirp 67 | ``` 68 | 69 | 在上述代码中,我们通过对象ninja.chirp方法的递归调用了自己。但是,因为我们在函数上s会用了非直接引用,也就是ninja对象的chirp属性,所以才能够实现递归,这也就引出来一个问题:引用丢失 70 | 71 | ### 引用丢失的问题 72 | 73 | 上面的示例代码,依赖于一个进行递归调用的对象属性引用。与函数的实际名称不同,因为这种引用可能是暂时的。 74 | 75 | ```javascript 76 | var ninja = { 77 | chirp:function(n) { 78 | return n>1?ninja.chirp(n-1)+'-chirp':'chirp'; 79 | } 80 | } 81 | var samurai = {chirp:ninja.chirp}; 82 | ninja = {}; 83 | 84 | try{ 85 | console.log(samurai.chirp(3) === 'chirp-chirp-chirp') 86 | }catch (err){ 87 | if(err) alert(false); 88 | } 89 | ``` 90 | 91 | 如上,我们把ninja属性上的方法赋值给了samurai,然后置空ninja,然后你懂得~这就是引用丢失的问题。 92 | ![](../../img/171030.png) 93 | ***截图自《JavaScript忍者秘籍》*** 94 | 95 | 通过完善之前对匿名函数的粗略定义,我们可以修复解决这个问题。在匿名函数中,我们不在使用显示的ninja引用。这里我们使用this(关于this的使用详解,请关注我的个人微信公众号:前端的全栈之路)。 96 | 97 | ```javascript 98 | var ninja = { 99 | chirp:function(n) { 100 | return n>1?this.chirp(n-1)+'-chirp':'chirp'; 101 | } 102 | } 103 | ``` 104 | 当函数作为方法被调用的时候,函数的上下文指的是该方法的对象。 105 | 106 | 使用this调用,可以让我们的匿名函数更加的强大且灵活。但是。。。 107 | 108 | ### 内联命名函数 109 | 110 | 上面我们解决了作为函数方法作为递归时候的一个完美操作。但实际上,不管是否进行方法递归,巧妙使用this都是我们应该所掌握的(关注微信公众号,早晚都给你说到)。 111 | 112 | 话说回来,其实这样写也还是有问题的,问题在于给对象定义方法的时候,方法名称是写死的,如果属性名称不一样,岂不是一样会丢失引用? 113 | 114 | 所以,这里我们采用另一种解决方案,给匿名函数起个名字吧!对的,肯定又人会说,我擦!那还是匿名函数么?嗯。。。好吧,那就不叫匿名函数了吧,叫内联函数~ 115 | 116 | ```javascript 117 | var ninja = { 118 | chirp:function signal(n) { 119 | return n>1?signal(n-1)+'-chirp':'chirp'; 120 | } 121 | } 122 | var samurai = {chirps:ninja.chirp}; 123 | ninja = {}; 124 | 125 | try{ 126 | console.log(samurai.chirps(3) === 'chirp-chirp-chirp') 127 | }catch (err){ 128 | if(err) alert(false); 129 | } 130 | ``` 131 | 132 | 所以如上的解决办法,就完美解决了我们之前说到所有问题。内联函数还有一个很重要的一点,就是尽管可以给内联函数进行命名,但是这些名称只能在自身函数内部才可见。 133 | 134 | ## 将函数视为对象 135 | 136 | JavaScript中的函数和其他语言中的函数有所不同,JavaScript赋予了函数很多的特性,其中最重要的特性之一就是函数作为第一类型对象。是的,对象! 137 | 138 | 所以,我们可以给函数添加属性,甚至可以添加方法。 139 | 140 | ### 函数存储 141 | 有时候,我们可能需要存储一组相关但又独立的函数,事件回调管理是最为明显的例子。向这个集合添加函数时候,我们得知道哪些函数在集合中存在,否则不添加。 142 | 143 | ```javascript 144 | var store = { 145 | nextId:1, 146 | cache:{}, 147 | add:function(fn) { 148 | if(!fn.id){ 149 | fn.id = store.nextId++; 150 | return !!(store.cache[fn.id] = fn); 151 | } 152 | } 153 | } 154 | 155 | function ninja() {} 156 | 157 | console.log(store.add(ninja)); 158 | console.log(store.add(ninja)); 159 | ``` 160 | 上述代码比较简单常规,也就不做过多解释。 161 | 162 | ### 自记忆函数 163 | 164 | 缓存记忆是构造函数的过程,这种函数能够记住先前计算的结果。通过避免重复的计算,极大地提高性能。 165 | 166 | #### 缓存记忆昂贵的计算结果 167 | 168 | 作为一个简单的例子,这里我来判断一个数字是否为素数。 169 | 170 | ```javascript 171 | function isPrime(value) { 172 | if(!isPrime.answers) isPrime.answers = {}; 173 | if(isPrime.answers[value]!=null){ 174 | return isPrime.answers[value] 175 | } 176 | var prime = value != 1;//1 不是素数 177 | for(var i = 2;i 223 | 224 | 225 | 226 | 227 | 242 | 243 | 244 | ``` 245 | 246 | 通常,Array.prototype.push()是通过其函数上下文操作其自身数组的。这里我们通过call方法来讲我们自己的对象扮演了一次他的上下文。push的方法会增加length的值(会认为他就是数组的length属性),然后给对象添加一个数字属性,并将其引用到传入的元素上。 247 | 248 | 关于函数的执行上下文,以及prototype的一些说明,将在后续文章写到。 249 | 250 | ## 可变函数的参数列表 251 | 252 | JavaScript灵活且强大的特性之一是函数可以接受任意数量的参数。虽然JavaScript没有函数的重载,但是参数列表的灵活性是获取其他语言类似重载功能的关键所在 253 | 254 | ### 使用apply()支持可变参数 255 | 256 | 需求:查找数组中的最大值、最小值 257 | 258 | 一开始,我认为Math中提供的min(),max()可以满足,但是貌似他并不能够找到数组中的最大值最小值,难道要我这样:Math.min(arr[0],arr[1],arr[3]...)?? 259 | 260 | 来吧,我们继续我们的奇淫技巧。 261 | 262 | ```javascript 263 | function smallest(arr) { 264 | return Math.min.apply(Math,arr); 265 | } 266 | function largest(arr) { 267 | return Math.max.apply(Math,arr); 268 | } 269 | 270 | console.log(smallest([0,1,2,3,4])); 271 | console.log(largest([0,1,2,3,4])); 272 | ``` 273 | 不做过多解释,操作常规,是不是又是一个眼前一亮呢? 274 | 275 | ### 函数重载 276 | 之前我们有介绍过函数的隐士传递,arguments,也正是因为这个arguments的存在,才让函数有能力处理不同数量的参数。即使我们只定义固定数量的形参,通过arguments参数我们还是可以访问到实际传给函数的所有的参数。 277 | 278 | #### 检测并遍历参数 279 | 280 | 方法的重载通常是通过在同名的方法里声明不同的实例来达到目的。但是在javascript中并非如此,在javaScript中,我们重载函数的时候只有一个实现。只不过这个实现内部是通过函数实际传入的参数的特性和个数来达到相应目的的。 281 | ```javascript 282 | function merge(root){ 283 | for(var i = 1;i2012){ 54 | this._year = value, 55 | this.edition++ 56 | } 57 | } 58 | }); 59 | 60 | book.year = 2013; 61 | console.log(book.edition);//2 62 | 63 | 其实对于多个属性的定义,我们可以使用Object.defineProperties方法。然后对于读取属性的特性我们可以使用Object.getOwnPropertyDescriptor()方法。大家自行查看哈。 64 | 65 | ## 创建对象 66 | 创建对象,我们不是直接可以通过Object的构造函数或者对象字面量的方法来实现对象的创建嘛?当然,这些方法是可以的,但是有一个明显的缺点:使用同一个接口创建很多对象,产生大量重复的代码。所以这里,我们使用如下的一些骚操作 67 | 68 | ### 工厂模式 69 | 70 | 一种很基础的设计模式,简而言之就是用函数来封装以特定接口创建对象的细节。 71 | 72 | function createAnimal(name,type){ 73 | var o = new Object(); 74 | o.name = name; 75 | o.type = type; 76 | o.sayName = function(){ 77 | alert(this.name) 78 | } 79 | return o; 80 | } 81 | var cat = createAnimal('小猫','cat'); 82 | var dog = createAnimal('小🐽','dog'); 83 | 84 | 优点:可以无数次的调用这个函数,来创建相似对象。 85 | 缺点:不能解决对象识别的问题。也就是说,我不知道你是谁家的b孩子 86 | 87 | ### 构造函数模式 88 | 89 | ECMAScript中的构造函数可以用来创建特定类型的对象。在运行时会自动出现在执行环境中(这句话后面讲解this的时候还是会说到)。 90 | 91 | function Animal(name,type){ 92 | this.name = name; 93 | this.type = type; 94 | this.say = function(){ 95 | alert(this.name); 96 | } 97 | } 98 | 99 | var cat = new Animal('小猫','cat'); 100 | var dog = new Animal('小🐽','dog'); 101 | 102 | 注意上面我们没有显示的return过一个对象出来,为什么?因为this(后面会讲this的)。 103 | 104 | 关于构造函数惯例首字母大写就不啰嗦了。强调构造函数一定要使用关键字new来调用。为什么使用new呢?因为你使用了new,他会 105 | 106 | - 创建一个新的对象 107 | - 将构造函数的作用域赋值给新对象(this执行新的对象) 108 | - 执行构造函数的代码 109 | - 返回新的对象 110 | 111 | 那么解决了工厂模式的诟病了么?当然~ 112 | 113 | 在实例对象中,都有一个constructor属性。 114 | 115 | cat.constructor == Animal //true 116 | dog.constructor == Animal //true 117 | cat instanceof Animal //true 118 | dog instanceof Animal //true 119 | 120 | 构造函数模式的优点如上所说,但是缺点还是有的,比如说 121 | ```javascript 122 | cat.sayName == dog.sayName //false 123 | ``` 124 | 也就是说,他创建了两个功能一样的函数,这样是很没有必要的,当然,我们可以把sayName放到构造函数外面,然后通过this.sayName=sayName来操作,但是这样的话,又会导致全局变量的污染。肿么办??? 125 | 126 | ### 原型模式 127 | 128 | 我们在创建每一个函数的时候都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。而这个对象的用途就是包含由特定类型的所有实例共享的属性和方法。 129 | 130 | ```javascript 131 | function Animal() {} 132 | Animal.prototype.name = '毛毛'; 133 | Animal.prototype.type = 'dog'; 134 | Animal.prototype.sayName = function() { 135 | alert(this.name); 136 | } 137 | var cat = new Animal(); 138 | var dog = new Animal(); 139 | alert(cat.sayName == dog.sayName)//true 140 | ``` 141 | 原型模式的好处就是可以让所有的对象实例共享他的属性和方法。不必在构造函数中定义对象实例的信息。 142 | 143 | ```javascript 144 | function Person() {} 145 | Person.prototype.name = 'Nealyang'; 146 | Person.prototype.age = 24; 147 | Person.prototype.sayName = function(){ 148 | alert(this.name); 149 | } 150 | var neal = new Person(); 151 | console.log(neal.name)//'Nealyang' -> 来自原型 152 | neal.name = 'Neal'; 153 | console.log(neal.name)// Neal -> 来自实例 154 | 155 | delete neal.name; 156 | console.log(neal.name)//'Nealyang' -> 来自原型 157 | ``` 158 | 上面的例子说明两点 159 | - 原型中的对象属性可以被实例所覆盖重写 160 | - 通过delete可以删除实例中的属性,但是删除不了对象上的 161 | 162 | > 我们可以通过hasOwnProperty()方法来确定一个属性是在原型上还是在实例上。person1.hasOwnProperty('name'),如果name为实例属性,则返回true。 163 | 我们也可以通过 'name' in person1 来确定,person1上是否有name这个属性。 164 | 165 | 上面大家可能已将发现,这种原型模式的写法非常的繁琐,有了大量的XXX.prototype. 这里有一种简写的形式。 166 | 参照具体说明参照[阮神的博客 面向对象第二篇](http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html) 167 | 168 | ```javascript 169 | function Person(){} 170 | Person.prototype = { 171 | constructor:Person, 172 | name:"Neal", 173 | age:24, 174 | job:'Software Engineer', 175 | sayName:function(){ 176 | alert(this.name); 177 | } 178 | } 179 | ``` 180 | 上面代码特意添加了一个constructor属性,因为每创建一个函数,就会自动创建他的prototype对象,这个对象会自动获取contractor属性。而我们这中写法,本质上重写了默认的prototype对象,因此,constructor属性也就变成新的对象的constructor属性了(指向Object构造函数),所以这里的简写方式,一定要加上constructor。 181 | 182 | 下面我们再谈一谈原型模式的优缺点。 183 | 184 | 优点,正如上面我们说到的,可以省略为构造函数传递出实话参数这个环节,并且很多实例可以共享属性和方法。正是因为原型中所有的属性是被所有的实例所共享的,这个特性在方法中非常实用,但是对于包含引用类型的属性来说问题就比较突出了。 185 | 186 | function Person(){}; 187 | 188 | Person.prototype = { 189 | constructor:Person, 190 | name:"neal", 191 | friends:['xiaohong','xiaoming'], 192 | sayName:function(){ 193 | alert(this.name); 194 | } 195 | } 196 | 197 | var person1 = new Person(); 198 | var person2 = new Person(); 199 | 200 | person1.friends.push('xiaohua'); 201 | 202 | alert(person1.friends);//'xiaohong','xiaoming','xiaohua' 203 | alert(person2.friends);//'xiaohong','xiaoming','xiaohua' 204 | alert(person1.friends == person2.friends)//true 205 | 206 | 由于friends数组存在于Person.prototype上,并不是person1上面,所以当我们修改的时候,其实修改的是所有实例所共享的那个值。 207 | 208 | ### 组合使用构造函数和原型模式 209 | 这是创建自定义类型最常见的一种方式。就是组合使用构造函数和原型模式.构造函数模式用于定义实力属性,原型模式用于定义方法和共享的属性。 210 | 211 | function Person(name,age){ 212 | this.name = name, 213 | this.age = age 214 | } 215 | 216 | Person.prototype = { 217 | constructor:Person, 218 | sayName:function(){ 219 | alert(this.name); 220 | } 221 | } 222 | 223 | var person1 = new Person('Neal',24); 224 | var person2 = new Person('Yang',23); 225 | ... 226 | 227 | 上面的例子中,实例所有的属性都是在构造函数中定义,而实例所有共享的属性和方法都是在原型中定义。这种构造函数和原型模式混合的模式,是目前ECMAScript中使用最为广泛的一种方法。 228 | 229 | 当然,有些人会觉得独立的构造函数和原型非常的难受,所以也有推出所谓的动态原型构造模式的这么一说。 230 | 231 | function Person(name,age){ 232 | this.name = name, 233 | this.age = age, 234 | if(typeof this.sayName != 'function'){ 235 | Person.prototype.sayName = function(){ 236 | console.log(this.name) 237 | } 238 | } 239 | } 240 | ... 241 | 242 | 注意上面的代码,之后在sayName不存在的时候,才会在原型上给他添加相应的方法。因为对原型的修改,能够立即在所有的实例中得到反应。所以这中做法确实也是非常的完美。 243 | 244 | 关于javaScript高程中说到的别的寄生构造函数模式和稳妥构造函数模式大家可以自行查看哈~这里就不做过多介绍了。 245 | 246 | ## 继承 247 | 248 | 说到面向对象,当然得说到继承。说到继承当然得说到原型。说到原型,这里我们摘自[网上一篇博客里的段落](https://www.ibm.com/developerworks/cn/web/1304_zengyz_jsoo) 249 | 250 | > 为了说明javascript是一门面向对象的语言,首先有必要从面相对象的概念入手1、一切事物皆对象。2、对象具有封装和继承特性。3、对象与对象之间使用消息通信,各自存在信息隐秘 。 251 | javascript语言是通过一种叫做原型(prototype) 的方式来实现面向对象编程的。当然,还有比如java就是基于类来实现面向对象编程的。 252 | 253 | ### 基于类的面向对象和基于原型的面向对象方式比价 254 | 255 | 对于基于类的面向对象的方式中,对象依靠class类来产生。而在基于原型的面向对象方式中,对象则是依靠构造器(constructor)利用原型(prototype)构造出来的。举个客观世界的例子来说,例如工厂造一辆汽车一方面,工人必须参照一张工程图纸,设计规定这辆车如何制造,这里的工程图纸就好比语言中的类class。而车就是按照这个类制造出来的。另一方面,工人和机器相当于contractor,利用各种零部件(prototype)将汽车造出来。 256 | 257 | 当然,对于上面的例子两种思维各种说法。当然,笔者更加倾向于基于原型的面向对象编程,毕竟我是前端出生(咳咳,真相了),正当理由如下: 258 | 259 | 首先,客观世界中的对象的产生都是其他实物对象构造的世界,而抽象的图纸是不能产生出汽车的。也就是说,类,是一个抽象概念的而非实体,而对象的产生是一个实体的产生。其次,按照一切事物皆对象的这饿极本的面向对象的法则来说,类本身并不是一个对象,然而原型方式的构造函数和原型本身也是个对象。再次,在类的面向对象语言中,对象的状态又对象的实例所持有,对象的行为方法则由申明该对象的类所持有,并且只有对象的构造和方法能够被继承。而在原型的面向对象语言中,对象的行为、状态都属于对象本身,并且能够一起被继承。 260 | 261 | ### 原型链 262 | 263 | ECMAScript描述了原型链的概念,并将原型链作为实现继承的主要方法。基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。 264 | 265 | 实现原型链有一种基本模式: 266 | 267 | function SuperType(){ 268 | this.property = true; 269 | } 270 | 271 | SuperType.prototype.getSuperValue = function(){ 272 | return this.property; 273 | } 274 | 275 | function SubType (){ 276 | this.subproperty = false; 277 | } 278 | 279 | SubType.prototype = new SuperType(); 280 | 281 | SubType.prototype.getSubValue = function(){ 282 | return this.subproperty; 283 | } 284 | 285 | var instance = new SubType(); 286 | 287 | alert(instance.getSuperValue()); 288 | 289 | 在上面的代码中,我们没有使用SubType默认提供的原型,而是给它换了一个新的原型,这个新原型就是SuperType的实例。于是,新原型不仅具有所谓一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向SuperType的原型。最终结果是这样的:instance指向subtype的原型,subtype的原型又指向SuperType的原型。 290 | 291 | 通过实现原型链,本质上是扩展了原型搜索机制。 292 | 293 | 虽然如上,我们已经实现了javascript中的继承。但是依旧存在一些问题:最主要的问题来自包含引用类型的原型。第二个问题就是在创建子类型的实例时,不能向超类型的构造函数中传递参数。这两个问题上面也都有说到,这里就不做过多介绍,直接看解决办法! 294 | 295 | ### 借用构造函数 296 | 在解决原型中包含引用类型的数据时,我们可以在子类型构造函数内部调用超类型的构造函数。直接看代码: 297 | 298 | function SuperType(name){ 299 | this.colors = ['red','yellow']; 300 | this.name = name; 301 | } 302 | 303 | function SubType(name){ 304 | //继承了Super 305 | SuperType.call(this,name) 306 | } 307 | 308 | var instance1 = new SubType('Neal'); 309 | alert(instance1.name) 310 | instance1.colors.push('black'); 311 | alert(instance1.colors);//'red','yellow','black' 312 | 313 | var instance2 = new SubType('yang'); 314 | alert(instance2.colors);//'red','yellow' 315 | 316 | 317 | 毕竟函数只不过是在特定环境中执行代码的对象,因此可以通过call活着apply方法在新创建的对象上执行构造函数。而且如上代码也解决了子类构造函数中向超类构造函数传递参数的问题 318 | 319 | 但是,这样问题就来了,类似我们之前讨论创建的对象那种构造函数的问题:如果都是使用构造函数,那么,也就避免不了方法都在构造函数中定义,然后就会产生大量重复的代码了。 320 | 321 | ### 组合继承 322 | 323 | 因为考虑到上述的缺点,所以这里又使用了组合继承的方式,历史总是惊人的相似。直接看代码: 324 | 325 | function SuperType(name){ 326 | this.name = name; 327 | this.colors = ['red','yellow']; 328 | } 329 | 330 | SuperType.prototype.sayName = function(){ 331 | alert(this.name); 332 | } 333 | 334 | function SubType(name,age){ 335 | //继承属性 336 | SuperType.call(this,name); 337 | 338 | this.age = age; 339 | } 340 | 341 | //继承方法 342 | SubType.prototype = new SuperType(); 343 | SubType.prototype.constructor = SubType; 344 | SubType.prototype.sayAge = function(){ 345 | alert(this.age); 346 | } 347 | 348 | var instance1 = new SubType('Nealyang',24); 349 | instance1.colors.push('white'); 350 | instance1.sayName();//Nealyang 351 | instance1.sayAge();// 24 352 | 353 | var instance2 = new SubType('Neal',21); 354 | alert(instance2.colors);//'red','yellow' 355 | instance2.sayName();//Neal 356 | instance2.sayAge();//21 357 | 358 | 在上面的例子中,SuperType构造函数定义了两个属性,name和colors,SuperType的原型中定义了一个方法sayName,subtype的构造函数中调用SuperType构造函数并且传入name,然后将SuperType的实例赋值给subtype的原型。然后又在新的原型中定义了sayAge的方法。这样一来,就可以让两个不同的SubType实例既分别拥有自己的属性,包括colors,又可以使用相同的方法了。 359 | 360 | 组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点。成为javascript中最为常见的继承模式。而且instanceof和isPrototypeOf方法也能用于识别组合模式创建的对象。 361 | 362 | ### 别的继承模式 363 | 364 | 继承模式是有很多,上面只是说到我们经常使用到的继承模式。包括还有原型式继承、寄生式继承、寄生组合式继承等,其实,只要理解了原型、原型链、构造函数等对象的基本概念,理解起来这中模式都是非常容易的。后续如果有时间,再给大家总结吧~ 365 | 366 | ## 交流 367 | 368 | ***扫码关注我的个人微信公众号,分享更多原创文章。点击交流学习加我微信、qq群。一起学习,一起进步*** 369 | 370 | ![wx](../../img/pay/wx.jpg) 371 | 372 | --- 373 | 374 | 欢迎兄弟们加入: 375 | 376 | Node.js技术交流群:209530601 377 | 378 | React技术栈:398240621 379 | 380 | 前端技术杂谈:604953717 (新建) 381 | 382 | --- 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | -------------------------------------------------------------------------------- /doc/design mode/面向对象.md: -------------------------------------------------------------------------------- 1 | # 面向对象编程 2 | 3 | > 此篇总结与《JavaScript设计模式》github地址 [YOU-SHOULD-KNOW-JS](https://github.com/Nealyang/YOU-SHOULD-KNOW-JS) 4 | 5 | ## 封装 6 | ### 创建一个类 7 | 在JavaScript中创建一个对象很容易,首先声明一个函数保存在一个变量里。按编程习惯一般将这个变量名的首字母大写。然后内部通过this变量来添加属性或者方法来实现对类添加属性和行为。 8 | 9 | ```javascript 10 | var Book = function(id,bookname,price) { 11 | this.id = id; 12 | this.bookename = bookname; 13 | this.price = price; 14 | } 15 | ``` 16 | 当然,我们也可以通过在类的原型上添加属性和方法。有两种方式: 17 | ```javascript 18 | Book.prototype.display = function() { 19 | //展示展示这本书 20 | } 21 | //或者 22 | Book.prototype = { 23 | display:function() { 24 | //展示这本书 25 | } 26 | } 27 | ``` 28 | 29 | 这样,我们就将我们所需要的方法和属性都封装到我们封装的Book类里面了,当使用这些功能和方法的时候,我们不能直接使用这些类,而是需要使用关键字new来实例化新的对象。 30 | ```javascript 31 | var book = new Book(10,'JavaScript设计模式',20); 32 | console.log(book.bookname); 33 | ``` 34 | 注意,通过this添加的属性和方法是在当前对象上添加的,然而JavaScript是一种基于原型的语言,所以每创建一个对象时,他都有一个prototype用于指向其继承的属性、方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时,需要通过prototype一层一层往上查找。 35 | 36 | 简单的说,通过this定的属性和方法是该对象自身拥有的,所以我们每次通过类创建的一个新对象时,this执行的属性和方法都会得到相应的创建,而通过prototype继承的属性和方法是每一个对象通过prototype访问到的。所以我们每一次通过类创建一个新对象时,这些属性和方法不会再次创建。 37 | 38 | ![](../../img/17120401.png) 39 | 如上图,实例的__proto__属性执行原型。原型的constructor属性指向构造函数。当创建一个函数或者对象时都会为其创建一个原型对象prototype,在prototype对象中,又会像函数中创建this一样创建一个constructor属性,这个属性指向的就是拥有整个原型对象的函数或者对象。 40 | 41 | ### 属性与方法封装 42 | 由于JavaScript是函数级作用域,申明在函数内部的变量或者方法在外部是访问不到的,通过此特性即可创建类的私有变量和私有方法。然而在函数内部通过this创建的属性和方法,在类创建对象时,每个对象自身都拥有一份,并且在外部访问到。因此用this创建的属性可以看做是对象的共有属性和共有方法。而通过this创建的方法不但可以访问这些对象的共有属性和共有方法,还可以访问类自身的私有属性和私有方法,这权利比较大,所以我们称之为特权方法。 43 | 在创建对象时,我们可以使用这些特权方法来初始化实例对象的一些属性,因此这些在创建对象时,调用的特权方法可以看做是类的构造器 44 | 45 | ```javascript 46 | var Book = function(id,name,price) { 47 | //私有属性 48 | var num = 1; 49 | //私有方法 50 | function checkId() { 51 | 52 | } 53 | 54 | //特权方法 55 | this.getName = function() {} 56 | this.getPrice = function() {} 57 | this.setName = function() {} 58 | this.setPrice = function() {} 59 | //对象共有属性 60 | this.id = id; 61 | //对象共有方法 62 | this.copy = function() { 63 | 64 | } 65 | //构造器 66 | this.setName(name); 67 | this.setPrice(price); 68 | } 69 | ``` 70 | 71 | 通过new关键字创建新对象时,由于类外面通过点语法添加的属性和方法没有执行到,所以新创建的对象中无法获取他们,当时可以通过类来使用。因此我们称之为静态共有属性和静态共有方法。而通过类的prototype创建的属性和方法在类的实例中可以通过this访问到的(新创建对象的__ptoto__指向类的原型所指的对象),所以我们将prototype中的属性和方法称之为共有属性和方法 72 | ```javascript 73 | //静态的共有属性和方法,对象不能访问 74 | Book.isChinese = true; 75 | Book.setTime = function() { 76 | console.log('new time'); 77 | } 78 | Book.prototype = function() { 79 | //共有属性和方法 80 | isBook:true; 81 | display = function() { 82 | 83 | } 84 | } 85 | ``` 86 | 通过new关键字创建的对象,实际上是对新对象this的不断赋值,并将prototype指向类的prototype所指向的对象,而类的构造函数外面的通过点语法添加的属性和方法不会添加到新创建的对象上去。 87 | 88 | ### 闭包的实现 89 | 有时候我们经常将类的静态变量通过闭包来实现 90 | ```javascript 91 | var Book = (function() { 92 | //静态私有变量、静态私有方法 93 | var bookNum = 0; 94 | function checkBook(name) { 95 | 96 | } 97 | //返回构造函数 98 | return function(newId,newName,newPrice) { 99 | //私有变量、方法 100 | var name,price; 101 | function checkId(id) { 102 | 103 | } 104 | //特权方法 105 | this.getName = function(){}; 106 | this.getPrice = function(){}; 107 | this.setName = function(){}; 108 | this.setPrice = function(){}; 109 | //公有属性、公有方法 110 | this.id = newId; 111 | this.copy = function(){}; 112 | bookNum++; 113 | if(bookNum>100){ 114 | throw new Error('我们仅出版了100本书'); 115 | } 116 | //构造器 117 | this.setNmae(name); 118 | this.setPrice(price) 119 | } 120 | })() 121 | Book.prototype = { 122 | //静态共有属性、静态公有方法 123 | isJsBook:false, 124 | display:function() { 125 | 126 | } 127 | } 128 | ``` 129 | 130 | 闭包就是有权访问另外一个函数作用域中变量的函数,即在一个函数内部创建另外一个函数。我们将这个闭包作为创建对象的构造函数,这样,它既是闭包又是可实例化对象的函数,即可访问到类作用域中的变量。但是在闭包外部添加原型属性和方法看上去似乎脱离闭包这个类,所以咱们可以用下面的方式来搞一搞 131 | 132 | ```javascript 133 | var Book = (function() { 134 | //静态私有变量 135 | var bookNum = 0; 136 | //静态私有方法 137 | function checkBook(name) { 138 | console.log(name); 139 | } 140 | function _book(newId,newName,newPrice) { 141 | //私有变量 142 | var name,price; 143 | //私有方法 144 | function checkId(id) { 145 | console.log(id) 146 | } 147 | //特权方法 148 | this.getName = function(){}; 149 | this.getPrice = function(){}; 150 | this.setName = function(){}; 151 | this.setPrice = function(){}; 152 | //公有属性 153 | this.id = newId; 154 | //公有方法 155 | this.copy = function(){}; 156 | bookNum++; 157 | if(bookNum>100) 158 | throw new Error('我们仅仅出版了100本书'); 159 | //构造器 160 | this.setName(name); 161 | this.setPrice(price) 162 | } 163 | _book.prototype = { 164 | //静态共有属性、方法 165 | isJSBook:false, 166 | display:function() { 167 | console.log('display') 168 | } 169 | } 170 | return _book; 171 | })() 172 | ``` 173 | 174 | ## 继承 175 | ### 类式继承 176 | ```javascript 177 | function SuperClass() { 178 | this.superValue = true; 179 | } 180 | SuperClass.prototype.getSuperValue = function() { 181 | return this.superValue; 182 | } 183 | 184 | function SubClass() { 185 | this.subValue = false; 186 | } 187 | SubClass.prototype = new SubClass(); 188 | 189 | SubClass.prototype.getSubValue = function() { 190 | return this.subValue; 191 | } 192 | ``` 193 | 继承非常简单,就是声明两个类而已,不过类式继承需要将第一个类的实例赋值给第二个类的原型,因为类的原型对象作用就是为类的原型添加共有方法,但是类不能直接访问这些属性和方法,必须通过原型prototype来访问。而我们实例化一个父类的时候,新创建的对象复制了父类构造函数的属性和方法并将原型__proto__指向父类的原型对象,这样就拥有了父类原型对象的属性和方法,并且这个新创建的对象可以直接访父类原型对象上的属性和方法。而且新创建的对象不仅仅可以访问父类原型上的属性和方法,同样可以访问父类构造函数中复制的属性和方法。将这个对象赋值给子类的原型,那么这个子类的原型同样可以访问父类原型上的属性和方法与从父类构造函数中复制的属性和方法。 194 | 195 | 另外,我们可以通过instanceof来检测某个对象是否为某个类的实例 196 | ```javascript 197 | var instance = new SubClass(); 198 | 199 | console.log(instance instanceof SuperClass) 200 | console.log(instance instanceof SubClass) 201 | console.log(SubClass instanceof SuperClass) 202 | ``` 203 | 204 | 关于结果大家可以自行尝试。注意,instanceof是判断对象是否是后面类的实例,它并不表示二者的继承。 205 | ```javascript 206 | console.log(SubClass.prototype instanceof SuperClass); 207 | ``` 208 | 209 | 但是这种类式继承有两个缺点,其一,由于子类通过其原型prototype对父类实例化,继承了父类,所以说父类中如果共有属性是引用类型,就会在子类中被所有的实例所共享,因此一个子类的实例更改子类原型从父类构造函数中继承的共有属性就会直接影响到其他的子类。 210 | ```javascript 211 | function SuperClass() { 212 | this.books = ['js','css']; 213 | } 214 | function SubClass() {} 215 | SubClass.prototype = new SuperClass(); 216 | 217 | var instance1 = new SubClass(); 218 | var instance2 = new SubClass(); 219 | console.log(instance2.books); 220 | instance1.books.push('html'); 221 | console.log(instance1.books,instance2.books) 222 | ``` 223 | 其二,由于子类实现的继承是靠其原型prototype对父类进行实例化实现的,因此在创建父类的时候,是无法向父类传递参数的。因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化 224 | 225 | ### 构造函数继承 226 | 直接看代码 227 | ```javascript 228 | function SuperClass(id) { 229 | this.books = ['js','css']; 230 | this.id = id; 231 | } 232 | SuperClass.prototype.showBooks = function() { 233 | console.log(this.books); 234 | } 235 | function SubClass(id) { 236 | //继承父类 237 | SuperClass.call(this,id); 238 | } 239 | //创建第一个子类实例 240 | var instance1 = new SubClass(10); 241 | //创建第二个子类实例 242 | var instance2 = new SubClass(11); 243 | 244 | instance1.books.push('html'); 245 | console.log(instance1) 246 | console.log(instance2) 247 | instance1.showBooks();//TypeError 248 | ``` 249 | 如上,SuperClass.call(this,id)当然就是构造函数继承的核心语句了,由于call这个方法可以更改函数的作用环境,因此在子类中,对superClass调用这个方法就是将子类中的变量在父类中执行一遍。由于父类中给this绑定属性,因此子类自然也就继承父类的共有属性。由于这种类型的继承没有涉及到原型prototype,所以父类的原型方法自然不会被子类继承,而如果想被子类继承,就必须放到构造函数中,这样创建出来的每一个实例都会单独的拥有一份而不能共用,这样就违背了代码复用的原则,所以综合上述两种,我们提出了组合式继承方法 250 | 251 | ### 组合继承 252 | 253 | 类式继承是通过子类原型prototype对父类实例化实现的,构造函数继承是通过在子类的构造函数作用环境中执行一次父类的构造函数来实现的。 254 | 255 | ```javascript 256 | function SuperClass(name) { 257 | this.name = name; 258 | this.books = ['Js','CSS']; 259 | } 260 | SuperClass.prototype.getBooks = function() { 261 | console.log(this.books); 262 | } 263 | function SubClass(name,time) { 264 | SuperClass.call(this,name); 265 | this.time = time; 266 | } 267 | SubClass.prototype = new SuperClass(); 268 | 269 | SubClass.prototype.getTime = function() { 270 | console.log(this.time); 271 | } 272 | ``` 273 | 274 | 如上,我们就解决了之前说到的一些问题,但是是不是从代码看,还是有些不爽呢?至少这个SuperClass的构造函数执行了两遍就感觉非常的不妥。 275 | 276 | ### 原型式继承 277 | 原型式继承大致的实现方式是这个样子的 278 | ```javascript 279 | function inheritObject(o) { 280 | //申明一个过渡对象 281 | function F() { } 282 | //过渡对象的原型继承父对象 283 | F.prototype = o; 284 | //返回过渡对象的实例,该对象的原型继承了父对象 285 | return new F(); 286 | } 287 | ``` 288 | 其实这种方式和类式继承非常的相似,他只是对类式继承的一个封装,其中的过渡对象就相当于类式继承的子类,只不过在原型继承中作为一个普通的过渡对象存在,目的是为了创建要返回的新的实例对象。 289 | 290 | ```javascript 291 | var book = { 292 | name:'js book', 293 | likeBook:['css Book','html book'] 294 | } 295 | var newBook = inheritObject(book); 296 | newBook.name = 'ajax book'; 297 | newBook.likeBook.push('react book'); 298 | var otherBook = inheritObject(book); 299 | otherBook.name = 'canvas book'; 300 | otherBook.likeBook.push('node book'); 301 | console.log(newBook,otherBook); 302 | ``` 303 | 如上代码我们可以看出,原型式继承和类式继承一个样子,对于引用类型的变量,还是存在子类实例共享的情况。 304 | 305 | 所以,我们还有下面的寄生式继承 306 | 307 | ### 寄生式继承 308 | 直接看代码 309 | ```javascript 310 | var book = { 311 | name:'js book', 312 | likeBook:['html book','css book'] 313 | } 314 | function createBook(obj) { 315 | //通过原型方式创建新的对象 316 | var o = new inheritObject(obj); 317 | // 拓展新对象 318 | o.getName = function(name) { 319 | console.log(name) 320 | } 321 | // 返回拓展后的新对象 322 | return o; 323 | } 324 | ``` 325 | 其实寄生式继承就是对原型继承的拓展,一个二次封装的过程,这样新创建的对象不仅仅有父类的属性和方法,还新增了别的属性和方法。 326 | 327 | 328 | ### 寄生组合式继承 329 | 回到之前的组合式继承,那时候我们将类式继承和构造函数继承组合使用,但是存在的问题就是子类不是父类的实例,而子类的原型是父类的实例,所以才有了寄生组合式继承。 330 | 331 | 而寄生组合式继承是寄生式继承和构造函数继承的组合。但是这里寄生式继承有些特殊,这里他处理不是对象,而是类的原型。 332 | 333 | ```javascript 334 | function inheritPrototype(subClass,superClass) { 335 | // 复制一份父类的原型副本到变量中 336 | var p = inheritObject(superClass.prototype); 337 | // 修正因为重写子类的原型导致子类的constructor属性被修改 338 | p.constructor = subClass; 339 | // 设置子类原型 340 | subClass.prototype = p; 341 | } 342 | ``` 343 | 组合式继承中,通过构造函数继承的属性和方法都是没有问题的,所以这里我们主要探究通过寄生式继承重新继承父类的原型。我们需要继承的仅仅是父类的原型,不用去调用父类的构造函数。换句话说,在构造函数继承中,我们已经调用了父类的构造函数。因此我们需要的就是父类的原型对象的一个副本,而这个副本我们可以通过原型继承拿到,但是这么直接赋值给子类会有问题,因为对父类原型对象复制得到的复制对象p中的constructor属性指向的不是subClass子类对象,因此在寄生式继承中要对复制对象p做一次增强,修复起constructor属性指向性不正确的问题,最后将得到的复制对象p赋值给子类原型,这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。 344 | 345 | 346 | ```javascript 347 | function inheritPrototype(subClass,superClass) { 348 | // 复制一份父类的原型副本到变量中 349 | var p = inheritObject(superClass.prototype); 350 | // 修正因为重写子类的原型导致子类的constructor属性被修改 351 | p.constructor = subClass; 352 | // 设置子类原型 353 | subClass.prototype = p; 354 | } 355 | function inheritObject(o) { 356 | //申明一个过渡对象 357 | function F() { } 358 | //过渡对象的原型继承父对象 359 | F.prototype = o; 360 | //返回过渡对象的实例,该对象的原型继承了父对象 361 | return new F(); 362 | } 363 | function SuperClass(name) { 364 | this.name = name; 365 | this.books=['js book','css book']; 366 | } 367 | SuperClass.prototype.getName = function() { 368 | console.log(this.name); 369 | } 370 | function SubClass(name,time) { 371 | SuperClass.call(this,name); 372 | this.time = time; 373 | } 374 | inheritPrototype(SubClass,SuperClass); 375 | SubClass.prototype.getTime = function() { 376 | console.log(this.time); 377 | } 378 | var instance1 = new SubClass('React','2017/11/11') 379 | var instance2 = new SubClass('Js','2018/22/33'); 380 | 381 | instance1.books.push('test book'); 382 | 383 | console.log(instance1.books,instance2.books); 384 | instance2.getName(); 385 | instance2.getTime(); 386 | ``` 387 | 388 | ![](../../img/QQ20171204-180745@2x.png) 389 | 390 | 这种方式继承其实如上图所示,其中最大的改变就是子类原型中的处理,被赋予父类原型中的一个引用,这是一个对象,因此有一点你需要注意,就是子类在想添加原型方法必须通过prototype.来添加,否则直接赋予对象就会覆盖从父类原型继承的对象了。 391 | 392 | ## 多继承 393 | 由于JavaScript中的继承是通过原型链来实现的,只有一条原型链,所以理论上来说是实现不了继承多个父类的。但是我们可以通过一些小技巧,来实现一个类似的多继承 394 | 395 | ```javascript 396 | var extend = function(target,source) { 397 | // 遍历源对象中的属性 398 | for(var property in source){ 399 | //将源对象中的属性复制到目标对象中 400 | target[property] = source[property]; 401 | } 402 | //返回目标对象 403 | return target; 404 | } 405 | ``` 406 | 当然,此处我们实现的这是浅复制,对于引用类型的它还是无能为力的。jquery中实现了深复制,就是将源对象中的引用类型的属性再执行一遍extend方法而实现。这里我们实现的比较简单。 407 | 408 | ```javascript 409 | var book = { 410 | name:'javascript 设计模式', 411 | alike:['css','html'] 412 | } 413 | var another = { 414 | color:'blue' 415 | }; 416 | extend(another,book); 417 | console.log(another.name); 418 | console.log(another.alike); 419 | another.alike.push('React'); 420 | another.name = '设计模式'; 421 | console.log(another,book); 422 | ``` 423 | 424 | 上面是实现一个对象的赋值,当然,多继承,也就是在外层多套一个循环的事情了,这里就不在赘述了 425 | 426 | ## 多态 427 | 多态,其实就是同一个方法多种的调用方式,在JavaScript中其实有很多种实现方式的。只不过要对传入的参数进行判断以实现多种的调用方式。 428 | 429 | 操作比较常规,这里就不再赘述了。可以参考我的另一篇文章[忍者级别的函数操作](https://github.com/Nealyang/YOU-SHOULD-KNOW-JS/blob/master/doc/basic_js/%E5%BF%8D%E8%80%85%E7%BA%A7%E5%88%AB%E7%9A%84%E6%93%8D%E4%BD%9C%E5%87%BD%E6%95%B0.md) 430 | -------------------------------------------------------------------------------- /doc/basic_js/JavaScript中的跨域总结.md: -------------------------------------------------------------------------------- 1 | # 不就是跨域么。。。慌个xx 2 | 3 | > 前端开发中,跨域使我们经常遇到的一个问题,也是面试中经常被问到的一些问题,所以,这里,我们做个总结。小小问题,不足担心 4 | 5 | 原文地址:[YOU-SHOULD-KNOW-JS](https://github.com/Nealyang/YOU-SHOULD-KNOW-JS) 6 | 7 | ## 什么是跨域 8 | 9 | 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。 10 | 11 | 12 | 同源策略限制了一下行为: 13 | 14 | - Cookie、LocalStorage 和 IndexDB 无法读取 15 | - DOM 和 JS 对象无法获取 16 | - Ajax请求发送不出去 17 | 18 | ## 常见的跨域场景 19 | 20 | 所谓的同源是指,域名、协议、端口均为相同。 21 | 22 | ``` 23 | http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域 24 | 25 | http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同 26 | 27 | http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同 28 | 29 | http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同 30 | 31 | https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同 32 | 33 | localhost 调用 127.0.0.1 跨域 34 | ``` 35 | 36 | ## 跨域的解决办法 37 | 38 | ### jsonp跨域 39 | 40 | jsonp跨域其实也是JavaScript设计模式中的一种代理模式。在html页面中通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的,所以我们可以通过这个“犯罪漏洞”来进行跨域。一般,我们可以动态的创建script标签,再去请求一个带参网址来实现跨域通信 41 | 42 | ```ecmascript 6 43 | //原生的实现方式 44 | let script = document.createElement('script'); 45 | 46 | script.src = 'http://www.nealyang.cn/login?username=Nealyang&callback=callback'; 47 | 48 | document.body.appendChild(script); 49 | 50 | function callback(res) { 51 | console.log(res); 52 | } 53 | ``` 54 | 当然,jquery也支持jsonp的实现方式 55 | ```javascript 56 | $.ajax({ 57 | url:'http://www.nealyang.cn/login', 58 | type:'GET', 59 | dataType:'jsonp',//请求方式为jsonp 60 | jsonpCallback:'callback', 61 | data:{ 62 | "username":"Nealyang" 63 | } 64 | }) 65 | 66 | ``` 67 | 虽然这种方式非常好用,但是一个最大的缺陷是,只能够实现get请求 68 | 69 | ### document.domain + iframe 跨域 70 | 这种跨域的方式最主要的是要求主域名相同。什么是主域名相同呢? 71 | www.nealyang.cn aaa.nealyang.cn ba.ad.nealyang.cn 这三个主域名都是nealyang.cn,而主域名不同的就不能用此方法。 72 | 73 | 假设目前a.nealyang.cn 和 b.nealyang.cn 分别对应指向不同ip的服务器。 74 | 75 | a.nealyang.cn 下有一个test.html文件 76 | 77 | ```html 78 | 79 | 80 | 81 | 82 | html 83 | 84 | 85 | 86 |
A页面
87 | 92 | 105 | 106 | 107 | ``` 108 | 109 | 利用 iframe 加载 其他域下的文件(nealyang.cn/1.html), 同时 document.domain 设置成 nealyang.cn ,当 iframe 加载完毕后就可以获取 nealyang.cn 域下的全局对象, 110 | 此时尝试着去请求 nealyang.cn 域名下的 test.json (此时可以请求接口),就会发现数据请求失败了~~ 惊不惊喜,意不意外!!!!!!! 111 | 112 | 数据请求失败,目的没有达到,自然是还少一步: 113 | 114 | ```html 115 | 116 | 117 | 118 | 119 | html 120 | 121 | 128 | 129 | 130 |
B页面
131 | 132 | 133 | ``` 134 | 135 | 此时在进行刷新浏览器,就会发现数据这次真的是成功了~~~~~ 136 | 137 | ### window.name + iframe 跨域 138 | 139 | window.name属性可设置或者返回存放窗口名称的一个字符串。他的神器之处在于name值在不同页面或者不同域下加载后依旧存在,没有修改就不会发生变化,并且可以存储非常长的name(2MB) 140 | 141 | 假设index页面请求远端服务器上的数据,我们在该页面下创建iframe标签,该iframe的src指向服务器文件的地址(iframe标签src可以跨域),服务器文件里设置好window.name的值,然后再在index.html里面读取改iframe中的window.name的值。完美~ 142 | 143 | ```html 144 | 145 | 153 | 154 | ``` 155 | 当然,这样还是不够的。 156 | 157 | 因为规定如果index.html页面和和该页面里的iframe框架的src如果不同源,则也无法操作框架里的任何东西,所以就取不到iframe框架的name值了,告诉你我们不是一家的,你也休想得到我这里的数据。 158 | 既然要同源,那就换个src去指,前面说了无论怎样加载window.name值都不会变化,于是我们在index.html相同目录下,新建了个proxy.html的空页面,修改代码如下: 159 | 160 | ```html 161 | 162 | 171 | 172 | ``` 173 | 理想似乎很美好,在iframe载入过程中,迅速重置iframe.src的指向,使之与index.html同源,那么index页面就能去获取它的name值了!但是现实是残酷的,iframe在现实中的表现是一直不停地刷新, 174 | 也很好理解,每次触发onload时间后,重置src,相当于重新载入页面,又触发onload事件,于是就不停地刷新了(但是需要的数据还是能输出的)。修改后代码如下: 175 | ```html 176 | 177 | 198 | 199 | ``` 200 | 201 | 所以如上,我们就拿到了服务器返回的数据,但是有几个条件是必不可少的: 202 | - iframe标签的跨域能力 203 | - window.names属性值在文档刷新后依然存在的能力 204 | 205 | ### location.hash + iframe 跨域 206 | 此跨域方法和上面介绍的比较类似,一样是动态插入一个iframe然后设置其src为服务端地址,而服务端同样输出一端js代码,也同时通过与子窗口之间的通信来完成数据的传输。 207 | 208 | 关于锚点相信大家都已经知道了,其实就是设置锚点,让文档指定的相应的位置。锚点的设置用a标签,然后href指向要跳转到的id,当然,前提是你得有个滚动条,不然也不好滚动嘛是吧。 209 | 210 | 而location.hash其实就是url的锚点。比如http://www.nealyang.cn#Nealyang的网址打开后,在控制台输入location.hash就会返回#Nealyang的字段。 211 | 212 | 基础知识补充完毕,下面我们来说下如何实现跨域 213 | 214 | 如果index页面要获取远端服务器的数据,动态的插入一个iframe,将iframe的src执行服务器的地址,这时候的top window 和包裹这个iframe的子窗口是不能通信的,因为同源策略,所以改变子窗口的路径就可以了,将数据当做改变后的路径的hash值加载路径上,然后就可以通信了。将数据加在index页面地址的hash上, 215 | index页面监听hash的变化,h5的hashchange方法 216 | 217 | ```html 218 | 219 | 241 | 242 | ``` 243 | 244 | >补充说明:其实location.hash和window.name都是差不多的,都是利用全局对象属性的方法,然后这两种方法和jsonp也是一样的,就是只能够实现get请求 245 | 246 | ### postMessage跨域 247 | 这是由H5提出来的一个炫酷的API,IE8+,chrome,ff都已经支持实现了这个功能。这个功能也是非常的简单,其中包括接受信息的Message时间,和发送信息的postMessage方法。 248 | 249 | 发送信息的postMessage方法是向外界窗口发送信息 250 | ```javascript 251 | otherWindow.postMessage(message,targetOrigin); 252 | ``` 253 | otherWindow指的是目标窗口,也就是要给哪一个window发送消息,是window.frames属性的成员或者是window.open方法创建的窗口。 254 | Message是要发送的消息,类型为String,Object(IE8、9不支持Obj),targetOrigin是限定消息接受范围,不限制就用星号 * 255 | 256 | 接受信息的message事件 257 | 258 | ```javascript 259 | var onmessage = function(event) { 260 | var data = event.data; 261 | var origin = event.origin; 262 | } 263 | 264 | if(typeof window.addEventListener != 'undefined'){ 265 | window.addEventListener('message',onmessage,false); 266 | }else if(typeof window.attachEvent != 'undefined'){ 267 | window.attachEvent('onmessage', onmessage); 268 | } 269 | ``` 270 | 271 | 举个栗子 272 | 273 | a.html(http://www.nealyang.cn/a.html) 274 | 275 | ```html 276 | 277 | 292 | ``` 293 | 294 | b.html(http://www.neal.cn/b.html) 295 | 296 | ```html 297 | 311 | ``` 312 | 313 | ### 跨域资源共享 CORS 314 | 因为是目前主流的跨域解决方案。所以这里多介绍点。 315 | #### 简介 316 | CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 317 | 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 318 | 319 | CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。 320 | 321 | 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。 322 | 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。 323 | 324 | #### 两种请求 325 | 说起来很搞笑,分为两种请求,一种是简单请求,另一种是非简单请求。只要满足下面条件就是简单请求 326 | 327 | - 请求方式为HEAD、POST 或者 GET 328 | - http头信息不超出一下字段:Accept、Accept-Language 、 Content-Language、 Last-Event-ID、 Content-Type(限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain) 329 | 330 | 为什么要分为简单请求和非简单请求,因为浏览器对这两种请求方式的处理方式是不同的。 331 | 332 | #### 简单请求 333 | 334 | ##### 基本流程 335 | 336 | 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。 337 | 下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。 338 | ``` 339 | GET /cors HTTP/1.1 340 | Origin: http://api.bob.com 341 | Host: api.alice.com 342 | Accept-Language: en-US 343 | Connection: keep-alive 344 | User-Agent: Mozilla/5.0 345 | ... 346 | ``` 347 | Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。 348 | 349 | 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。 350 | 浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。 351 | 352 | 注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。 353 | 354 | 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。 355 | ``` 356 | Access-Control-Allow-Origin: http://api.bob.com 357 | Access-Control-Allow-Credentials: true 358 | Access-Control-Expose-Headers: FooBar 359 | Content-Type: text/html; charset=utf-8 360 | ``` 361 | 362 | 上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头 363 | 364 | - Access-Control-Allow-Origin :该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求 365 | - Access-Control-Allow-Credentials: 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。 366 | - Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。 367 | 368 | ##### withCredentials 属性 369 | 上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。 370 | 371 | 另一方面,开发者必须在AJAX请求中打开withCredentials属性。 372 | ```javascript 373 | var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 374 | 375 | // 前端设置是否带cookie 376 | xhr.withCredentials = true; 377 | 378 | xhr.open('post', 'http://www.domain2.com:8080/login', true); 379 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 380 | xhr.send('user=admin'); 381 | 382 | xhr.onreadystatechange = function() { 383 | if (xhr.readyState == 4 && xhr.status == 200) { 384 | alert(xhr.responseText); 385 | } 386 | }; 387 | 388 | // jquery 389 | $.ajax({ 390 | ... 391 |    xhrFields: { 392 |        withCredentials: true // 前端设置是否带cookie 393 |    }, 394 |    crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie 395 | ... 396 | }); 397 | ``` 398 | 399 | 否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。 400 | 但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。 401 | 402 | 需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。 403 | 404 | #### 非简单请求 405 | 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。 406 | 407 | 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。 408 | 409 | 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。 410 | ```javascript 411 | var url = 'http://api.alice.com/cors'; 412 | var xhr = new XMLHttpRequest(); 413 | xhr.open('PUT', url, true); 414 | xhr.setRequestHeader('X-Custom-Header', 'value'); 415 | xhr.send(); 416 | ``` 417 | 418 | 浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。 419 | 420 | ``` 421 | OPTIONS /cors HTTP/1.1 422 | Origin: http://api.bob.com 423 | Access-Control-Request-Method: PUT 424 | Access-Control-Request-Headers: X-Custom-Header 425 | Host: api.alice.com 426 | Accept-Language: en-US 427 | Connection: keep-alive 428 | User-Agent: Mozilla/5.0... 429 | ``` 430 | "预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。 431 | 432 | 除了Origin字段,"预检"请求的头信息包括两个特殊字段。 433 | - Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。 434 | - Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header 435 | 436 | ##### 预检请求的回应 437 | 服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应 438 | 439 | ``` 440 | HTTP/1.1 200 OK 441 | Date: Mon, 01 Dec 2008 01:15:39 GMT 442 | Server: Apache/2.0.61 (Unix) 443 | Access-Control-Allow-Origin: http://api.bob.com 444 | Access-Control-Allow-Methods: GET, POST, PUT 445 | Access-Control-Allow-Headers: X-Custom-Header 446 | Content-Type: text/html; charset=utf-8 447 | Content-Encoding: gzip 448 | Content-Length: 0 449 | Keep-Alive: timeout=2, max=100 450 | Connection: Keep-Alive 451 | Content-Type: text/plain 452 | ``` 453 | 454 | 上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。 455 | 456 | 如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。 457 | 458 | 服务器回应的其他CORS相关字段如下: 459 | ``` 460 | Access-Control-Allow-Methods: GET, POST, PUT 461 | Access-Control-Allow-Headers: X-Custom-Header 462 | Access-Control-Allow-Credentials: true 463 | Access-Control-Max-Age: 1728000 464 | ``` 465 | - Access-Control-Allow-Methods:该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。 466 | - Access-Control-Allow-Headers:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。 467 | - Access-Control-Allow-Credentials: 该字段与简单请求时的含义相同。 468 | - Access-Control-Max-Age: 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。 469 | 470 | ##### 浏览器正常请求回应 471 | 472 | 一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。 473 | 474 | ``` 475 | PUT /cors HTTP/1.1 476 | Origin: http://api.bob.com 477 | Host: api.alice.com 478 | X-Custom-Header: value 479 | Accept-Language: en-US 480 | Connection: keep-alive 481 | User-Agent: Mozilla/5.0... 482 | ``` 483 | 484 | 浏览器的正常CORS请求。上面头信息的Origin字段是浏览器自动添加的。下面是服务器正常的回应。 485 | 486 | ``` 487 | Access-Control-Allow-Origin: http://api.bob.com 488 | Content-Type: text/html; charset=utf-8 489 | ``` 490 | Access-Control-Allow-Origin字段是每次回应都必定包含的 491 | 492 | #### 结束语 493 | 494 | CORS与JSONP的使用目的相同,但是比JSONP更强大。JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。 495 | 496 | ### WebSocket协议跨域 497 | 498 | WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。 499 | 500 | 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。 501 | 502 | 前端代码: 503 | 504 | ```html 505 |
user input:
506 | 507 | 527 | ``` 528 | node Server 529 | ```node 530 | var http = require('http'); 531 | var socket = require('socket.io'); 532 | 533 | // 启http服务 534 | var server = http.createServer(function(req, res) { 535 | res.writeHead(200, { 536 | 'Content-type': 'text/html' 537 | }); 538 | res.end(); 539 | }); 540 | 541 | server.listen('8080'); 542 | console.log('Server is running at port 8080...'); 543 | 544 | // 监听socket连接 545 | socket.listen(server).on('connection', function(client) { 546 | // 接收信息 547 | client.on('message', function(msg) { 548 | client.send('hello:' + msg); 549 | console.log('data from client: ---> ' + msg); 550 | }); 551 | 552 | // 断开处理 553 | client.on('disconnect', function() { 554 | console.log('Client socket has closed.'); 555 | }); 556 | }); 557 | ``` 558 | 559 | ### node代理跨域 560 | 561 | node中间件实现跨域代理,是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。 562 | 563 | 564 | 利用node + express + http-proxy-middleware搭建一个proxy服务器 565 | 566 | 前端代码 567 | ```javascript 568 | var xhr = new XMLHttpRequest(); 569 | 570 | // 前端开关:浏览器是否读写cookie 571 | xhr.withCredentials = true; 572 | 573 | // 访问http-proxy-middleware代理服务器 574 | xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true); 575 | xhr.send(); 576 | ``` 577 | 后端代码 578 | ```javascript 579 | var express = require('express'); 580 | var proxy = require('http-proxy-middleware'); 581 | var app = express(); 582 | 583 | app.use('/', proxy({ 584 | // 代理跨域目标接口 585 | target: 'http://www.domain2.com:8080', 586 | changeOrigin: true, 587 | 588 | // 修改响应头信息,实现跨域并允许带cookie 589 | onProxyRes: function(proxyRes, req, res) { 590 | res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); 591 | res.header('Access-Control-Allow-Credentials', 'true'); 592 | }, 593 | 594 | // 修改响应信息中的cookie域名 595 | cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 596 | })); 597 | 598 | app.listen(3000); 599 | console.log('Proxy server is listen at port 3000...'); 600 | ``` 601 | 602 | ### nginx代理跨域 603 | NGINX其实个人没有怎么玩过,所以暂且也就不能误人子弟了,原谅笔者才疏尚浅~ 有机会学习研究再回来补充~~ 604 | 605 | ## 交流 606 | 607 | 欢迎加入react技术栈、前端技术杂谈QQ群 608 | 609 | 610 | >前端技术杂谈:604953717
611 | react技术栈:398240621 612 | 613 | 614 | 615 | ## 参考文档 616 | 617 | [http://www.ruanyifeng.com/blog/2016/04/cors.html](http://www.ruanyifeng.com/blog/2016/04/cors.html) 618 | [https://segmentfault.com/a/1190000011145364](https://segmentfault.com/a/1190000011145364) 619 | --------------------------------------------------------------------------------