├── _config.yml ├── fb.md ├── spider-for-ssr.md ├── this.md ├── viewport.md ├── webpack-loader.md ├── xss.md ├── zhihu-spider.md └── 答题救不了前端新人.md /_config.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fb.md: -------------------------------------------------------------------------------- 1 | # JavaScript 函数式编程到底是个啥 2 | 3 | 随着大前端时代的到来,在产品开发过程中,前端所占业务比重越来越大、交互越来越重。传统的老夫拿起JQuery就是一把梭应付当下重交互页面已经十分乏力。于是乎有了Angular,React,Vue这些现代框架。 4 | 5 | 但随之而来的还有大量的新知识新名词,如MVC,MVVM,Flux这些设计模式就弄得很多同学傻傻分不清。这时候又见到别人讨论什么函数式编程,更是一脸懵逼了。 6 | 7 | 我们大多听过面向对象编程,面向过程编程,那啥又是函数式编程呢?在我们前端开发中又有哪些应用场景?我抱着这个疑惑,初步的学习了下。 8 | 9 | ## 函数式编程 10 | 11 | ### 定义 12 | 13 | 函数式编程(Functional Programming,后面简称FP),维基百科的定义是: 14 | 15 | >是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。 16 | 17 | 我来尝试理解下这个定义,好像就是说,在敲代码的时候,我要把过程逻辑写成函数,定义好输入参数,只关心它的输出结果。而且可以把函数作为输入输出。感觉好像平常写js时,就是这样的嘛! 18 | 19 | ### 特性 20 | 21 | 网上FP的定义与特性琳琅满目。各种百科、博客、一些老师的网站上都有大同小异的介绍。为了方便阅读,我列下几个好像比较重要的特性,并附上我的第一眼理解。 22 | 23 | 1. **函数是一等公民**。就是说函数可以跟其他变量一样,可以作为其他函数的输入输出。喔,回调函数就是典型应用。 24 | 25 | 2. **不可变量**。就是说,不能用var跟let咯。按这要求,我似乎有点难写代码。 26 | 27 | 3. **纯函数**。就是没有副作用的函数。这个好理解,就是不修改函数外部的变量。 28 | 29 | 4. **引用透明**。这个也好理解,就是说同样的输入,必定是同样的输出。函数内部不依赖外部状态,如一些全局变量。 30 | 31 | 5. **惰性计算**。大意就是:一个表达式绑定的变量,不是声明的时候就计算出来,而是真正用到它的时候才去计算。 32 | 33 | 还有一些衍生的特性,如柯里化与组合,三言两语说不清,就不阐述了,有兴趣的同学可以自己再了解了解。 34 | 35 | ## FP在JavaScript中的应用 36 | 37 | React就是典型的FP。它不同于Vue这样的MVVM框架,它仅仅是个View层。 38 | `ReactView = render(data)` 它只关心你的输入,最终给你返回相应视图。所以你休想在react组件中去修改父组件的状态,更没有与dom的双向绑定。 39 | 40 | 这个是框架上的应用,那么在我们平常书写JavaScript时有哪些应用呢?换句话说,平常书写js时候,遇到什么情况,我们采用FP会更好。 41 | 42 | 从最常见的入手吧,如典型的操作数组: 43 | ```javascript 44 | // 从users中筛选出年龄大于15岁的人的名字 45 | const users = [ 46 | { 47 | age: 10, 48 | name: '张三', 49 | }, { 50 | age: 20, 51 | name: '李四' 52 | }, { 53 | age: 30, 54 | name: '王五' 55 | } 56 | ]; 57 | 58 | // 过程式 59 | const names = []; 60 | for (let i = 0; i < users.length; i++) { 61 | if (users[i].age > 15) { 62 | names.push(users[i].name); 63 | } 64 | } 65 | // 函数式 66 | const names = users.filter(u => u.age > 15).map(u => u.name); 67 | 68 | ``` 69 | 70 | 嗯,代码精简了很多,但是貌似带来了更大的开销。如果是非常大的数据,非常多的筛选工作,那就会循环多次。 71 | 72 | 这里得想到刚刚的惰性计算。按照惰性求值的要求,应该是要最后返回结果时,才真正去筛选年纪并得到姓名数组。 73 | 74 | 然而JavaScript的数组并不支持惰性求值。这时候我们得上一些工具库,如[Lodash](https://lodash.com/)。可以看下它文档中的例子:[_.chain](https://lodash.com/docs/4.17.4#chain)。 75 | 76 | 好像也没好到哪里去啊,不就是把多行代码变一行嘛?说的那么玄乎,还多了性能开销,然后又跟我说得上个工具库。。。 77 | 78 | 说的好像很有道理,但是for循环是有个弊端的,它产生了变量i,而这个变量又是不可控的,如果业务逻辑一复杂,谁知道它循环到什么时候i有没有发生变化,然后导致循环出问题呢? 79 | 80 | 我们再看一个与DOM交互的场景: 81 | 假如页面有一个按钮`button`,我们需要求出用户点击了几次,但是一秒钟内重复点击的不算。传统方法会这么写。 82 | ``` javascript 83 | var count = 0; 84 | var rate = 1000; 85 | var lastClick = Date.now() - rate; 86 | var button = document.querySelector('button'); 87 | button.addEventListener('click', () => { 88 | if (Date.now() - lastClick >= rate) { 89 | console.log(`Clicked ${++count} times`); 90 | lastClick = Date.now(); 91 | } 92 | }); 93 | ``` 94 | 妥,完全没问题。但是发现多了很多状态,count,rate,lastClick,还得对比来对比去。那如果用FP会是怎么样的呢? 95 | 96 | 抱歉。。。没法写。。。除非很强大的编程能力,自己封装好方法去处理。所以在这里,我们可以上个工具---[Rx.js](http://reactivex.io/rxjs/manual/overview.html),上述的例子就是rxjs中引用的,我们看它是如何优雅地处理的。 97 | 98 | ``` javascript 99 | var button = document.querySelector('button'); 100 | Rx.Observable.fromEvent(button, 'click') 101 | .throttleTime(1000) // 每隔1000毫秒才能触发事件 102 | .scan(count => count + 1, 0) // 求值,默认值是0 103 | .subscribe(count => console.log(`Clicked ${count} times`)); // 订阅结果、输出值 104 | ``` 105 | 巧夺天工!再也不用去管理状态了,不需要声明一堆变量,修改来修改去,判断来判断去,简直完美。 106 | 107 | 平常我们有很多需要更新dom的异步操作,如搜索行为:用户连续输入查询值,如果停顿半秒就执行搜索,如果搜索了多次,发起了多次请求,那只返回最终输入的那次搜索结果。 108 | 109 | 闭上眼想想,你之前是怎么实现的。反正我都是设置开始时间,结束时间,上次时间,等等变量。繁琐,而且不可控。 110 | 111 | 当我们以FP的思想去实现时,就会想方设法的减少变量,来优雅程序。最常见的方法就是用下别人的工具库来实现它。当然有些简单的场景也可以自己实现,最主要的还是要有这个意识。 112 | 113 | 其实我们平常已经写了一些FP了,只是我们没意识到,或者没怎么写好。就好比闭包,很多人都不了解闭包的概念,但实际上已经写了很多闭包代码。其实闭包本身也是函数式编程的一个应用。 114 | 115 | 鉴于我自己理解也不深,没法多阐述FP的应用,大家如果有兴趣,可以多了解了解。 116 | 117 | ## FP在JavaScript中的优劣势 118 | 119 | 总结一下FP的优劣,以便于我们在实际开发中,能更好的抉择是否采用FP。 120 | 121 | ### 优势 122 | 123 | 1. **更好的管理状态**。因为它的宗旨是无状态,或者说更少的状态。而平常DOM的开发中,因为DOM的视觉呈现依托于状态变化,所以不可避免的产生了非常多的状态,而且不同组件可能还相互依赖。以FP来编程,能最大化的减少这些未知、优化代码、减少出错情况。 124 | 125 | 2. **更简单的复用**。极端的FP代码应该是每一行代码都是一个函数,当然我们不需要这么极端。我们尽量的把过程逻辑以更纯的函数来实现,固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响。 126 | 127 | 3. **更优雅的组合**。往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。参考上面第二点,更强的复用性,带来更强大的组合性。 128 | 129 | 4. 隐性好处。减少代码量,提高维护性。 130 | 131 | ### 劣势 132 | 133 | 1. JavaScript不能算是严格意义上的函数式语言,很多函数式编程的特性并没有。比如上文说的数组的惰性链求值。为了实现它就得上工具库,或者自己封装实现,提高了代码编写成本。 134 | 135 | 2. 跟过程式相比,它并没有提高性能。有些地方,如果强制用FP去写,由于没有中间变量,还可能会降低性能。 136 | 137 | 3. 代码不易读。这个因人而异,因码而已。特别熟悉FP的人可能会觉得这段代码一目了然。而不熟悉的人,遇到写的晦涩的代码,看着一堆堆lambda演算跟匿名函数 `() => () => ()` 瞬间就懵逼了。看懂代码,得脑子里先演算半小时。 138 | 139 | 4. 学习成本高。一方面继承于上一点。另一方面,很多前端coder,就是因为相对不喜欢一些底层的抽象的编程语言,才来踏入前端坑,你现在又让他们一头扎入FP,显得手足无措。 140 | 141 | ## 总结 142 | 143 | 个人觉得,FP还是好的。对于开发而言,确确实实能优化我们的代码,熟悉之后,也能提高编程效率。对于编程本身而言,也能拓展我们的思维,不局限在过程式的编程代码。 144 | 145 | 在编写JS中,可以尽量的运用FP的思维,如不可变量、纯函数、惰性求值。但也不必教条式的遵循函数式编程,一定要怎样怎样。比如我们看下知乎大V某温的一个回答:[传送门](https://www.zhihu.com/question/59871249/answer/171201717)。 146 | 147 | 唉,做个页面仔不容易啊。但是不想当大牛的页面仔不是好页面仔! 148 | 149 | 150 | ## 参考 151 | 152 | 1. [函数式编程入门教程-阮一峰](http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html) 153 | 2. [函数编程语言-维基百科](https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B8%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80) 154 | 3. [前端开发js函数式编程真实用途体现在哪里?-知乎答者](https://www.zhihu.com/question/59871249) 155 | -------------------------------------------------------------------------------- /spider-for-ssr.md: -------------------------------------------------------------------------------- 1 | 前端发展到现在,SPA应该已经被应用的非常广了。可惜的是,我们前进的是快,而人家搜索引擎爬虫跟用户的浏览器设备还跟不上脚步。辛辛苦苦写好的单页应用,结果到了SEO跟浏览器兼容这一步懵逼了。 2 | 3 | 很多同学肯定都想过服务端渲染的问题。然而一看vue、react关于服务端渲染的文档,可能就被唬住了。之前写好的并不能无缝迁移。而且,每当有个项目,就需要去run一套node服务。当然,架构能力好些的朋友,可以做好集中化管理。 4 | 5 | 所以,当我想在项目中,采用vue或者react的时候,就遇到这些非常大的阻力。正当我头疼脑热的时候呢,我发现了一条新途径。 6 | 7 | 在前不久呢,同事在群里分享了[puppeteer](https://github.com/GoogleChrome/puppeteer),它GitHub的介绍如下: 8 | 9 | > Puppeteer is a Node library which provides a high-level API to control headless Chrome over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome. 10 | 11 | 大意就是说,一个提供操作Headless Chrome的API的node库。 12 | 13 | 再具体的说,就是能在node环境中,通过一些API,来“模拟”真实chrome访问页面,并对其进行模拟用户操作、获取DOM等。 14 | 15 | 那既然它能够像真实Chrome那样去访问页面并且输出渲染后的html,我为什么不能通过它来给我们做服务端渲染呢? 16 | 17 | 设想一下,我们有这样一个服务A,它能够像chrome一样访问指定页面,并把最终页面上的dom返回给你。 18 | 19 | 而你原本的业务服务器B,只需要判断是爬虫,或者低版本IE来访问时,调取该服务,得到html,将html返回给用户,这就实现了服务端渲染。大致流程图如下: 20 | 21 | ![](https://user-gold-cdn.xitu.io/2017/9/30/241c30c3fb75ba37c6178cd447de2ce3) 22 | 23 | 有这样一个思路后,我们就想办法来实践它。实践的过程,就是解决问题的过程。仔细想想,我们会遇到如下几个问题: 24 | 25 | > Q1: 即使是模拟Chrome去请求页面,很多时候视图也是异步渲染的。比如先请求列表接口,得到数据再渲染出列表DOM。这个时间,我们并没有办法把控。那这个服务,到底时候才应该把加载完成的HTML返回呢? 26 | 27 | 遇到问题时,首先可以看看人家的文档 [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md)。欣喜的是,我们找到了如下几个方法: 28 | ```javascript 29 | page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]) 30 | page.waitForFunction(pageFunction[, options[, ...args]]) 31 | page.waitForNavigation(options) 32 | page.waitForSelector(selector[, options]) 33 | ``` 34 | 我们可以通过一些设定,让页面在某种情况下才返回。比如我们通过设定 `page.waitForSelector('#app')`, 让页面出现 `id="app"` 的元素时,才把html内容返回。 35 | 36 | 或者通过设定 `page.waitForFunction('window.innerWidth < 100')`,当页面宽度小于100px时,才将此时的html内容返回。 37 | 38 | 通过这些方法,我们就能有办法控制,想要输给爬虫的,是什么时候、什么样的页面。 39 | 40 | > Q2: 如果IE用户访问量比较大怎么办。我们虽然通过这样的系统,让本渲染不出页面的部分浏览器(IE9以下)能够渲染出页面了。但这样的请求过程相对而言会更耗时,这不是很合理。 41 | 42 | 那我们只要做一个缓存系统便好。每次请求,都会去判断此请求是否存在未过期的缓存HTML,如果存在,则直接返回缓存HTML,否则再去请求页面,保存缓存。 43 | 44 | > Q3: 虽然页面是出来了,IE用户还是没办法做一些JS的交互。 45 | 46 | 这个我们没办法在服务层上去解决了,但我们可以在前端上做更友好的交互提示。如果判断用户是低版本IE,则出现一个小Tip,提示用户下载更好的浏览器,获取更好的体验。 47 | 48 | > Q4: 单页应用的路由多是用锚点(哈希模式)来做的,而哈希参数,服务端无法获取,那就没办法请求正确的页面了。 49 | 50 | 这个有办法解决,可以采用HTML History模式的路由,如[vue-router](https://router.vuejs.org/zh-cn/essentials/history-mode.html),然后路由链接最好以生成a标签+href的模式写在页面中,而不是`onclick`后js跳转,这样爬虫能最好的爬取整站页面。 51 | 52 | 当问题都想到办法解决后,我们就能开始真正coding了。 53 | 54 | 啪啪啪,啪啪啪 => [SSR-SERVICE](https://github.com/DXY-F2E/ssr-service) 55 | 56 | 好,然后就好了,不到200行的代码,我们就实现了一个 通用化的、服务化的、单页应用服务端渲染解决方案。 57 | 58 | 59 | --[阅读原文](https://github.com/wuomzfx/blog/blob/master/spider-for-ssr.md) @[相学长](https://www.zhihu.com/people/xiang-xue-zhang) 60 | 61 | --转载请先经过本人授权。 62 | -------------------------------------------------------------------------------- /this.md: -------------------------------------------------------------------------------- 1 | 日常开发中,我们经常用到this。例如用Jquery绑定事件时,this指向触发事件的DOM元素;编写Vue、React组件时,this指向组件本身。对于新手来说,常会用一种意会的感觉去判断this的指向。以至于当遇到复杂的函数调用时,就分不清this的真正指向。 2 | 3 | 本文将通过两道题去慢慢分析this的指向问题,并涉及到函数作用域与对象相关的点。最终给大家带来真正的理论分析,而不是简简单单的一句话概括。 4 | 5 | 相信若是对this稍有研究的人,都会搜到这句话:**this总是指向调用该函数的对象**。 6 | 7 | 然而箭头函数并不是如此,于是大家就会遇到如下各式说法: 8 | 9 | 1. 箭头函数的this指向外层函数作用域中的this。 10 | 2. 箭头函数的this是定义函数时所在上下文中的this。 11 | 3. 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。 12 | 13 | 各式各样的说法都有,乍看下感觉说的差不多。废话不多说,凭着你之前的理解,来先做一套题吧(非严格模式下)。 14 | 15 | ```javascript 16 | /** 17 | * Question 1 18 | */ 19 | 20 | var name = 'window' 21 | 22 | var person1 = { 23 | name: 'person1', 24 | show1: function () { 25 | console.log(this.name) 26 | }, 27 | show2: () => console.log(this.name), 28 | show3: function () { 29 | return function () { 30 | console.log(this.name) 31 | } 32 | }, 33 | show4: function () { 34 | return () => console.log(this.name) 35 | } 36 | } 37 | var person2 = { name: 'person2' } 38 | 39 | person1.show1() 40 | person1.show1.call(person2) 41 | 42 | person1.show2() 43 | person1.show2.call(person2) 44 | 45 | person1.show3()() 46 | person1.show3().call(person2) 47 | person1.show3.call(person2)() 48 | 49 | person1.show4()() 50 | person1.show4().call(person2) 51 | person1.show4.call(person2)() 52 | ``` 53 | 大致意思就是,有两个对象`person1`,`person2`,然后花式调用person1中的四个show方法,预测真正的输出。 54 | 55 | 你可以先把自己预测的答案按顺序记在本子上,然后再往下拉看正确答案。 56 | 57 | 58 | *** 59 | *** 60 | 正确答案选下: 61 | ```javascript 62 | person1.show1() // person1 63 | person1.show1.call(person2) // person2 64 | 65 | person1.show2() // window 66 | person1.show2.call(person2) // window 67 | 68 | person1.show3()() // window 69 | person1.show3().call(person2) // person2 70 | person1.show3.call(person2)() // window 71 | 72 | person1.show4()() // person1 73 | person1.show4().call(person2) // person1 74 | person1.show4.call(person2)() // person2 75 | ``` 76 | 77 | 对比下你刚刚记下的答案,是否有不一样呢?让我们尝试来最开始那些理论来分析下。 78 | 79 | `person1.show1()`与`person1.show1.call(person2)`好理解,验证了**谁调用此方法,this就是指向谁**。 80 | 81 | `person1.show2()`与`person1.show2.call(person2)`的结果用上面的定义解释,就开始让人不理解了。 82 | 83 | 它的执行结果说明this指向的是window。那就不是所谓的定义时所在的对象。 84 | 85 | 如果说是外层函数作用域中的this,实际上并没有外层函数了,外层就是全局环境了,这个说法也不严谨。 86 | 87 | 只有**定义函数时所在上下文中的this**这句话算能描述现在这个情况。 88 | 89 | `person1.show3`是一个高阶函数,它返回了一个函数,分步走的话,应该是这样: 90 | ```javascript 91 | var func = person3.show() 92 | 93 | func() 94 | ``` 95 | 从而导致最终调用函数的执行环境是window,但并不是window对象调用了它。所以说,**this总是指向调用该函数的对象**,这句话还得补充一句:**在全局函数中,this等于window**。 96 | 97 | `person1.show3().call(person2)` 与 `person1.show3.call(person2)()` 也好理解了。前者是通过person2调用了最终的打印方法。后者是先通过person2调用了person1的高阶函数,然后再在全局环境中执行了该打印方法。 98 | 99 | `person1.show4()()`,`person1.show4().call(person2)`都是打印person1。这好像又印证了那句:**箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象**。因为即使我用过person2去调用这个箭头函数,它指向的还是person1。 100 | 101 | 然而`person1.show4.call(person2)()`的结果又是person2。this值又发生改变,看来上述那句描述又走不通了。一步步来分析,先通过person2执行了show4方法,此时show4第一层函数的this指向的是person2。所以箭头函数输出了person2的name。也就是说,箭头函数的this指向的是**谁调用箭头函数的外层function,箭头函数的this就是指向该对象,如果箭头函数没有外层函数,则指向window**。这样去理解show2方法,也解释的通。 102 | 103 | 这句话就对了么?在我们学习的过程中,我们总是想以总结规律的方法去总结结论,并且希望结论越简单越容易描述就越好。实际上可能会错失真理。 104 | 105 | 下面我们再做另外一个相似的题目,通过构造函数来创建一个对象,并执行相同的4个show方法。 106 | ```javascript 107 | /** 108 | * Question 2 109 | */ 110 | var name = 'window' 111 | 112 | function Person (name) { 113 | this.name = name; 114 | this.show1 = function () { 115 | console.log(this.name) 116 | } 117 | this.show2 = () => console.log(this.name) 118 | this.show3 = function () { 119 | return function () { 120 | console.log(this.name) 121 | } 122 | } 123 | this.show4 = function () { 124 | return () => console.log(this.name) 125 | } 126 | } 127 | 128 | var personA = new Person('personA') 129 | var personB = new Person('personB') 130 | 131 | personA.show1() 132 | personA.show1.call(personB) 133 | 134 | personA.show2() 135 | personA.show2.call(personB) 136 | 137 | personA.show3()() 138 | personA.show3().call(personB) 139 | personA.show3.call(personB)() 140 | 141 | personA.show4()() 142 | personA.show4().call(personB) 143 | personA.show4.call(personB)() 144 | ``` 145 | 同样的,按照之前的理解,再次预计打印结果,把答案记下来,再往下拉看正确答案。 146 | 147 | *** 148 | *** 149 | 正确答案选下: 150 | ```javascript 151 | personA.show1() // personA 152 | personA.show1.call(personB) // personB 153 | 154 | personA.show2() // personA 155 | personA.show2.call(personB) // personA 156 | 157 | personA.show3()() // window 158 | personA.show3().call(personB) // personB 159 | personA.show3.call(personB)() // window 160 | 161 | personA.show4()() // personA 162 | personA.show4().call(personB) // personA 163 | personA.show4.call(personB)() // personB 164 | ``` 165 | 166 | 我们发现与之前字面量声明的相比,show2方法的输出产生了不一样的结果。为什么呢?虽然说构造方法Person是有自己的函数作用域。但是对于personA来说,它只是一个对象,在直观感受上,它跟第一道题中的person1应该是一模一样的。 `JSON.stringify(new Person('person1')) === JSON.stringify(person1)`也证明了这一点。 167 | 168 | 说明构造函数创建对象与直接用字面量的形式去创建对象,它是不同的,构造函数创建对象,具体做了什么事呢?我引用红宝书中的一段话。 169 | 170 | > 使用 new 操作符调用构造函数,实际上会经历一下4个步骤: 171 | > 1. 创建一个新对象; 172 | > 2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象); 173 | > 3. 执行构造函数中的代码(为这个新对象添加属性); 174 | > 4. 返回新对象。 175 | 176 | 所以与字面量创建对象相比,很大一个区别是它多了构造函数的作用域。我们用chrome查看这两者的作用域链就能清晰的知道: 177 | 178 | ![](https://user-gold-cdn.xitu.io/2017/9/2/4fe92965af4e9fd72fa2c65e031d934c) 179 | 180 | ![](https://user-gold-cdn.xitu.io/2017/9/2/b1a2a29391fe5324dd7876ab1b521b51) 181 | 182 | personA的函数的作用域链从构造函数产生的闭包开始,而person1的函数作用域仅是global,于是导致this指向的不同。我们发现,要想真正理解this,先得知道到底什么是作用域,什么是闭包。 183 | 184 | 有简单的说法称闭包就是能够读取其他函数内部变量的函数。然而这是一种闭包现象的描述,而不是它的本质与形成的原因。 185 | 186 | 我再次引用红宝书的文字(便于理解,文字顺序稍微调整),来描述这几个点: 187 | 188 | >...每个函数都有自己的执行环境(execution context,也叫执行上下文),每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。 189 | 190 | >...当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。当代码在环境中执行时,会创建一个作用域链,来保证对执行环境中的所有变量和函数的有序访问。函数执行之后,栈将环境弹出。 191 | 192 | >...函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中。 193 | 194 | 具体来说,当我们 `var func = personA.show3()` 时,`personA`的`show3`函数的活动对象,会一直保存在`func`的作用域链中。只要不销毁`func`,那么`show3`函数的活动对象就会一直保存在内存中。(chrome的v8引擎对闭包的开销会有优化) 195 | 196 | 而构造函数同样也是闭包的机制,`personA`的`show1`方法,是构造函数的内部函数,因此执行了 `this.show3 = function () { console.log(this.name) }`时,已经把构造函数的活动对象推到了show3函数的作用域链中。 197 | 198 | 我们再回到this的指向问题。我们发现,单单是总结规律,或者用一句话概括,已经难以正确解释它到底指向谁了,我们得追本溯源。 199 | 200 | 红宝书中说道: 201 | >...this引用的是函数执行的环境对象(便于理解,贴上英文原版:It is a reference to the context object that the function is operating on)。 202 | >...每个函数被调用时都会自动获取两个特殊变量:this和arguments。内部在搜索这个两个变量时,只会搜索到其活动对象为止,永远不可能直接访问外部函数中的这两个变量。 203 | 204 | 205 | 我们看下MDN中箭头函数的概念: 206 | >一个箭头函数表达式的语法比一个函数表达式更短,并且不绑定自己的 `this`,`arguments`,`super`或 `new.target`。...箭头函数会捕获其所在上下文的 `this` 值,作为自己的 `this` 值。 207 | 208 | 也就是说,普通情况下,this指向调用函数时的对象。在全局执行时,则是全局对象。 209 | 210 | 箭头函数的this,因为没有自身的this,所以this只能根据作用域链往上层查找,直到找到一个绑定了this的函数作用域(即最靠近箭头函数的普通函数作用域,或者全局环境),并指向调用该普通函数的对象。 211 | 212 | 或者从现象来描述的话,即**箭头函数的this指向声明函数时,最靠近箭头函数的普通函数的this。但这个this也会因为调用该普通函数时环境的不同而发生变化。导致这个现象的原因是这个普通函数会产生一个闭包,将它的变量对象保存在箭头函数的作用域中**。 213 | 214 | 故而`personA`的`show2`方法因为构造函数闭包的关系,指向了构造函数作用域内的this。而 215 | 216 | ```javascript 217 | var func = personA.show4.call(personB) 218 | 219 | func() // print personB 220 | ``` 221 | 因为personB调用了personA的show4,使得返回函数func的作用域的this绑定为personB,进而调用func时,箭头函数通过作用域找到的第一个明确的this为personB。进而输出personB。 222 | 223 | 讲了这么多,可能还是有点绕。总之,想充分理解this的前提,必须得先明白js的执行环境、闭包、作用域、构造函数等基础知识。然后才能得出清晰的结论。 224 | 225 | **我们平常在学习过程中,难免会更倾向于根据经验去推导结论,或者直接去找一些通俗易懂的描述性语句。然而实际上可能并不是最正确的结果。如果想真正掌握它,我们就应该追本溯源的去研究它的内部机制。** 226 | 227 | 我上述所说也是我自己推导出的结果,即使它不一定正确,但这个推断思路跟学习过程,我觉得可以跟大家分享分享。 228 | 229 | 230 | 231 | --[阅读原文](https://github.com/wuomzfx/blog/blob/master/this.md) @[相学长](https://www.zhihu.com/people/xiang-xue-zhang) 232 | 233 | --转载请先经过本人授权。 234 | -------------------------------------------------------------------------------- /viewport.md: -------------------------------------------------------------------------------- 1 | # 移动端适配初探 2 | 3 | IE时代,每一个前端同学都要费尽心力兼容各代版本IE。如今IE虽渐渐淘汰,但移动互联网崛起,我们又有了新的麻烦。因为各个手机的屏幕不同,为了保证产品视觉呈现与体验更加完美和谐,移动端的适配也显得较为棘手。 4 | 5 | 目前较为知名的一个方案是手淘的flexible。它具体如何实现移动端的适配,我们后面再阐述,因为在此之前,我们需要先理清一些基础概念。 6 | 7 | * **screen.width** 8 | 设备屏幕的宽度,以像素计。这个属性只读,不会因为浏览器的拖拉缩放而改变。 9 | 10 | * **window.innerWidth** 11 | 浏览器窗口的内部宽度。它包含滚动条,并且会随着浏览器拖拉缩放而改变。具体表现为呈现出内容的浏览器窗口的宽度。 12 | 13 | * **clientWidth** 14 | 元素的可见宽度(不计滚动条)。document.documentElement.clientWidth 即整个html的可见宽度。 15 | 16 | * **offetWidth** 17 | 元素的实际宽度(包含滚动条)。document.documentElement.offsetWidth即整个html的实际宽度。 18 | 19 | 然后我们来看一个莫名其妙的事情。我手上是一只三星s7e,号称屏幕分辨率1440*2560。 20 | 21 | 现在我写了一个页面,body里设置了一个div,设置宽高为300px。并打印了刚阐述的那些宽度尺寸与div的宽度,没看到结果前,乍一想,应该是4个1440和一个300吧。然后屏幕蹦跶出这样的结果。 22 | 23 | ![](http://mmbiz.qpic.cn/mmbiz_jpg/wP62YcCWCXXQMNDWBuZMFY6UsEzeayALAhEQZCesXpiaquNte82bgcqrwwNGAtDficBMMOIO5JXc7n5lWwegFHkA/640) 24 | 25 | 莫名其妙吧,跟想像中完全不一样啊。屏幕宽度咋成412px了,说好的2k屏呢?还有980px又是哪儿蹦达出来的。为了搞清这当中的奥秘,我们又得来学习一些概念了。 26 | 27 | * **设备像素** 28 | 顾名思义,就是设备的真实物理像素,指每英寸屏幕所拥有的像素(pixel)数目。 29 | 30 | * **设备独立像素** 31 | device independent pixel,独立于设备的用于逻辑上衡量像素的单位。 32 | 33 | * **CSS像素** 34 | 指的是CSS样式代码中使用的逻辑像素,比如上面那个div,我设置了宽度300px,就是CSS像素,它并不一定等于它真实的物理像素。所以CSS像素即是用在CSS语言中的设备独立像素。 35 | 36 | * **设备像素比** 37 | device pixel ratio,即设备像素/设备独立像素,即物理像素/逻辑像素。在网页中就是设备像素与CSS像素的比值。设备像素比能通过window.devicePixelRatio获取。 38 | 39 | 通过dpr我知道了s7e此比值为3.5。那么screen.width 就是这手机的独立像素的值,412*3.5,确实等于1442。 40 | 41 | 可是问题又来了,980px又是如何来的呢?为什么告诉我逻辑分辨率是412*731了,怎么html的宽度又是980呢? 42 | 43 | ## Viewport(视区) 44 | Viewport可以理解成浏览器用来呈现网页的那一部分区域,它是一个移动端的特性,可以通过Meta标签来设定。 45 | 46 | ### Layout viewport 47 | 因为移动设备的逻辑分辨率一般都是比PC小的,为了能让移动设备也能比较好的呈现PC网页内容,各大移动端浏览器就把viewport的默认值设成了980px或者1024px。 48 | 49 | 也就是说,不管你设备屏幕多大,分辨率多少,浏览器窗口的逻辑宽度,就是设定的值(比如980px),如果一个元素宽度是980px,那就刚好占满屏幕宽度。如果html宽度超出980px,那窗口就出现滚动条。除非隐藏内容,或者缩放屏幕。 50 | 51 | 这个呈现网页的区域,就叫做layout viewport。于是,我做的页面出现了上述结果。(至于为什么会多了1px成了981,这个不同手机有不同结果,我还未探索到确切原因,猜测是分辨率不同导致处理策略不同特意设的偏差值。) 52 | 53 | ### Visual viewport 54 | 上面陈述到,layout viewport可能是会比浏览器可视区域大的,这个visual viewport指的就是可视的viewport区域。简单的理解,就是屏幕可视区域。可以在visual viewport上拖拉缩放,来查看layout viewport的内容。 55 | 56 | ### Ideal viewport 57 | layout默认值是980px,可是我们很多页面就是专门为了移动端设计的,我们不希望用户要缩放、横屏滚动才能看到内容,而且内容还很小。最好viewport就是屏幕宽度,这样viewport就是ideal vieaport 。 58 | 59 | ### Viewport meta 60 | 我们希望可以自己设定viewport值,来呈现自己想呈现的内容,那就得通过meta标签来定义viewport属性,具体如下: 61 | 62 | Name | Value | Description 63 | --------------|------------------------|---- 64 | width | 正整数或device-width | 定义视口的宽度,单位为像素 65 | height | 正整数或device-height | 定义视口的高度,单位为像素 66 | initial-scale | [0.0-10.0] | 定义初始缩放值 67 | minimum-scale | [0.0-10.0] | 定义缩小最小比例,它必须小于或等于maximum-scale设置 68 | maximum-scale | [0.0-10.0] | 定义放大最大比例,它必须大于或等于minimum-scale设置 69 | user-scalable | yes/no | 定义是否允许用户手动缩放页面,默认值yes 70 | 71 | 最常用的写法就是 72 | ```html 73 | 76 | ``` 77 | 78 | 意思就是,设置viewport视区大小为设备宽度,即理想视区,默认缩放1.0倍(就是不缩放),并且禁止用户缩放。 79 | 80 | 还有一种常用的写法,就是根据自己设备的dpr来还原设备本来宽度,如假设手机devicePixelRatio为2,则设置 81 | ```html 82 | 86 | ``` 87 | 88 | ## 开始适配 89 | 了解了这些,前端适配工作总算可以开始了。但马上出现了一道大山。我们让所有设备的viewport都等于device-width,带来了一个新的问题。不同设备的device-width并不相同。即使不考虑pad,只考虑各大主流手机,也从320-400多不等。我们希望不同尺寸设备能呈现几乎一致的内容来保证用户体验。 90 | 91 | 在布局上,我们可以通过诸如flex布局等方法来解决。但是当涉及元素尺寸大小的时候,如果还是用px做单位,那么将会呈现天差地别的差异。那该怎么办呢? 92 | 93 | ### CSS单位rem 94 | 传统页面尺寸我们都用px定义。而rem是相对于根元素的font-size来做计算的。举个例子:根元素的font-size:16px; 那么1rem=16px,0.5rem = 8px。这样一来,我们只要根据不同屏幕尺寸设定不同的font-size。后面只要采用rem做尺寸单位,就能保持视觉一致。那该如何判断不同尺寸屏幕并设置相应font-size呢? 95 | 96 | ### 媒体查询 97 | 媒体查询允许我们来根据设备不同来设定不同的css样式,最常用的就是根据不同屏幕尺寸来设定不同样式。举个栗子: 98 | 99 | ```css 100 | @media screen and (max-width: 320px) { 101 | html { 102 | font-size: 20px; 103 | } 104 | } 105 | ``` 106 | 107 | 意思就是当layout viewport小于320px时,根节点的font-size为20px; 108 | 109 | ### JS判断 110 | 在页面加载时判断当前屏幕尺寸,并根据尺寸设定相应的font-size到根节点。 111 | 112 | 这样一来,我们做前端适配的思路就清晰了。 113 | 114 | ## 适配步骤 115 | 116 | 1. **设置viewport为device-width。** 117 | 118 | 2. **通过媒体查询、或者js,判断不同屏幕尺寸,设定根节点font-size。** 119 | 120 | 3. **使用rem来设定元素尺寸单位。** 121 | 122 | 这样我们再去看手淘的[解决方案](https://github.com/amfe/article/issues/17)[5],就能大致清晰它的原理了。 123 | 但对比上述的几点,可以发现flexible并不是采用viewport设置为device-width,而是采用还原成设备物理分辨率,这样的好处是为了解决高分屏下的1px问题。具体就不展开讨论了。 124 | 125 | 126 | ## 参考 127 | 1. [W3CSchool](http://www.w3school.com.cn)(http://www.w3school.com.cn) 128 | 2. [A pixel is not a pixel is not a pixel](http://www.quirksmode.org/blog/archives/2010/04/a_pixel_is_not.html)(http://www.quirksmode.org/blog/archives/2010/04/a_pixel_is_not.html) 129 | 3. [移动前端开发之viewport的深入理解](http://www.cnblogs.com/2050/p/3877280.html)(http://www.cnblogs.com/2050/p/3877280.html) 130 | 4. [移动前端第一弹:viewport详解](http://www.kancloud.cn/jaya1992/fe-notes/86799)(http://www.kancloud.cn/jaya1992/fe-notes/86799) 131 | 5. [使用Flexible实现手淘H5页面的终端适配](https://github.com/amfe/article/issues/17)(https://github.com/amfe/article/issues/17) 132 | -------------------------------------------------------------------------------- /webpack-loader.md: -------------------------------------------------------------------------------- 1 | # 编写自己的Webpack Loader 2 | 3 | 本文将简单介绍webpack loader,以及如何去编写一个loader来满足自身的需求,从而也能提高对webpack的认识与使用,努力进阶为webpack配置工程师。 4 | 5 | ## Webpack Loader 6 | 7 | [webpack](https://github.com/webpack/webpack)想必前端圈的人都知道了,大多数人也都或多或少的用过。简单的说就是它能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。可以说,它作为一个打包工具,在前端工程化浪潮中,起到了中流砥柱的作用。 8 | 9 | 那webpack其中非常重要的一环就是,能够对加载的资源文件,进行一些处理。比如把less、sass文件编译成css文件,负责这个处理过程的,就是webpack的loader。 10 | 11 | > loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。 12 | 13 | ![](https://user-gold-cdn.xitu.io/2017/10/12/0dfdedeff330f47ba6a00c1e36896f71) 14 | 15 | 举个稍微复杂的例子,[vue-loader](https://github.com/vuejs/vue-loader),它官网介绍如下: 16 | 17 | > vue-loader 是一个 Webpack 的 loader,可以将指定格式编写的 Vue 组件转换为 JavaScript 模块。 18 | 19 | Vue组件默认分成三部分,`