Giter Club home page Giter Club logo

sunmaobin.github.io's Introduction

Welcome 🎉🎉🎉

本仓库用于博客记录,博客内容查看:Issues

sunmaobin.github.io's People

Contributors

dependabot[bot] avatar sunmaobin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sunmaobin.github.io's Issues

MAC 新手指南

一、了解 MAC

1、C盘、D盘哪去了?

  • 访达 中的 文稿 对应Windows上的 我的电脑

  • 启动台 对应Windows上的 所有程序

  • 系统偏好设置 是Mac上设置所有选项的地方,相当于Windows上的 控制面板

    Imgur

  • 文稿 中新建几个根文件夹,然后可以作为Windows上的C盘、D盘之类的,可以右键文件夹,标记为不同的颜色,以后操作的时候,可以通过颜色快速定位到想要的目录,不必要从 文稿 一级一级往下翻

2、APP顶部的菜单栏去哪了?

在Mac中,所有APP共享顶部的导航栏,在不同App中,顶部的导航栏显示当前App的导航栏。比如:

  • 显示桌面的时候,顶部导航栏:

  • 在Webstorm编辑器中时,顶部导航栏:

3、新增按键 Command

在 Windows 上许多组件键 Ctrl + * 在 Mac 上可能都换成了 Command键 + *

比如:

  • 复制 Command键 + C
  • 粘贴 Command键 + V

二、自带常用软件

  • 备忘录(系统自带软件,但是很好用,记录Node很舒服)
  • 文本编辑器(相当于 Windows 上的记事本,也挺好用)
  • 日历
  • 计算器

三、装机必备

  • Chrome 浏览器(入口)
  • 微信(聊天)
  • Sougou 输入法(打字)
  • The Unarchiver(解压,挺好用)
  • IINA - 视频播放软件(默认QuickTime不支持播放AVI格式)
  • Kugoo(听歌)
  • Office(办公)
  • Project Viewer 365(查看 Office Project 软件,Mac 上没有免费的 Office Project)
  • Hyperswitch(同类进程之间切换方便)
  • Xnip (截图软件)

四、前端开发

  • Git
  • Webstorm(VSCode)
  • DataGrid(管理数据库)
  • Sublime Text(文本编辑器,用系统自带的也可以)
  • Typora(MarkDown编辑器)
  • PS
  • Sketch
  • Axure
  • Charles(抓包)
  • SwitchHosts!(修改Host)
  • ColorSlurp(颜色提取器)
  • kdiff3(文件对比工具)
  • FileZilla(FTP工具)
  • Brew(软件安装器)
  • iTerm2(比原生命令行好用)
  • 有道翻译(翻译,或者用系统自带的词典)

五、日常操作

1、打印机设置

系统偏好,打印机与扫描仪,点击 + 就可以扫描到网络打印机,点击确定即可添加。

2、远程电脑

# Windows -> Mac

Windows 和 Mac 上同时安装 Teamview

每次开会前,如果要远程主机的电脑,最好做如下的事情:

  • 打开Mac上的Teamview,并拍照记下 您的ID密码
  • 在某个使用Windows电脑的同事机器上安装Teamview
  • 远程时先通过会议室,远程同事Windows电脑,再通过同事的Teamview连接Mac的Teamview

# Mac -> Windows

安装软件:Microsoft Remote Desktop

# Mac -> Mac

访达 界面,按快捷键 ⌘ + K ,地址栏输入:vnc://172.25.122.86 即可远程连接另一台Mac机器。

悲催的是 Mac 远程另一台电脑,另一个电脑无法息屏,相当于在同步直播。。。。

3、访问共享目录

打开 访达 ,顶部菜单 前往/连接服务器… 或者执行快捷键

⌘ + K 调出目录,输入共享目录地址,如:smb://192.168.2.114 , 连接即可。

4、切换输入法

  1. 任何App内使用快捷键:⌃ + 空格
  2. 或者鼠标点击右上角输入法图标,切换

5、便捷操作

  • F3 平铺所有窗口
  • F4 打开应用启动台
  • 触摸板两指按压 相当于右键(常用)
  • 触摸板两指上下滑动 滚动页面(常用)
  • 触摸板两指左右滑动 如果在浏览器中就是前进、后退(常用)
  • 触摸板三指下滑 平铺同类应用,可以快速切换
  • 触摸板三指上滑 平铺所有窗口(同F3)
  • 触摸板四指回拢 打开应用启动台(同F4)

6、底部 Docker 栏固定顺序(建议)

我的底部 Docker 栏 从左到右依次是:

  • 访达
  • 启动台
  • 偏好设置
  • Chrome 浏览器
  • 日历
  • 计算器
  • Webstorm
  • DataGrid
  • SwitchHosts!
  • 备忘录
  • Sublime Text3
  • 文本编辑器
  • Typora
  • iTerm
  • 微信
  • Ps
  • Sketch
  • Axure RP
  • FileZilla
  • Charles
  • Word
  • Excel
  • PowerPoint
  • ColorSlurp
  • KDiff3
  • 有道词典

五、高级操作

1、切换窗口

在Mac下切换窗口,通用的命令:⌘+Tab ,但是这个命令有个缺陷,就是只能在不同的App直接切换。比如:Webstorm如果打开3个项目,其实就是同一个App打开了3个进程,这时候就不能切换。

如果要在这3个相同进程之间切换,如果不借助外力,目前我只找到如下两种切换方式:

  • 在底部的Docker栏中,鼠标长按Webstorm的图标,然后切换:

  • 在Webstorm中的 窗口 菜单中切换:

那么如何能快速切换组内窗口呢? 安装应用:hyperswitch - https://bahoom.com/hyperswitch

安装之后,可以设置组内应用切换的快捷键、全局应用切换快捷键,比如我个人设置如下:

  • ⌥ + ~ 组内应用切换;
  • ⌥ + TAB 所有应用切换(包括组内和组外的所有窗口)

2、触发角

Mac屏幕的4个角,可以设置快捷方式,就是当鼠标放到4个角的时候触发的操作,个人觉得非常方便,我个人的设置如下:

尤其是右上角的启动屏幕保护程序,每当我离开位置的时候,我顺手将鼠标挪到右上角,直接就进入屏保界面,同时配合从屏保唤醒桌面需要输入密码,简直比Windows上面的 Windos + L 还要方便。当然Mac上也有锁屏快捷键,即:

  • ⌃ + ⌘ + Q 进入屏保界面(这种方式情况下,屏幕马上就熄灭,进入睡眠状态,但是上面进入屏保界面不会)
  • ⇧ + ⌘ + Q 进入关机流程

如何进入设置触发角?

系统偏好 -> 桌面与屏幕保护程序 -> 触发角

3、多个桌面切换

Mac允许设置多个桌面,每个桌面都可以打开多个APP,这样更有助于你归类开发,默认情况下是有2个:

  • 仪表盘 - 有一些小工具在上面
  • 桌面 - 默认的桌面

快速切换多个桌面,可以直接在鼠标上面2个或者3个指头同时左右滑动切换。

新增桌面,只需要打开 调度中心 (快捷键 F3 )窗口,右上角点击 + 即可。

JS解惑-history

本文不是讲history的replaceState和pushState这2个方法怎么用,而是要说在无法删除浏览器history的情况下,如何能正确回退页面的问题。

场景

有个H5的需求,需要一屏一屏的切换页面,到最后一个结果页面时,如果点击了手机的返回键,或者浏览器自带的返回键,希望能回到主页。但是在中间的切屏过程中,点返回或者前进,就是这几个页面之间切换。

比如,总共有这么几个页面:

  1. 首页
  2. 1/4页
  3. 2/4页
  4. 3/4页
  5. 4/4页
  6. 结果页

如果你在2/4页时,点击返回是要回到1/4页,如果你是在结果页,点击返回,是要回到首页。

处理

典型的spa应用,通过改变 location.hash 并且监听 onhashchange 事件,处理对应的页面即可。

问题

对于浏览器而言,改变了location.hash,其实也是给浏览器的history对象,写入了一条历史记录。

所以,对于我这个应用的处理,其实就生成了6条历史记录,可以通过:history.length查看(假如浏览器地址栏输入地址直接进入的首页)。

那么,对于需求来说,在中间的页面切换都没有问题,但是到了 结果页 时,默认返回是到了 4/4页页,如果要回到 首页 需要回退好多次。更麻烦的问题是,如果这每一页有参数携带的话,比如我从1/4的参数一直携带到4/4页,最后提交的时候,才取出数据使用,但是如果我直接从3/4页开始下一步,那么前2页的数据就携带不上了,最终结果可能报错。

思路

  1. 首先,我想到的办法是删除历史记录,也就是到了结果页,删除前4页的历史记录,这样按返回键的时候,直接就回到首页了。但是可惜,history根本没有删除的方法。
  2. 调查了半天貌似只有history.go(-n)这个函数回退页面了。

经验

关于history的几个知识点:

  1. history.length , 你前进了几页,值就等于几,但是返回的时候这个值不变,除非你再次前进,才会更新。
    • 比如:你前进了5页,这时候length=5,又回退了3页,这时候length=5(理论上应该是2),但是只要你再前进一页,这时候length=3,也就是重新前进时会更新length。
    • 所以,使用length的时候一定要注意以上问题。
  2. history.go(-n),相当于回退页面
  3. history.back() 和 history.forward() 分别表示返回上一页,进入下一页,如果没有历史记录了,则没有任何操作。
  4. 刷新浏览器不会影响历史记录

解决

我建立了3个页面,index.html,page.html,result.html,其中page.html使用hash控制4个页面。

在page.html中做了判断,如果location.hash 不为空,则表示刷新了页面,或者Back回来的,这时候就执行history.go(-n)

伪代码:

//page.js
//页面加载时判断
//以下逻辑可以保证,假如当前在page3,刷新后就回退3页,就到首页了;
//如果是从result.html返回来的,那么是page4,刷新后,也回到首页了;
//但是,如果在page.html页面内部,只需要控制好onhashchange对应的模块显示/隐藏即可;
switch(hash){
	case 1 : history.go(-1);break;
	case 2 : history.go(-2);break;
	case 3 : history.go(-3);break;
	case 4 : history.go(-4);break;
}

//result.js
//页面上有返回首页的按钮时,千万不能用:location.href='index.html',不然用户点击浏览器自带的返回按钮时,又会错乱。
//应该用history.go来处理,只有这种处理了,回到首页后,如果用户用浏览器自带的返回键,是会回到进入首页的页面的,不然又会回到结果页了。
$('#backHome').click(function(){
	history.go(-5);
});

以下总结感觉纯属扯淡,讲的比较啰嗦,估计一般人没耐心看完,不过如果你恰好遇到这类型问题了,估计还是会有所启发吧。

JS解惑-连等表达式

JS解惑-连等表达式

问题

如下3个表达式的区别:

//sample1
var str = 123;
var str1 = 123;
var str2 = 123;

//sample2
var str = 123,str1 = 123,str2 = 123;

//sample3
var str = str1 = str2 = 123;

答案

//`sample1` = `sample2`,区别仅仅是代码风格而已;

//sample2
var str = 123,str1 = 123,str2 = 123; //str,str1,str2 全部为局部变量

//sample3
var str = str1 = str2 = 123; //str 局部变量,str1,str2 为全局变量

最新补充于:2018年3月18日

关于连等表达式多啰嗦一些

连等表达式执行过程说明

  1. 先解析连等表达式,进行变量提升(hoisting)
    • 如果是局部变量,不存在则先定义
  2. 将连等式最后一个值,同时赋给前面的其它变量;
    • 如果最后一个值是对象,则是将这个对象先在内存中开辟一个位置(匿名),然后将这个指针指向前面的所有变量。

关于以上第1步的详细解释

比如:

var str=str1=str2=123;

解析过程如下:

  1. 先找到表达式中的变量:strstr1str2
  2. 进行变量提升,并定义:
    • var str,表示这是一个局部变量,则直接在当前执行函数中定义变量str,默认值:undefined

注意: str1str2,由于他俩前面并没有直接有 var 符号,但是又得为它们赋值,这时候JS引擎赋值时,就会把他们挂到全局对象 window 对象上,因为 window 对象已经存在,所以不会在定义了;而对象的属性,只有在赋值时,才会动态在堆内存中追加。所以,var1var2 也不会创建。

记住:变量提升创建的,只包含:基本类型、对象和声明的函数,不包含对象上的属性。

接下来就是一步步赋值的过程了,见下面第2步的解释。

关于以上第2步的详细解释

比如:

var str=str1=str2=123;

并不是: 将123先赋值给str2,再将str2赋值给str1,再将str1赋值给str;
而是: 将123赋值给str2,再将123赋值给str1,再将123赋值给str;

也就是上方的连等式可以理解为:

var str;
str = 123;
str1 = 123
str2 = 123;

复杂示例

请大家给出如下表达式的执行结果:

var a = {n:1};  
var b = a;
a.x = a = {n:2};  
console.log('a.x=',a.x);//?
console.log('b.x=',b.x);//?

大家先自己给出一个答案,然后看看下面的分析跟你想的一样不?

详细分析

第一步:先将代码中的变量提升,不存在的变量先定义。

  1. 先找出以上代码片段中的所有变量
    • a
    • b
  2. 定义变量
    • var a:定义局部变量a,默认值 undefined
    • var b:定义局部变量b,默认值 undefined

于是代码片段变为:

//第一步做的事情
var a;  
var b;

//第二步开始的地方
a = {n:1}; 
b = a;

a.x = a = {n:2};  
console.log('a.x=',a.x);//?
console.log('b.x=',b.x);//?

第二步:一步步执行代码片段并赋值。

  1. a = {n:1} 将a赋值为一个对象 {n:1},这里发生了什么事情?

  2. b = a 将a对象赋值给b,我们知道,对象赋值其实就是指针的指向问题,就是b的指针也指向了a的值;

  3. a.x = a = {n:2},这一步我们按照上方的原理(并不是依次从右往左执行),把它拆解一下:

    • a.x = {n:2};
    • a = {n:2}
  4. 先看前一步,a.x = {n:2},其实就是在a的堆内存中,再额外增加一个属性x,并且指向新的对象,如下:

    • 执行完这一步,大家能看到,其实这时候:a 和 b 还是指向同一个对象,只是那个对象多了一个属性x而已。
    • 这时候:a.x = b.x 都等于 {n:2}
  5. 再看后一步,a = {n:2},就是将a的指针变化了,指向这个新的对象,如下:

    • 这时候:a = b.x 都等于 {n:2}(看蓝色的箭头指向同一个)
    • 而这时候a指针对应的对象中 {n:2},根本不存在x这个属性,所以 a.x=undefined;

最终答案

console.log('a.x=',a.x);//undefined
console.log('b.x=',b.x);//{n:2}

其实本题最容易绕晕的2个地方:

  1. 很多人把连等当成从右往左一个变量给另一个变量赋值!其实不是;
  2. 最后 {n:2} 是个新的对象,注意的地方是对象赋值,一定是一个新的地址了。

参考

(全文完)

一些常见的浏览器兼容插件

整理一些常见的浏览器兼容插件。

CSS HACK

  1. Normalize.css
  2. Google搜索:css hack
    • 会搜索到很多浏览器兼容的问题及其解决办法

JS 框架

  1. HTML5 Shiv
  2. Respond.js
  3. Babel.js

JS解惑-function前面有+和!等特殊字符

让一个函数页面加载后就运行(专业术语叫:IIFE),你的脑海中能想出几种办法呢?

本文就总结下一些常用的办法,同时一些不常用的,但是在一些牛人写的框架的源码中经常会出现的函数自执行的方法。

一、几种常见的方法

1、在body中直接执行方法

function autoFun(){};
autoFun();

2、在head中window.onload 函数中执行函数

function autoFun(){};
window.onload = function(){
    autoFun();
};

3、使用body的onload事件执行

<script>
function autoFun(){};
</script>

<body onload="autoFun()">

</body>

4、借助jquery的启动函数执行

function autoFun(){};
$(function(){
    autoFun();
});

5、使用匿名函数执行

jquery 插件就是这么用的。

(function(){})();

二、两种不常见的用法

1、+function

+function autoFun(){}();

2、!function

bootstrap 组件就是这么用的。

!function autoFun(){}();

以上两种方法,都是在function前面,增加了操作符,于是函数就自动运行了,原理是什么呢?

答案是:通过操作符,将函数申明变为函数表达式,然后再执行。

2017年3月29日21:05:26 修正和补充以下内容。

比如:!function autoFun(){}(); 可以这么理解:

  • 第一步:function autoFun(){}; //函数表达式
  • 第二步:!autoFun(); //执行函数,并将结果求反(!)

之所以这么连起来,最主要的问题是以下这种写法是错误的:

function autoFun(){}();

因为受JS语法解析器的影响,函数的定义和执行不能同时进行的。

但是!如果是一个表达式,那么JS语法解析器会走另外的处理流程,这个流程就是上面提到的2步。

当然,只要是表达式就可以,所以函数的定义不行,但是函数的申明就是可以的:

var autoFun = function(){}();

这么写的结果就是function会执行,但是autoFun=undefined;原因是这时候autoFun的值等于function执行的结果,由于function是个空方法里面没有return值,所以结果就是未定义。
当然,你这么写的目的,可能只是为了快速执行function,所以最后的autoFun的结果你并不关心,那就无所谓了。

具体哪些操作符,可以达到这个目的,参考:IIFE

另外,如果不知道 函数申明函数表达式 的,请自行Google去吧!

参考

JS解惑-语法糖

什么是语法糖?其实很简单。

定义

什么是语法糖?

语法糖,英文是:Syntactic Sugar

维基百科的定义是:

In computer science, syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express.

翻译过来的定义是:

在计算机科学中,语法糖是一种编程语言中的语法,设计语法糖的目的是为了使事情处理起来更加容易阅读或者表达。

更加直白的说法是:语法糖就是某种特性语法的简写形式。

扩展

1、什么是语法盐?

语法盐,英文是:Syntactic Salt

定义:

为避免容易犯的语法错误加上的额外语法限制

2、什么是语法糖精/语法糖浆

语法糖精,英文是:Syntactic Saccharin
语法糖浆,英文是:Syntactic Syrup

定义:

使得程序更加容易的一种语法。

关于这2个的定义,可以参看维基百科:

总结

语法糖并不是某一种特定语言的独有语法,而是所有计算机语言都有的,比如鼻祖类的C语言中就有了数组的语法糖等。

只不过现在在前端JS中,尤其ES6中为了使得程序开发更有效率,更加一致,所以增加了很多语法糖(简单语法)而已。

不要以为这是什么神秘技术,只是一种概念的称呼而已。

更多参考

  1. 维基百科-语法糖
  2. ES6 语法糖之我见

Git入门介绍

本文主要从Git原理、常用命令、分支管理等方面进行简单介绍说明。

Git 和 Svn 的区别

  1. Git是分布式的管理,而Svn不是
    • Git的这个特性可以保证,每个使用Git的人员,本地都有一个独立的库跟远端相同。这样做的好处是,如果哪天断网了,或者在公司Clone的代码到家里了。这时候依然可以Add,Commit,依然可以查看版本记录。
  2. Git的分支管理和Svn有很大区别
    • Git的分支之间可以随意合并、切换,而Svn的分支就相当于是一个额外的目录。
    • Git的分支使用要比Svn更加有优势,更加适合于快速开发、分块交付的工作。
  3. Git数据存储方式和Svn大不一样
    • 这主要表现在Git一个仓库目录下有一个.git隐藏目录,用于存储git的数据信息,而仓库内部的所有文件就没有了.git临时文件。这样做的好处是,你可以随意的拷贝/挪动,而不需要考虑隐藏文件占很大的磁盘。而Svn是每个目录下都有一个.svn目录,用于控制版本和数据信息。有时候挪动了整个目录,切换了Svn的地址,需要clean等操作,非常麻烦,而且占的体积非常大。
    • 当然Git的这种存储有更大的作用,就是他实际存储了整个库到本地了,所以平时更新提交数据非常快。
    • 更多数据存储,参考:Git是如何存储对象的?

Git 架构

注:此图片来自阮一峰博客

Git 安装配置

  1. host中添加以下内容:
    host所在目录:C:\Windows\System32\drivers\etc\

    192.168.1.68 git.gdd
    192.168.1.68 jenkins.gdd
    192.168.1.68 mysql.gdd
    192.168.1.68 server.gdd

  2. 下载Git客户端并安装:

    • 点击下载
    • 安装时除以下步骤选择最后一个外,其他都可以默认。

  3. 安装后进行如下操作:

    • 在E盘或者F盘新建一个目录叫:gitlab

    • 右键gitlab目录,运行:Git Bash

  4. 在命令窗口分别执行以下命令:
    注意:每个命令,一直回车即可。

    git config --global user.name "<your username>"
    git config --global user.email "<your email>"
    ssh-keygen -t rsa -C "<your email>"
    
  5. 登录git网页版

    • 地址:http://git.gdd:8889/
    • 用户名:自己的英文名字
    • 默认密码:88888888
    • 点击右上角的用户头像,进入个人设置

    • 点击左侧的:SSH Keys

    • 点击右侧的:Add SSH Key

    • 进入Add an SSH Key窗口

    • 用记事本打开文件:C:\Users\Administrator.ssh\id_rsa.pub
    • 拷贝id_rsa.pub中的内容,到上面的窗口,并保存

至此,git的环境已经配置好,现在查看自己有哪些git仓库目录。

  1. 在git的首页,能看到右侧有一些Project,这是你有权访问的一些项目。

  2. 比如:点击Administrator / gdd-doc 进入这项目后,点击SSH,并复制URL[email protected]:root/gdd-doc.git

  3. 在刚才的Git Bash 窗口,执行如下命令并回车,开始下载gdd-doc的文件

    • 注意:第一次运行时,可能需要确认,直接输入:yes,回车即可。
  4. 文件下载完成后,就可以看到所有的项目资料了。

到此为止,git的安装和初次使用就结束了,还要进行其他的学习和操作,可以参考本文档其他章节,后者以下文档:

Git 常用命令

获取文件

  1. git clone url 克隆一个仓库
  2. git fetch origin 更新本地仓库文件跟远端一致
  3. git branch 查看本地有哪些分支
  4. git branch -r 查看远端有哪些分支
  5. git branch -a 查看本地和远端所有分支
  6. git branch -D feature1 删除本地分支feature1,远端仓库一般不建议删除(管理员除外)
  7. git checkout develop 下载并切花到develop分支(如果develop分支本地已有,则直接切换过去)
  8. git checkout -b feature1 以当前分支为准,创建一个新的分支,名字叫feature1,并切换到feature1分支
  9. git pull origin develop 从远端develop分支更新内容到工作区

提交文件

  1. git status 查看改动的文件(或者打开GUI查看)
  2. git add <file> 提交文件
  3. git add -A 提交改动的所有文件
  4. git commit -m 'comment' 添加评论提交代码到本地仓库
  5. git push origin develop 提交代码到develop分支(**注意:**前提示当前分支也是develop分支,保持一致。)

打标签

  1. git tag 查看所有tag
  2. git tag tag1 将当前分支的当前代码打一个标签,名称叫tag1
  3. git push origin tag1 将打好的标签tag1提交到远端
  4. git checkout tag1 临时切换到啊tag1标签上
  5. git checkout -b feature2 tag1tag1标签上创建一个分支并feature1并切换到这个分支上

其它命令

  1. git merge --no-ff feature1 当前分支合并feature1分支,注意: --no-ff 要加。
  2. git log 查看提交日志,按:q退出
  3. git reset --hard 'hash' 硬性回退到之前的一个版本。hash是每个版本有一个hash码。

Git GUI工具

稍后补充

参考

  1. 常用 Git 命令清单-阮一峰:http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html
  2. 我的git笔记-颜海镜:http://yanhaijing.com/git/2014/11/01/my-git-note/

Linux 常用命令

Linux 常用命令,最实用的命令集锦。

常用命令

  • ls -l | grep "^.json" |wc -l 查看目录文件个数
  • wc -l info.log 统计文件行数
  • ls -lh test.log 查看文件大小
  • nano -w 比vi更好用些
    • ctrl + o 回车 保存
    • ctrl + x 退出
  • grep abc jvm.log 从jvm.log中查询abc
  • grep -n abc jvm.log 查看jvm.log中含有abc的个数
  • tail -500f 滚屏500行
  • tail -n 500 查询最后500行
  • head -n 500 查询开始500行
  • du -lh 查看目录大小

查看机器性能

  • dstat -arlpim 查看系统整体性能:磁盘、吞吐、内存、CPU等
  • df -h 查看磁盘容量
  • du -h 查看目录大小
  • free 查看整体容量
  • top 查看性能
  • htop 查看详细性能

文件 vi

  • %s/find/replace/g 替换文件中所有的find字符串为replace
  • split -l 50 拆分原始文件

screen 启动后台进程

  • screen 回车 进入一个后台界面
  • screen -S name 创建一个名为name的screen
  • screen -r night 进入name为screen的窗口
  • ctrl+A D 退出当前screen
  • screen -r 查看所有screen
  • screen -r pid 进入某一个screen
  • ctrl+Z 结束当前screen
  • screen -wipe 检查目前所有的screen作业,并删除已经无法使用的screen作业
  • ctrl+s 锁屏
  • ctrl+q 解锁

压缩/解压命令

  • unzip xxx.zip -d /home 解压到指定目录home
  • gzip -dc jvm-app-0.log.20141218.0506.gz | grep UnknownHostException 查看压缩文件并且搜索关键字
  • tar -czf file.tar /usr/local/.. 压缩
  • tar –xvf file.tar 解压 tar包
  • tar -xzvf file.tar.gz 解压tar.gz

删除所有的进程

  • ps -ef |grep s3cmd |awk '{print $2}'|xargs kill -9

远程传输

  • dyscp 10.1.5.60 /data/py_overlord_data/td_11/ xa-02.log
  • dyscp 10.1.2.18 /dianyi/app/origin-1.13/ *
  • dyscp 10.1.5.60 /data/py_overlord_data/td_15/ *

软连接

  • ln -s a b 创建软连接。a 就是源文件,b是链接文件名,其作用是当进入b目录,实际上是链接进入了a目录
  • rm -rf b 删除软链接。注意不是 rm -rf b/

JS解惑-jQuery.clone


title: JS解惑-jQuery.clone
date: 20161107104059
categories: [WEB]
tags: [js]


关于jQuery中clone的一些解惑。

用途

假如你想把一个html元素copy一份到另一个元素中,就可以使用 .clone 了。

比如:

<div class="container">
  <div class="hello">Hello</div>
  <div class="goodbye">Goodbye</div>
</div>

如果你想把 .hello 放到 .goodbye 中,那么以下写法就是不对的。

$( ".hello" ).appendTo( ".goodbye" );

这样的结果将是:

<div class="container">
  <div class="goodbye">
    Goodbye
    <div class="hello">Hello</div>
  </div>
</div>

但是,如果你使用 clone() ,那么结果将是你预期的。

$( ".hello" ).clone().appendTo( ".goodbye" );
<div class="container">
  <div class="hello">Hello</div>
  <div class="goodbye">
    Goodbye
    <div class="hello">Hello</div>
  </div>
</div>

注意: jquery中的clone,只针对的是 html 原型而言的!如果你将一个js对象clone(),就会报错了。

jquery.min.js:2 Uncaught TypeError: Cannot read property 'ownerDocument' of undefined()

API

$.clone() 可以传递2个参数:

  • withDataAndEvents,默认:false
  • deepWithDataAndEvents,默认:等于withDataAndEvents(特别注意这里

这里就不区分jQuery的版本了,假设我们都 > 1.5。如果还有低版本的,可以看看 官网 的兼容性说明。

分析

  1. $.clone():仅仅克隆当前元素的html结构以及当前元素及其子元素html上绑定的事件和数据(下文解释);
    • 数据是指通过 data-* 属性赋值的属性。
  2. $.clone(true,false):克隆当前元素html结构、当前元素及其子元素html上绑定的事件和数据,但是不包括子元素上动态绑定的事件和数据;
  3. $.clone(true):克隆当前元素html结构、当前元素及其子元素html上绑定的事件和数据,以及子元素的事件和数据。
    • 等同于 $.clone(true,true),因为通过API我们知道,第2个参数默认等于第1个参数。

示例

<p class="wrapper">
    <input class="test" type="text" oninput="inputEvt(this)"/>
</p>

<script src="http://code.jquery.com/jquery-1.6.1.js"></script>
function inputEvt(obj){
    console.log($(obj).val())
};

$('.wrapper').click(function () {
    console.log('click');
});

$('.test').data('obj','test');

//clone1
$('.wrapper').clone(false).prependTo($("body"));

//clone2
$('.wrapper').clone(true,false).prependTo($("body"));

//clone3
$('.wrapper').clone(true).prependTo($("body"));

结果说明:

  1. clone1
    • .wrapper,click *无输出(click);
    • .test,输入文字立即有输出(oninput
    • .test,data=undefined(data
  2. clone2
    • .wrapper,click有输出(click);
    • .test,输入文字立即有输出(oninput
    • .test,data=undefined(data
  3. clone3
    • .wrapper,click有输出(click);
    • .test,输入文字立即有输出(oninput
    • .test,data=test(data

总结

clone()方法其实包含了3部分内容:

  • html结果以及一开始绑定写在html上的事件和数据
  • + 动态绑定到html上的事件和数据(直接绑定,live/delegate/on等委托绑定除外)
  • + 子元素及其事件和数据

而以上3部分内容,分别对应以下用法:

  • clone()
  • clone(true,false)
  • clone(true) 或 clone(true,true)

特例

处于性能的考虑 textareaselect 这2个标签动态增加的属性有点例外。

  • textara 克隆后,值不会带过去;
  • select 选中的 option 克隆后,依然是未选择状态;

副作用

如果你把一个含有ID的元素进行克隆,那么克隆以后就会出现ID重复!

为了避免这问题,建议你这么做:

  • 克隆的元素尽量使用 class 而非 id;
  • 克隆以后修改新元素的 id

(全文完)

参考

  1. jQuery官网API

HTML解惑-href属性的特殊用法

针对HTML中href的一些特殊用法的解惑。

一、A标签的用法

1、跳转到另一个目标地址

<a href="http://www.night123.com">跳转到Night博客</a>

2、跳转到当前文档的固定章节

指定一个 hash 值,跳转到当前文档 id=hash 所在的位置。

比如:在 <body> 有如下标签。

<!-- 案例展示 -->
<div id="cases">
	...
</div>

在 页面中间一个超链接,点击后定位到 cases 所在位置,则可以增加一个 a 标签操作。

<p>
	<a href = "#header">查看案例</a>
</p>

3、调用系统默认邮件系统发送邮件

<a href="mailto:[email protected]?Subject=hello%20night">发送邮件</a>

4、执行 JavaScript 脚本

<a href="javascript:alert('Hello World!');">运行JS</a>

二、href 属性特殊用法解惑

我们通过以上章节,知道了 a 标签的功能,但是在实际工作中,还有一些特殊用法,接下来我将一一为你解惑。

1、href="#"

这个本来正常的做法,是 href="#hash",但是偏偏只有一个 #,用意何在?

作用有2个:

1、跳转到顶部

<a href="#">返回顶部</a>

2、保留 href 默认样式的情况下,不使用默认的跳转功能

有时候为了业务需要跳转功能是通过 js 来控制,这样我们就需要屏蔽 href 的默认跳转,这时候我可能会这么做。

  • 去掉 href 属性,但是去掉后,发现浏览器默认的 a 标签样式没了,鼠标滑过也没有手的形状,一切都得重置,麻烦。
  • href="#" 点击后,继续在当前页面,后续逻辑交给 js 处理,但是这么处理后,假如 a 标签在页面底部,页面有滚动条,这时候,点击以后页面会先返回顶部,再执行js的操作。所以,这么做体验也还是不好。

当然,有时候我们在页面的 menu 里面,也可能会有一些 a 标签,用来定位跳转到不同的页面,如:

<ul class="nav">
	<li><a href="#">首页</a></li>
	<li><a href="#">案例</a></li>
	<li><a href="#">关于</a></li>
</ul>

如果这个 nav 在页面顶部,只要不是需要拖动滚动条的地方,那么点击后,使用 js 处理跳转页面几乎没感觉。唯一有一点点小的瑕疵,就是在地址栏上的href后面,会多出一个 #

比如,之前的地址是:http://www.night123.com/index.html

那么点击任何一个 a 标签后,页面会变成:http://www.night123.com/index.html#

现在知道了 href="#" 的两个作用了吧,虽然这2个作用在解决相关问题,都不是很完美,但是还是有一些人一直在用。

补充说明,有人说 hrefa 标签必不可少的一个属性,其实不然。
延伸阅读请参考:http://w3c.github.io/html/links.html#attr-hyperlink-href

2、href="javascript:;"

这种用法,现在也很常见,目的也是解决避免默认的 href 跳转的。

但是这种做法就比上面第一种做法要好,因为这么做相当于是执行了一个空的 js 片段,这样在页面的任何位置点击 a 标签后,原页面本身都是无感知的。

当然这个做法也还不是最好的,因为从功能上完全实现,体验上也没有问题,但是从写法上,总是有些怪异。比如:javascript:;,不是越看越不好理解吗?

当然要避免这种问题,可以使用js原生的事件机制,来屏蔽原始的事件,方法如下:

方法1:

<a id="click" href='#'>Click Me!</a>

<script>
document.getElementById("click").onclick  = function(e){
	// 屏蔽原始事件
    	e.preventDefault();

	// TODO: 实现自己的业务逻辑

}
</script>

或者使用另一种做法:

<script>
document.getElementById("click").onclick  = function(e){

	// TODO: 实现自己的业务逻辑

	// 阻止事件传递
	return false;
}
</script>

参考:stackoverflow激烈讨论

个人觉得方法一是最完美的处理方式。

3、href="javascript:void(0);"

这是禁止页面跳转的一种方式。

看了这篇文章就知道了:
谈谈Javascript中的void操作符

4、href="#!home"

此种写法在 SPA 应用中最为常见,原理就是通过监听js的 onStateChange 事件,来控制路由规则,加载不同的HTML模板,实现页面切换。

但是浏览器地址中的的 hash 不是 # 后面的参数吗?为什么要增加一个感叹号,不能直接这么写 #home 吗?

看看阮一峰大神的这篇文章,就明白了:
URL的井号

JS解惑-(fun)()表达式的含义

JS中经常能看到 (function(){...})(),那么双括号()到底是什么意思呢?

背景

我们先来看看一些实际工作中用到的()的例子。

形式是这样的:

(function(){
    //TODO
})();

当然在jquery中,有时候为了避免变量$冲突,也经常这么用:

jQuery.noConflict();
(function($){
    //TODO
})(jQuery);

分析

引自 Ecma-26212.2.10.4章节 的语句:

ParenthesizedExpression : ( Expression ) 
   1. Return the result of evaluating Expression. This may be of type Reference.

我个人的理解翻译:

圆括号括起来的表达式:(表达式)

返回计算表达式的结果,这可能是一个引用。

返回计算表达式的结果

这句话怎么理解呢?举个例子:

(function(){
    console.log(123456);
});

打印结果就是这个function的函数表达式,即:

function(){
    console.log(123456);
}

如果上面的例子,后面再加一个(),那么就是立即执行这个函数,即:

(function(){
    console.log(123456);
})();

结果:123456

引申:

写到这里大家可能会问,为什么要这么用呢?

2个作用:

  • 立即执行函数,即:self-executing function
  • 构建独立不受污染的块级作用域

关于立即执行函数,参考我之前写过的一片文章:

另外,我们知道JS在ES6之前是没有块级作用域的,那么通过这种 (匿名函数) 的形式,可以保证 () 函数中拥有独立的作用域,而外部无法访问。

什么是块级作用域?
即,在一些类似于C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的。
出自《JavaScript 权威指南》中3.10.1章节,即57页

比如:

for(var i=0;i<10;i++){
   //TODO
};
console.log(i);

结果为:10,也就是说,你在for中定义的变量i,在for结束后依然生效。

这可能是一个引用

这句话怎么理解呢?举个例子:

var obj = {
    name : 'night',
    sayHi : function(){
        console.log('hello ' + this.name);
    }
};
(obj);

结果就是这个obj的对象本身(也就定义中说的,是对obj的引用):

Object {name: "night"}
    name:"night"
    sayHi:()
    __proto__:Object

既然是这样,那么以下语句结果就不言而喻了吧:

(obj.sayHi)();//hello night
//注意:(obj.sayHi())(),是不正确的哦!!!
//因为(obj.sayHi)本身就返回了一个引用对象,不能在()里面直接就运行哦,那你要两个()就没用了。

我还是分析下吧:

  • (obj) = obj;//即,对obj的引用
  • (obj.sayHi) = obj.sayHi();//即,调用对象自己的sayHi方法

引申:

JavaScript 高级程序设计(第三版)7.2.2 关于this对象 中有个例子,看了以上概念,大家肯定更好理解了。

例子在这本书的 183页 最终结果:My Object,大家自己去看。

结论

(expression) 的理解:

  • 返回计算表达式的结果
  • 还可能返回的是一个引用

(expression)() 的作用:

  • 立即执行函数
  • 构建独立不受污染的作用域

参考

  1. Ecma-262
  2. http://stackoverflow.com/questions/4043647/what-does-this-function-a-function-inside-brackets-mean-in-javascript
  3. http://stackoverflow.com/questions/440739/what-do-parentheses-surrounding-a-javascript-object-function-class-declaration-m

HTML解惑-行内元素之间有空隙

有没有发现,行内元素之间有一些空隙?

一、行内元素之间的空隙

先举个栗子

<div>
	<a href="#">1</a>
	<a href="#">2</a>
	<a href="#">3</a>
</div>

展示出来的结果,你发现1、2、3之间并未严格贴在一起,而是之间有一些间隙?

这就是行内元素之间默认的间隙!

本文简单的讲下出现这种问题的常见的场景!

二、行内元素的形成

2.1 默认的行内元素存在间隙

如:aspanimg

2.2 强制将元素属性设置为 inline 或者 inline-block

div{
	display : inline;/* inline-block */
}

三、空隙带来的负作用

  1. 无法准确控制元素之间的间隙;
  2. 4个25%的inline-block元素,会出现换行(即所有元素间隙之和大于100%);
  3. 如果是图片的话,100%宽度显示图片时,父容器的底部会有间隙,除非把图片设置为:block;

四、常见的解决办法

参考:https://css-tricks.com/fighting-the-space-between-inline-block-elements/

JS解惑-一元操作符+操作

你知道 +new Date() 结果等于多少吗?

背景

昨天看到一种书写方式,当时就有点懵,写法如下:

var time = +new Date();

好奇之,就Google了一下,豁然开朗,于是分享之……

结果

以上代码,等于如下代码:

function(){
   return Number(new Date);
}

所以,结果其实就是 当前时间的时间戳,也等于如下结果:

new Date().getTime();//20170329205038

引申

+ 可以与以下类型进行运算:

  • undefined
  • null
  • Object
  • Array
  • Boolean
  • Date
  • String

结果如下:

+undefined

+undefined = NaN(Not As Number的意思)

扩展: undefined 和其它任何值进行任何运算结果都是NaN,比如:1+undefined,6*undefined等

+null

扩展: 1+null = 1

+Object

  • +{} = NaN
  • +new Object(1) = 1 (其实就等于+new Number(1) = +1)

+Array

  • +new Array(1) = undefined (申明一个1维的空数组,第1个元素的值未定义,+一维数子就 = +数组中的第一个元素)
  • +[2] = 2(其实等于+2,所以结果就等于2)
  • +[2,3] = NaN(如果是多维数组,那么:+数组 = +对象 = NaN)

扩展: +[2,3][1] = 3(相当等于 +3 = 3)

+Boolean

  • +true = 1
  • +false = 0
  • +new Boolean(1) = 1
  • +new Boolean(0) = 0

+Date

  • +new Date() = 20170329213914 (等于 new Date().getTime())
  • +new Date = 20170329213914 (补充一下:其实构造函数可以简写,也就是可以不带括号,语法是正确的,只是这种写法我们不提倡)
  • +new Date(1) = 1 (其实 new Date(1).getTime() = 1)

+String

  • +'' = 0
  • +'1' = 1
  • +'a' = NaN

其实,+String我们经常见,就是把String转换为数子然后操作,如果无法转换为数字,那么结果就是NaN啦!

参考

(全文完)

WEB解惑-URL双斜杠开头//

大家有没有发现,在一些免费的CDN站点提供的JS或者CSS的路径都是以 // 开头的?

比如:

<link href="//cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">

概念

// 开头的资源地址,其实专有名词叫:Protocol-relative URL,也就是:相对协议URL

提起相对协议URL,其实我们实际中也经常用到,大家常见的有:

作用

这么书写的作用就是:浏览器在请求资源时会依据当前页面的协议头来决定加载资源的协议头。

比如:当前页面是 https 开头的,那么依赖的这个文件就会以 https 协议去加载,否则使用 http加载。

WHY

为什么要这么做呢?不是js和css可以随便引入吗?为啥还要分协议呢?我统一使用 http 开头来加载资源有啥问题呢?

  • 如果你的站点就是 http 的,那么没一点问题。

  • 如果你的站点是 https 的,那么默认是不建议引入 http 的资源的,资源包括:js、css、图片等。在Chrome、Firefox等浏览器下,控制台会打印警告!而在一些版本的IE浏览器下,会弹出如下警告:

  • 如果你引入的 http 资源中又引入了别的资源,会直接被浏览器决绝掉,提示:跨域请求失败的错误信息。

    • 比如:JS中动态创建JS;
    • 比如:CSS中通过font-face引入字体;

解决方案

知道了为什么会出现这个问题,那么我们经常引入一些外部资源的时候,就会有以下常见的办法。

  • 使用上面提到的 Protocol-relative URL 双斜杠的办法,让浏览器自动匹配,但是这种方法可能有兼容性问题,比如在IE6上,或者一些比较特殊的WEB容器中,就无法解析,这时候的处理办法,可以参考:
<!-- 优先取得CDN上的资源 -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>

<!-- 如果CDN资源获取失败,则动态加载本地资源 -->
<script>!window.jQuery && document.write(unescape('%3Cscript src="js/libs/jquery-1.4.2.js"%3E%3C/script%3E'))</script>
  • 判断当前页面的 protocol 动态加载js,比如:
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

这两种方法现在都比较常用,第一种一般用在CDN资源的引入上,而第二种一般用在第三方脚本引入上。比如:Google分析、百度分析、多说评论框等等,引入一段脚本的时候都会这么做。

比如,大家看看多说评论框的JS脚本:

<!-- 多说评论框 start -->
<div class="ds-thread" data-thread-key="请将此处替换成文章在你的站点中的ID" data-title="请替换成文章的标题" data-url="请替换成文章的网址"></div>
<!-- 多说评论框 end -->
<!-- 多说公共JS代码 start (一个网页只需插入一次) -->
<script type="text/javascript">
var duoshuoQuery = {short_name:"your name"};
(function() {
	var ds = document.createElement('script');
	ds.type = 'text/javascript';ds.async = true;
	ds.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//static.duoshuo.com/embed.js';
	ds.charset = 'UTF-8';
	(document.getElementsByTagName('head')[0] 
	 || document.getElementsByTagName('body')[0]).appendChild(ds);
})();
</script>
<!-- 多说公共JS代码 end -->

推荐直接用HTTPS

那为什么不直接进入 https 的资源呢?

其实是可以的!只是以前,人们一直以为 https 协议请求相对来说比较耗时,所以大家一般都不用,或者有些资源网站也不支持 https

但是现在技术发展挺快,https 也已经很稳定、高效,并且最关键的一点,在 http 网站中引入 https 是可以的!

所以,如果你现在引入资源的话,建议直接引入 https 的资源(如果支持),而且 https 网站也是趋势!

延伸

反过来看待上面的 // 的这种做法,许多人这么评价: protocol-relative URLs are an anti-pattern,翻译过来就是:相对协议URL是一种反模式

意思就是说:其实 HTTPS 才是大势所趋,我们应该鼓励直接用 HTTPS,而不是妥协它做一些兼容性的解决方案。

比如这篇文章的观点:Moving CDNs to HTTPS

参考

(全文完)

我对知识的认知

总结下我最近对于知识的认知,希望能与君共勉。

1、知识要成体系,即画圆。

  • 工作几年总觉得自己很牛,但是在面试被问到一些具体问题,就语无伦次,最后心理还犯嘀咕:我能很快做出很多东西,问那么多有个鸟用!!!
  • 后来我意识到,自己真是个傻缺!如果我的知识真的画圆了的话,你从任何一个位置问我,我都能左右关联回答你,这体现在工作中,不是能更周全的想问题吗?
  • 我现在的体会,凡是某个知识点说不上来个123的,基本都是没掌握牢固,或者半瓶子晃荡!

2、读书很重要,是真的。

  • 上学的时候,有个数学老师下棋很厉害,基本全市数一数二,很多人找他下棋,他会问一个问题:你看过棋谱没?凡是没看过的,他都婉拒了。后来他说,我不太喜欢和没谱的人下棋。
  • 而我之前的特点是喜欢先动手搞出结果(我自信自己有创造天赋),再看书,这样我会很有动力看,要不然我就实在没兴趣看书。但是后来,随着时间推移,我在动手的过程中,想起了很多之前硬逼着自己看的一些书中说的理论!而恰恰是这些理论,让我做出了很多美好的结果。于是我慢慢的喜欢读书了,其实也就是这2年的事情。
  • 所以,如果你不喜欢读书,那么抽时间找感兴趣的点,逼迫自己先读一点点,从中找到认知的地方。如果哪一天你用到了这个知识点,而且你能从中获益,你就会跟我一样,慢慢喜欢读书,这就跟一层窗户纸一样,以后也会喜欢读书,而且会吸收的多。
  • 读书破万卷,下笔如有神!程序猿更是这样,尤其半路出家做前端WEB的,更应该多看看书。

js解惑-函数执行顺序

js解惑-函数执行顺序

背景

JS基础知识温习。

函数解析原理

分3个阶段:

  1. 准备(Hoisting)
  2. 装载(填充数据)
  3. 执行(逐行处理)

准备

本阶段就是书本中所说的Hoisting,包括:形参变量创建函数体内变量提升创建函数申明创建

就是先把函数中所有的变量或者声明的函数名都先定义好,空间都开辟好。

关于准备阶段的特别说明:

  1. 如果变量已经定义过,则不会重新定义(比如:形参中有个参数a,并且调用函数时传了值进来,这时候函数中还有个变量a,那么在这一阶段,函数中的变量a是不会重新定义,形参中的值也不会被覆盖。);

装载

这里装载填充的数据包括:形参申明的函数体

也许你要问了,为什么一般的变量只是拿到前面定义好,此时值是 undefined,填充数据需要等到执行那一行才进行,而 形参申明的函数 在代码执行前就要装载好呢?

答: 我个人的理解是(有更专业的解释,欢迎批评指正):

  • 形参 是外部传进来的,是函数在执行前就已经知道的数据,所以直接就装载上;而对于函数中普通的变量,受限于JS解析顺序的机制影响,只能等到具体执行到那一行时才能知道。
  • 函数申明 为什么要放到前面去呢?这应该也是JS的策略吧,不然函数表达式(var xxx=fn)为啥就没这个待遇呢?

关于装载的特别说明:

  1. 数据装载的数据为:函数形参 > 函数申明
  2. 对于函数声明装载时,如果已经有相同的函数名的话,会覆盖前面的(比如:形参中有个参数a,并且外部给他赋了值,这时候函数中如果有个函数申明a,那么在这一阶段,形参中的a就会被覆盖为这个函数)。

执行

通过上面的2个阶段,大家就知道,当函数真正一行行开始执行的时候,其实有些值已经存在了,并不是大家想象中的全部为 undefined

本阶段就是纯粹的执行代码了,执行就包括了:变量赋值、对象调用等等。

但是本阶段其实JS引擎还做了另外一件事情,就是:代码检查。如果报错了,会直接中断程序,除非使用 try/catch 捕获。

示例一

function test() {
    console.log('1、a=',a);
    var a = b = 123;
    console.log('2、a=',a);
    console.log('3、b=',b);
};
test();
console.log('4、b=',b);
console.log('5、a=',a);

分析

第一步:准备

变量提升(Hoisting),这一步执行后,实际的代码变为:

function test() {
    var a;
    console.log('1、a=',a);
    b=123;
    a=b;
    console.log('2、a=',a);
    console.log('3、b=',b);
};
test();
console.log('4、b=',b);
console.log('5、a=',a);

补充说明: 关于 var a = b = 123 的解释,请移步: JS解惑-连等表达式

第二步:装载

由于函数没有形参和函数申明,所以该步直接跳过。

第三步:执行

//1 var a;
//2 console.log('1、a=',a);
//3 b=123;
//4 a=b;
//5 console.log('2、a=',a);
//6 console.log('3、b=',b);
  • //1 变量定义:a,默认值:undefined
  • //2 打印变量:a,输出:1、a= undefined
  • //3 给变量b赋值为123,由于变量b在test函数中未定义,所以js引擎就会默认在全局对象window所对应的对象下面创建属性b,并且为其赋值为123,window.b=123;
  • //4 将b的值赋给a,这时候a就等于123,a=123;
  • //5 打印变量:a,输出:2、a= 123
  • //6 打印变量:b,输出:3、b= 123

最后:函数执行完后,局部变量立即销毁,全局变量仍然保留。

//7 console.log('4、b=',b);
//8 console.log('5、a=',a);
  • //7 由于b为全局变量,执行完函数后未被销毁,所以输出:4、b= 123
  • //8 由于a在函数执行后已经销毁,而全局变量又没有a,所以打印时就报错了;

最终结果

补充说明: 错误类型为 ReferenceError 引用错误,也就是说系统根本不知道哪个对象或者函数下面的属性a,所以会报这个错误。如果这时候你打印 window.a,那么结果将是 undefined 而不会报错。

示例二

function test(a) {
    console.log('1、a=',a);
    var a=123;
    console.log('2、a=',a);
    function a(){};
    console.log('3、a=',a);
};
test(1);

分析

第一步:准备

定义变量a,代码变为:

function test(a) {
    var a;
    console.log('1、a=',a);
    a=123;
    console.log('2、a=',a);
    function a(){};
    console.log('3、a=',a);
};

第二步:装载

  1. 先将形参的值1赋值给a,a=1;
  2. 将函数申明赋值给a,a=function(){};

装载完毕后,代码变为:

function test(a) {
    var a = function(){};
    console.log('1、a=',a);
    a=123;
    console.log('2、a=',a);
    console.log('3、a=',a);
};

第三步:执行

看到第二步装载完毕后的代码,那么结果也就很清楚了。

最终结果

示例三

function test(a,b){
    console.log('1、a=',a);
    c=0;
    var c;
    console.log('2、c=',c);
    a=3;
    b=2;
    console.log('3、b=',b);
    function b(){};
    function d(){};
    console.log('4、b=',b);
};
test(1);

分析

准备

找到所有的局部变量,注意包含形参中的,包括:

  • a,来自:形参
  • b,来自:形参、函数申明
  • c,来自:局部变量
  • d,来自:函数申明

于是原函数就变为:

function test(a,b){
    var a;
    var b;
    var c;
    var d;

    console.log('1、a=',a);
    c=0;
    console.log('2、c=',c);
    a=3;
    b=2;
    console.log('3、b=',b);
    function b(){};
    function d(){};
    console.log('4、b=',b);
};
test(1);

装载

注意装载的顺序:形参先装载,其次是函数声明,而且函数申明会覆盖已定义的变量。

于是函数就变成为:

function test(){
    var a=1;
    var b=function(){};//实参并没有传第2个参数,默认为undefined;但后来又被函数申明覆盖了。
    var c=undefined;//没有地方为其赋值
    var d=function(){};

    console.log('1、a=',a);
    c=0;
    console.log('2、c=',c);
    a=3;
    b=2;
    console.log('3、b=',b);
    console.log('4、b=',b);
};
test(1);

执行

先看看装载后的函数的执行结果:

所以,其实这类题目最难的就是分析阶段,包括:(准备、装载),一旦这2个阶段处理好,执行阶段基本就是直接打印结果了。

最终结果

示例四

最后一道带一些逻辑,可能会影响到分析阶段的。

function test(a,b){
    console.log('2、b=',b);
    if(a){
        var b=100;
    };
    console.log('3、b=',b);
    c=456;
    console.log('4、c=',c);
};
var a;
console.log('1、a=',a);
test();
a=10;
console.log('5、a=',a);
console.log('6、c=',c);

分析

准备

这类题目,先不用管函数体外的代码,因为函数准备和装载,跟外部代码怎么执行没关系。

  • a,来自形参、局部变量;
  • b,来自形参、局部变量;(注意:函数的准备阶段,是不用管是否有if的,只要看到var b,就一定会提前)

对于变量前没有var申明的,说明是全局变量,不用理会。

于是代码变为:

function test(a,b){
    var a;
    var b;

    console.log('2、b=',b);
    if(a){
        b=100;
    };
    console.log('3、b=',b);
    c=456;
    console.log('4、c=',c);
};
var a;
console.log('1、a=',a);
test();
a=10;
console.log('5、a=',a);
console.log('6、c=',c);

装载

装载阶段依然只管函数体内。

function test(){
    var a=undefined;
    var b=undefined;

    console.log('2、b=',b);
    if(a){
        b=100;
    };
    console.log('3、b=',b);
    c=456;
    console.log('4、c=',c);
};
var a;
console.log('1、a=',a);
test();
a=10;
console.log('5、a=',a);
console.log('6、c=',c);

执行

先增加一个行号:

function test(){
      var a=undefined;
      var b=undefined;

//7   console.log('2、b=',b);
      if(a){
         b=100;
      };
//8   console.log('3、b=',b);
      c=456;
//9   console.log('4、c=',c);
};
//1 var a;
//2 console.log('1、a=',a);
//3 test();
//4 a=10;
//5 console.log('5、a=',a);
//6 console.log('6、c=',c);
  • 第1行、第2行,由于没有test参与,所以结果直接就打印:a=undefined;
  • 第3行开始进入函数体内;
  • 第7行,b=undefined;
  • 第8行,因为上方if(a)条件为假,所以b并没有赋值100,所以:b=undefined;
  • 第9行,由于局部并没有变量c,于是就找全局变量c,这时候恰恰是上方赋值的c=456,所以这时候:c=456;
  • 函数体内执行完毕,第4行,给外层函数的局部变量a赋值为10
  • 第5行,a=10;
  • 第6行,由于在函数体内赋值了一个全局变量c=456,函数执行完并没有销毁,所以这里:c=456;

最终结果

彩蛋

通过以上讲解,中间穿插了一些基础知识,这里跟大家简要总结分享下。

函数的定义

函数的定义有三种形式:

  • 函数申明

    function fun1(){};
  • 函数表达式

    var fun2 = function(){};
  • 构造函数Function

    var fun3 = new Function("a", "b", "return a * b");

变量提升(Hoisting)

就是将函数体内的 局部变量函数申明 放到函数的最前面定义。

连等表达式

请移步: JS解惑-连等表达式

什么是形参、实参

function fun4(var1,var2){//函数结构体括号内的变量,就叫做形参。
    //TODO
};
fun4('abc',123);//调用函数时,实际传的值就叫做实参。

GO和VO对象是什么

  • VO,Variable Object,变量对象。这是一个伪对象,是用在函数体内,在函数 准备 阶段时,用来存放准备的数据的,这些数据包括3类:形参、变量和函数声明。
    • VO对象,不能直接在函数中访问,因为其实它只是一种说法而已,用来表示数据的存储行为的。
    • 所以,上文中就没提这个概念,你要用的时候,直接就访问函数的变量了,跟这个对象没啥关系,大家也可以不用太关心,只需要了解即可。
  • GO,Global Object,全局对象。这个概念是跟VO有点对立,就是全局存在的一个对象,这个对象一般指的是 window 对象。

参考

(全文完)

移动端H5解惑-页面适配(二)

一、基础概念

在了解如何做H5页面适配前,大家都应该把移动端涉及的一些概念搞明白,比如:dpr 是什么意思?

移动端H5解惑-概念术语(一)

二、为什么要做页面适配

2.1 PC端为什么要解决浏览器兼容

因为在PC端,由于浏览器种类太多啦,比如几个常用的:IE、火狐、Chrome、Safari等。同时,由于历史原因,不同浏览器在不同时期针对当时的WEB标准有一些不一样的处理(虽然大部分一样),比如:IE6、IE8、IE10+等对于一些JS事件处理、CSS样式渲染有所不同。

而恰恰又有一些人在使用着不同类型的浏览器,以及不同浏览器的不同版本。所以,为了能让你的网站让这些不同的人看到效果一致,你就不得不做兼容,除非这些人不是你的目标用户。

2.2 移动端为什么要做适配

在移动端虽然整体来说大部分浏览器内核都是webkit,而且大部分都支持CSS3的所有语法。但是,由于手机屏幕尺寸不一样,分辨率不一样,或者你需要考虑横竖屏的问题,这时候你也就不得不解决在不同手机上,不同情况下的展示效果了。

另外一点,UI一般输出的视觉稿只有一份,比如淘宝就会输出:750px 宽度的(高度是动态的一般不考虑)(详情),这时候开发人员就不得不针对这一份设计稿,让其在不同屏幕宽度下显示 一致

一致是什么意思?就是下面提到的几个主要解决的问题。

三、页面适配主要解决的问题

  1. 元素自适应问题
  2. 文字rem问题
  3. 高清图问题
  4. 1像素问题
  5. 横竖屏显示问题
  6. 手机字体缩放问题

3.1 元素自适应问题

举个栗子:

1080px 的视觉稿中,左上角有个logo,宽度是 180px(高度问题同理可得)。

那么logo在不同的手机屏幕上等比例显示应该多大尺寸呢?

其实按照比例换算,我们大致可以得到如下的结果:

  • 在CSS像素是 375px 的手机上,应该显示多大呢?结果是:375px * 180 / 1080 = 62.5px
  • 在CSS像素是 360px 的手机上,应该显示多大呢?结果是:360px * 180 / 1080 = 60px
  • 在CSS像素是 320px 的手机上,应该显示多大呢?结果是:320px * 180 / 1080 = 53.3333px

以下就是一些实现思路:

方案1:使用css的媒体查询 @media

@media only screen and (min-width: 375px) {
  .logo {
    width : 62.5px;
  }
}

@media only screen and (min-width: 360px) {
  .logo {
    width : 60px;
  }
}

@media only screen and (min-width: 320px) {
  .logo {
    width : 53.3333px;
  }
}

这个方案有2个比较突出的问题:

  1. 如果再多一种屏幕尺寸,就得多写一个 @media 查询块;
  2. 页面上所有的元素都得在不同的 @media 中定义一遍不同的尺寸,这个代价有点高。

方案2:使用 rem 单位

注意我们的推导公式:

  • 在CSS像素是 375px 的手机上,应该显示多大呢?结果是:375px * 180 / 1080 = 62.5px
  • 在CSS像素是 360px 的手机上,应该显示多大呢?结果是:360px * 180 / 1080 = 60px
  • 在CSS像素是 320px 的手机上,应该显示多大呢?结果是:320px * 180 / 1080 = 53.3333px
@media only screen and (min-width: 375px) {
  html {
    font-size : 375px;
  }
}

@media only screen and (min-width: 360px) {
  html {
    font-size : 360px;
  }
}

@media only screen and (min-width: 320px) {
  html {
    font-size : 320px;
  }
}

.logo{
	width : 180rem / 1080;
}

方案2有效的解决了方案1中,同一个元素需要在多个 media 中写的问题,这里只需要多定义几个 media 就可以大致解决问题了。

但是本方案仍然有以下问题:

  1. 针对不同的手机分辨率,需要写多套 @media 查询语句,多一种机型就需要多写一套查询语句,而且随着现在手机的层出不穷,这个页面很有可能在一些新出的机型上有问题。
  2. 每个元素都需要除以1080这个设计稿的尺寸。

针对除以1080我们能不能直接放到html的font-size上,变成这样:

@media only screen and (min-width: 375px) {
  html {
    font-size : 375px / 1080;
  }
}

@media only screen and (min-width: 360px) {
  html {
    font-size : 360px / 1080;
  }
}

@media only screen and (min-width: 320px) {
  html {
    font-size : 320px / 1080;
  }
}

.logo{
	width : 180rem;
}

如果变成这样,那么 .logo 的css中就只需要按设计稿的尺寸大小写就可以了。到底可不可以呢?

答案是:不可以

主要原因是,浏览器有最小字体限制:

  • PC上最小 font-size=12
  • 手机上最小 font-size=8

如果小于最小字体,那么字体默认就是最小字体。

再来看上面的css,比如:

@media only screen and (min-width: 375px) {
  html {
    font-size : 375px / 1080; //0.347222px
  }
}

.logo{
	width : 180rem; //期望结果:375px / 1080 * 180 = 62.5px
}

所以当你这么设置font-size时,在手机上由于小于最小字体8px,所以页面会按照默认字体算。

所以,最终就相当于你是这么设置的:

@media only screen and (min-width: 375px) {
  html {
    font-size : 8px;
  }
}

.logo{
	width : 180rem; //实际结果:1440px
}

所以,大家在设置html的font-size的时候一定要保证最小等于8px!

因而为了解决这个问题,建议大家使用Sass这种Css开发语言,可以定义公式的,这样写css就方便了。

最终使用Sass的代码如下:

@media only screen and (min-width: 375px) {
  html {
    font-size : 375px;
  }
}

@media only screen and (min-width: 360px) {
  html {
    font-size : 360px;
  }
}

@media only screen and (min-width: 320px) {
  html {
    font-size : 320px;
  }
}

//定义方法:calc
@function calc($val){
    @return $val / 1080;
}

.logo{
	width : calc(180rem);
}

以上方案虽然解决了问题,但任然有以下缺陷:

  1. 不同的尺寸需要写多个 @media
  2. 依赖css的开发工具,比如:sass/less等
  3. 所有涉及到使用rem的地方,全部都需要调用方法 calc() ,这个也挺麻烦的。

方案3:js动态设置根字体

由于方案2最主要的问题就是需要针对不同的屏幕尺寸,定义多个 @media,所以我们先将这个字体设置改为动态设置。

注意我们的推导公式:

  • 在CSS像素是 375px 的手机上,应该显示多大呢?结果是:375px * 180 / 1080 = 62.5px
  • 在CSS像素是 360px 的手机上,应该显示多大呢?结果是:360px * 180 / 1080 = 60px
  • 在CSS像素是 320px 的手机上,应该显示多大呢?结果是:320px * 180 / 1080 = 53.3333px
//获取手机屏幕宽度
var deviceWidth = document.documentElement.clientWidth;

//将方案二中的media中的设置,在这里动态设置
//这里设置的就是html的font-size
document.documentElement.style.fontSize = deviceWidth + 'px';

需要注意

document.documentElement.clientWidth 这个语句能获取到的准确的手机尺寸的前提是建立在html中设置了如下标签:

<meta name="viewport" content="width=device-width">

要不然获取到的结果将始终是:980(查看原因

然后Sass中就可以按照设计稿的尺寸大小去写就行了:

//定义方法:calc
@function calc($val){
    @return $val / 1080;
}

.logo{
	width : calc(180rem);
}

如果不考虑别的因素,只是页面元素大体适配的话,该方案基本就满足要求了,但是现实中我们其实还有很多问题,所以我们的方案还需要继续优化。

3.2 文字rem问题

文字也采用rem的单位主要有什么问题呢?

  1. 可能会出现通过rem计算,最终呈现到页面上是 23.335px 这样的奇葩的字体大小,可能还会因此出现锯齿、模糊不清等问题;
  2. 对于大屏希望一行显示的文字多一些,小屏显示的少一些,而不是一视同仁的全部显示同样多的文字。这样在大屏下显得文字特别大(下文 3.5 横竖屏显示问题 会仔细讲)。

对于以上问题,我个人的建议

  1. 对于出现奇葩字体的问题,其实手机上表现并没那么明显,主要原因是现在屏幕显示效果统一编号,另外html对字体显示也做优化,所以,如果产品要求不严格,可以不用考虑处理;
  2. 对于横竖屏问题,看情况吧,如果要求不严格,也可以不用考虑。

如果一定要解决这个问题,那么字体就不要使用rem方案了,而是继续使用px单位。

我们上面提到 大屏 小屏 其实隐含的意思并不是手机屏幕大,而是手机的屏幕分辨率不一样,其实就是dpr不一样,所以我们针对不同的dpr设置具体的字体就可以了。

比如,我们针对页面的标题的字体大小就可以如下设置:

.title {
    font-size: 12px;
}
[data-dpr="2"] .title {
    font-size: 24px;
}
[data-dpr="3"] .title {
    font-size: 36px;
}

3.3 高清图问题

先来看看 这里 这篇文章,有讲解了为什么在有些屏幕上要使用 @2x @3x 的高清图。

再来看看 这里 讲解了具体的高清图的解决方案。

我这里简单归纳下。

3.3.1 对于 <img> 标签引入的图片高清解决方案

1、使用 srcset 标签

<img src="http://g.ald.alicdn.com/bao/uploaded/i1/TB1d6QqGpXXXXbKXXXXXXXXXXXX_!!0-item_pic.jpg_160x160q90.jpg" srcset="http://img01.taobaocdn.com/imgextra/i1/803091114/TB2XhAPaVXXXXXmXXXXXXXXXXXX_!!803091114.jpg 2x, http://gtms04.alicdn.com/tps/i4/TB1wDjWGXXXXXbtXVXX6Cwu2XXX-398-510.jpg_q75.jpg 3x">

关于 srcset 的说明:猛戳这里

2、使用js自带的 Image 异步加载图片

<img id="img" data-src1x="[email protected]" data-src2x="[email protected]" data-src3x="[email protected]"/>
var dpr = window.devicePixelRatio;
if(dpr > 3){
	dpr = 3;
};

var imgSrc = $('#img').data('src'+dpr+'x');
var img = new Image();
img.src = imgSrc;
img.onload = function(imgObj){
	$('#img').remove().prepend(imgObj);//替换img对象
};

3.3.2 对于背景图片高清解决方案

1、使用 media query 来处理

/* 普通显示屏(设备像素比例小于等于1)使用1倍的图 */
.css{
    background-image: url(img_1x.png);
}

/* 高清显示屏(设备像素比例大于等于2)使用2倍图  */
@media only screen and (-webkit-min-device-pixel-ratio:2){
    .css{
        background-image: url(img_2x.png);
    }
}

/* 高清显示屏(设备像素比例大于等于3)使用3倍图  */
@media only screen and (-webkit-min-device-pixel-ratio:3){
    .css{
        background-image: url(img_3x.png);
    }
}

2、使用 image-set 来处理(有兼容问题)

.css {
    background-image: url(1x.png); /*不支持image-set的情况下显示*/
    background: -webkit-image-set(
            url(1x.png) 1x,/* 支持image-set的浏览器的[普通屏幕]下 */
            url(2x.png) 2x,/* 支持image-set的浏览器的[2倍Retina屏幕] */
            url(3x.png) 3x/* 支持image-set的浏览器的[3倍Retina屏幕] */
    );
}

3.4 1像素问题

什么是 1像素问题 ?

我们说的1像素,就是指1 CSS像素。

比如设计师实际了一条线,但是在有些手机上看着明显很粗,为什么?

因为这个1px,在有些设备上(比如:dpr=3),就是用了横竖都是3的物理像素矩阵(即:3x3=9 CSS像素)来显示这1px,导致在这些设备上,这条线看上去非常粗!

其实在在中手机上应该是1/3px显示这条线。

关于 dpr,不理解的,可以看看之前的 这篇 文章。

问题描述清楚了,我们该怎么处理呢?

方案1:使用css3的 scaleY(0.5) 来解决

比如:div的border-top的1px问题解决。

.div:before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: auto;
  right: auto;
  height: 1px;
  width: 100%;
  background-color: #c8c7cc;
  display: block;
  z-index: 15;
  -webkit-transform-origin: 50% 0%;
          transform-origin: 50% 0%;
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
  .div:before {
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
  }
}
@media only screen and (-webkit-min-device-pixel-ratio: 3) {
  .div:before {
    -webkit-transform: scaleY(0.33);
            transform: scaleY(0.33);
  }
}

但是,这种方案只能解决直线的问题,涉及到圆角之类的,就无能为力!

方案2:页面缩放解决问题

我们先来讲讲页面缩放能解决1px问题的原理示。

首先大家需要了解一些 viewport 的常识,参考:这里

假如以下手机的 dpr=2

对于dpr=2的手机设备,1px就会有 2x2 的物理像素来渲染,但是当缩放以后其实就变成 1x1 个单位渲染了,看下面示意图:

所以,我们的思路就是将真个页面缩小dpr倍,再将页面的根字体放大dpr倍。这样页面虽然变小了,但是由于页面整体采用rem单位,当根字体放大dpr倍以后,整体都放大了,看上去整体样式没什么变化。

1、将scale设置为1/dpr

假如:dpr = 2

<meta name="viewport" content="width=device-width,initial-scale=0.5">

2、clientWidth获取的值会自动扩大dpr倍

比如,以前是360px,当页面缩小0.5倍,获取到的值会变为720px。

不知道这个原理的,这篇文章讲的还是比较清楚:这里

var deviceWidth = document.documentElement.clientWidth;
document.documentElement.style.fontSize = deviceWidth + 'px';

3、css中涉及到1像素问题的地方使用 px 作为单位

比如:

.box{
	border : 1px solid #ddd;
}

以上步骤最终整理的结果:

html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>
    <div class="box">
        <div class="logo">Logo</div>
    </div>
</body>
</html>

js:

//获取屏幕宽度、dpr值
var deviceWidth = document.documentElement.clientWidth,
    dpr = window.devicePixelRatio || 1;

//设置根字体扩大dpr倍
//由于deviceWidth当页面缩小dpr倍时,本身获取的值就增加dpr倍
//所以这里不需要再乘以dpr了
document.documentElement.style.fontSize = deviceWidth + 'px';

//设置页面缩放dpr倍
document.getElementsByName('viewport')[0]
    .setAttribute('content','width=device-width;initial-scale=' + 1/dpr)

scss:

@function calc($val){
    @return $val / 1080;
}

.logo{
	width : calc(180rem);
}

.box{
	border : 1px solid #ddd;
}

3.5 横竖屏显示问题

横竖屏问题,就是当你横屏手机、竖屏手机时看到的不一样的效果问题。

我这里要说的这个问题,就是设计师会针对横屏或者竖屏,做不一样的设计,比如:横屏时显示摘要,竖屏时只有标题,这种情况下,我们应该怎么适配的问题。

关于横竖屏问题,我将会分2个部分来说明:

  1. 横竖屏显示内容不同;
  2. 横竖屏显示样式不同;

3.5.1 横竖屏显示内容问题

我们知道横屏,相当于屏幕变宽了,这时候一行显示的内容就可以更多。所以,设计师可能会对横竖屏做2种不同的内容设计,如:

如果设计师本身就设计了2套样式,那么我们只需要准备2套css,依据横竖屏直接显示对应的样式,然后html中做好兼容即可。

下文会将如何判断横竖屏。

3.5.2 横竖屏显示样式问题

这里有个要说的就是,设计师没有设计横屏的样式,那么如果我们按照上文提到的方案去处理,那么就会发现在横屏模式下字体显得非常大(因为屏幕宽了),显示的内容就少了。

这种情况会显得很不协调,虽然还是等比例显示的,只不过需要多拖动下滚动条而已,但是你会觉得很怪。尤其是再有弹出框的时候,会更麻烦,弹出框有时候可能还会显示不完全。。。

比如,下面的样式:

像这种问题,我们上面提到的依据屏幕宽度自动调整根目录font-size的大小,就有点不合适了。这样虽然保证了横向的比例是严格按照设计搞来的,但是显示上非常丑。

所以,我们一般的做法就是在横屏的时候将 deviceWidth=deviceHeight

  • 正常竖屏大小:

  • 横屏时让width=height

  • 不做横竖屏特殊宽度处理时

以上3组画面对比我们得到的效果是:

  1. 在横屏下如果让width=height,那么整体页面的宽度等于竖屏时看到的宽度,整体布局不会有变化,只是纵向看到的内容多少发生了变化;
  2. 如果横屏不做处理,横屏是width其实就等于竖屏时的height,即700px,这时候整体页面显示非常宽,文字比较大。

所以,经过我们的实际对比体验以后,一致认为横屏时让 width=height 体验比较好。

附上核心代码:

var deviceWidth = document.documentElement.clientWidth,
    deviceHeight = document.documentElement.clientHeight

//横屏状态
if (window.orientation === 90 || window.orientation === -90) {
    deviceWidth = deviceHeight;
};

//设置根字体大小
document.documentElement.style.fontSize = deviceWidth + 'px';

3.5.3 附1:JS检测横竖屏

js获取屏幕旋转方向:window.orientation

  • 0 - 正常方向
  • -90 - 屏幕顺时钟旋转90度
  • 90 - 屏幕逆时针旋转90度
  • 180 - 屏幕旋转180度
window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function() {
    if (window.orientation === 180 || window.orientation === 0) { 
        console.log('竖屏状态!');
    };
    if (window.orientation === 90 || window.orientation === -90 ){ 
        console.log('横屏状态!');
    }  
}, false); 

3.5.4 附2:CSS判断横竖屏

  • 写在同一个CSS中:
@media screen and (orientation: portrait) {
  /*竖屏 css*/
} 
@media screen and (orientation: landscape) {
  /*横屏 css*/
}
  • 分开写在2个CSS中,在link中通过media筛选加载:
<!-- 竖屏 -->
<link rel="stylesheet" media="all and (orientation:portrait)" href="portrait.css">

<!-- 竖屏 -->
<link rel="stylesheet" media="all and (orientation:landscape)" href="landscape.css">

3.6 手机字体缩放问题

手机字体缩放是什么问题呢?

就是当你在手机 设置 -> 字体设置 中将字体放大或者缩小,使得手机整体系统字体发生了变化,这时候可能就会影响到H5页面正常的显示。

经过实际测试,这个问题当前发生的概率不是很大,因为很多手机厂商都已经做了保护措施。但是为了保险起见,我们还是有必要进行检测,一旦不一样就要采取措施。

3.6.1 如何检测手机字体不是默认字体

var deviceWidth = document.documentElement.clientWidth;

//设置根字体大小
var setFz = deviceWidth + 'px';
document.documentElement.style.fontSize = setFz;

//获取实际html字体大小
var realFz = window.getComputedStyle(document.documentElement)
	.getPropertyValue('font-size') //如:360px

//比较二者是否相同
if(setFz !== realFz){
    //TODO 设置的字体和实际显示的字体不同
};

3.6.2 如果手机字体不是默认字体如何处理

比如:你想设置的字体大小为100px,但是实际大小却为50px,那么你可以确定其实用户字体是缩放了0.5,这时候你就需要将你的字体扩大1倍,这样才能保证实际页面的字体是100。

所以,按照这个等比例换算以后,我们就需要重新设置页页面的font-size。

var deviceWidth = document.documentElement.clientWidth;

//设置根字体大小
var setFz = deviceWidth + 'px';
document.documentElement.style.fontSize = setFz;

//获取实际html字体大小
var realFz = window.getComputedStyle(document.documentElement)
	.getPropertyValue('font-size') //如:360px

//比较二者是否相同
if(setFz !== realFz){
    //去掉单位px,下面要参与计算
    setFz = parseFloat(setFz);
    realFz = parseFloat(realFz);
    
    //重新计算要设置的新的字体大小
    //公式推导:100 -> 50,x -> 100,求x?
    //即:setFz -> realFz, x -> setFz,求x?
    var x =  setFz * setFz / realFz;
    
    //重新设置html的font-size
    document.documentElement.style.fontSize = x + 'px';
};

这么做理论上已经解决了问题,但是还有点瑕疵,问题就是:

  • 如果字体不是默认字体,首先会设置一次font-size,重新计算后再次设置一次font-size,反应到页面上就是页面可能会快速晃动一次,因为两次设置的字体大小不一样,如果手机配置不高,估计晃动会很明显。

如何解决这个问题?思路就是:

  1. 给页面增加一个隐藏元素,设置其字体大小为100px
  2. 获取这个元素在页面上的实际字体大小是否为100px
  3. 如果设置的和实际的不等,则计算其比例。

最终代码片段如下:

var deviceWidth = document.documentElement.clientWidth;

var setFz = '100px';

//给head增加一个隐藏元素
var h = document.getElementsByTagName('head')[0],
    s = document.createElement('span');
    s.style.fontSize = setFz;
    s.style.display = 'none';
    h.appendChild(s);

//判断元素真实的字体大小是否100px
//如果不相等则获取真实的字体换算比例
var realFz = getComputedStyle(s).getPropertyValue('font-size');

if(setFz !== 'realFz'){
    //去掉单位px,下面要参与计算
    setFz = parseFloat(setFz);
    realFz = parseFloat(realFz);
    
    //由于:var x = setFz * setFz / realFz;
    //公式推导:x -> setFz, y -> deviceWidth
    //所以:var y = deviceWidth * x / setFz;
    
    //重置deviceWidth
    deviceWidth = deviceWidth * setFz / realFz;
};

document.documentElement.style.fontSize = deviceWidth + 'px';

四、最终适配方案(v1.0)

除了上面一步步的分析中间的原理之外,我们可能还需要考虑或者遇到以下问题:

  1. 到底什么时候动态初始化上面的js脚本?
  2. 偶尔还可能会遇到进入页面时 document.documentElement.clientWidth=0 的bug?
  3. 页面晃动?页面空白?页面整体凌乱然后又正常等问题?

所以,在尽可能的解决诸多问题后,最终的脚本如下:

html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>
    <!-- 正文 -->
</body>
</html>

js:

/**
 * @DESCRIPTION 移动端页面适配解决方案 v1.0
 * @AUTHOR      Night
 * @DATE        2018年08月01日
 */
(function(window, document){
    var docEle = document.documentElement,
        dpr    = window.devicePixelRatio || 1,
        scale  = 1 / dpr;
    
    var fontSizeRadio = 1, //手机字体正常比例
        isLandscape   = false;//是否横屏
    
    ///////////////////////// viewport start //////////////////////////////////
    
    //设置页面缩放比例并禁止用户手动缩放
    document.getElementsByName('viewport')[0].setAttribute('content','width=device-width,initial-scale='+scale+',maximum-scale='+scale+',minimum-scale='+scale+',user-scalable=no');
    
	///////////////////////// viewport end //////////////////////////////////
    
    //横屏状态检测
    if (window.orientation === 90 || window.orientation === -90) {
        isLandscape = true;
    };

    ///////////////////// system font-size check start //////////////////////
    
    //试探字体大小,用于检测系统字体是否正常
    var setFz = '100px';

    //给head增加一个隐藏元素
    var headEle = document.getElementsByTagName('head')[0],
        spanEle = document.createElement('span');
        spanEle.style.fontSize = setFz;
        spanEle.style.display = 'none';
        headEle.appendChild(spanEle);

    //判断元素真实的字体大小是否setFz
    //如果不相等则获取真实的字体换算比例
    var realFz = getComputedStyle(headEle).getPropertyValue('font-size');

    if(setFz !== 'realFz'){
        //去掉单位px,下面要参与计算
        setFz = parseFloat(setFz);
        realFz = parseFloat(realFz);

        //获取字体换算比例
        fontSizeRadio = setFz / realFz;
    };
    
    ///////////////////// system font-size check end //////////////////////
    
    var setBaseFontSize = function(){
        var deviceWidth = docEle.clientWidth,
            deviceHeight= docEle.clientHeight;
        
        if(isLandscape){
            deviceWidth = deviceHeight;
        };
        
        docEle.style.fontSize = deviceWidth * fontSizeRadio + 'px';
    };
    setBaseFontSize();
    
    //页面发生变化时重置font-size
    //防止多个事件重复执行,增加延迟300ms操作(防抖)
    var tid;
    window.addEventListener('resize', function() {
        clearTimeout(tid);
        tid = setTimeout(setBaseFontSize, 300);
    }, false);
    window.addEventListener('pageshow', function(e) {
        if (e.persisted) {
            clearTimeout(tid);
            tid = setTimeout(setBaseFontSize, 300);
        };
    }, false);
    
})(window, document);

scss:

//设计稿尺寸大小,假如设计稿宽度750
$baseDesignWidth = 750;

@function calc($val){
    @return $val / $baseDesignWidth;
}

//适配元素采用rem,假如设计稿中元素宽度180
.logo{
	width : calc(180rem);
}

//边框采用px,假如设计稿边框宽度1px
.box{
	border : 1px solid #ddd;
}

五、新页面适配技术可以考虑(v2.0)

如果不太考虑老的手机型号,可以采用 viewport 单位。

由于我本人也没有在项目中使用这个方案,所以不过多发表言论,大家有兴趣的可以研究下。

具体方案细节参考:

六、后记

讲了这么多,这里总结下,任何事情弄懂原理最重要!

比如,当你首次看 使用Flexible实现手淘H5页面的终端适配 这篇文章的时候你会很感慨,感觉很有收获,但是当你实际开始项目的时候,却不知道该怎么下手。

俗话说,台上一分钟,台下十年功。

为了写本文以及姊妹篇,我个人零零散散的时间加起来不下1个月,一直到(2016年12月2日)才发表了本文的第一版。由于收到一些流言反馈和后续知识的积累,于是决定今天(2018年08月01日)再把它重新整理一遍,以让大家更清楚这中间的原理。

因为中间涉及的东西太多,只要有一个知识有些不清楚,可能就会卡克!比如这个概念 dips,不同的文章有不同的说法,而且还给你解释了它跟 dip 的不同,其实就是指 CSS像素,这些人故意发明一些专业词汇,搞的你晕头转向,所以,当你看了我的这两篇文章,也许还是一知半解,这很正常,慢慢来,多多练习,相信你会明白的。

如果还有哪里不清楚,或者本文有错误的地方,感谢批评指正。

本文重新编辑于:2018年08月01日

  1. 针对大家的留言以及个人的反复推敲,重新整理了这遍文章;
  2. 增加针对手机本身字体放大、缩小的解决方案,以及一些新的替代方案思路。

参考

(全文完)

JS解惑-undefined和null

undefinednull本质上没什么区别,只是用法上代表的含义不一样罢了。

前言

为什么说,undefinednull本质上没什么区别?

通过这个来看看:

undefined == null //true
null == undefined //true

另外,再阅读本文之前,建议大家先看看阮一峰老师的博客,以便了解下这二者的历史。

阮一峰 > undefined与null的区别

定义

  • null,表示一个“无”的对象;
  • undefined,表示一个“无”的原始值;

如何理解这2个定义?看看例子:

typeof(null) //object
typeof(undefined) //undefined

另外,二者转换为Number的结果也不一样。

Number(null) // 0
Number(undefined) //NAN

NAN,Not a Number,在IEEE浮点数算术标准(IEEE 754)中定义,表示一些特殊数值。
INF,Infinity的缩写,无穷大的意思,比如:1/0,结果就是Infinity

用途

说到这里大家可能就犯迷糊了,你一方面说没什么本质区别,另一方面通过定义又有区别,这让老夫如何是好?

好吧,接下来看看用途你大概就明白为什么Js这么定义二者了。

二者最大的用途不同在于 阶段不同

  • undefined,用于变量声明时;
  • null,用于变量赋值时;

我们知道变量的声明和赋值是2个不同阶段,只是有时候我们连起来写罢了,比如:

var name = 'night';

这里其实就是声明和赋值一起写了,拆分开来是这样:

var name; // 变量申明
name = 'night'; //变量赋值

好了,当您明白了这个逻辑有,再来看看上面说的二者迭代 阶段不同 是什么意思。

看看下面的两个例子,你可能就释然了:

var name;
console.log(name); //undefined
name = 'night';
console.log(name); //night
name = null;
console.log(name); //null

解释下上面的例子:

  • 先定义一个变量,如果变量未赋值,那么就是 undefined;
  • 如果赋值了,那么 undefined 的任务也就完成了;
  • 但如果赋值为一个 null,那么结果也就是 null 了;

说到这里,你可能又会问:“好端端的我赋值一个 null 作甚?”

好吧,你是一个充满好奇心的人,且听洒家慢慢道来。

关于Null

在Js中,你可以这么归类:“除了基本数据类型外,其余变量全部是对象”。

关于这句话解释下:

  • 基本数据类型,包括:undefined,null,number,string,boolean(个人觉得不应有object)
  • 所有的函数都可归结为对象(只不过创建对象的函数名遵循首字母要大写罢了)

如果按照这么分类,那么 null 的一个作用就凸显了。

给一个变量赋值,基本数据类型可以直接赋值,那么我要赋值一个对象,或者清空一个对象的赋值,该怎么办呢?

那就发面一个空的对象吧,即:null

当然变量被赋值为 null 后,还有另一层作用,那就是方便JS引擎对这个变量进行垃圾回收。

在JS中,凡是不用的变量,JS会定期清理掉。当我们把一个对象设置为 null 时,其实也就等于把这个对象标记为不用了,可以垃圾回收了。

参考

经验分享-jQuery.extend使用注意

经验分享-jQuery.extend使用注意

问题

最近项目中有个需求,就是查询条件可以动态追加,也就是有个按钮,点击后增加查询条件,比如:查询条件中有个select,点击添加,就多一个select。

于是出现的问题是,添加一个select,option的默认值就是上一个select中选中的值,而不是默认选择第一个!

原因

实现思路大致为:

  1. 将select中的值放到一个数组list中;
  2. 每次新增select,就copy这个数组的内容,然后绑定到mvvm框架的vm对象上
  3. 前端渲染出来,同时增加双向绑定功能

大致实现思路是:

var ary = {
    type1 : [{
            id : 1,
            name : 'name1'
        },{
            id : 2,
            name : 'name2'
        }],
    type2 : [{
            id : 3,
            name : 'name3'
        },{
            id : 4,
            name : 'name4'
        }]
};

var vm1 = $.extend({},ary);

页面上使用的是MVVM框架 avalon 的双工绑定 duplex

问题就出现在:

var vm1 = $.extend({},ary);

看下文的分析。

分析

先来看看 jQuery.extend 的用法:

//通过递归的方式,将a对象的所有属性复制到b对象上
$.extend(true, b, a);

//如果不使用递归,而是浅拷贝,则:
$.extend(b, a);
  • 而上面直接使用浅拷贝复制一个对象,也就是每次给 vm1 赋值的时候,其实是 ary 对象的一级节点的引用!
  • 最后这个对象绑定到select中,而页面上变化后,会改变这个对象,其实就间接影响到了 ary 对象。
  • 所以,下次新增的时候,在使用这个 ary 模板的时候,其实已经被更改了。

所以,这个问题最根本的解决办法就是使用深拷贝,即:

var vm1 = $.extend(true,{},ary);

补充

WEB解惑-lib和vendor

web前端目录中的 libvendor 到底有啥区别?

讨论

先来看看定义:

  • lib,即libraries
  • vendor,即third-party libraries

从这个层面上来看,vendor应该是lib的一个子集。

于是,第一种目录关系出现了,即:

|- lib
   |- plugin
   |- util
   |- ...
   |- vendor
	  |- jquery/
	  |- boostrap/ 

以上目录结构也没有错,但是总感觉有些不妥,哪里不妥呢?

就是自己项目的函数库和第三方的混到一起,使得有以下问题:

  • 动静不分,即经常修改的和经常不修改的混合;
  • vendor,有可能使用cdn,到时候剥离起来,还得从lib/目录下删掉,影响整个结构

鉴于以上分析,有人提出了这样的概念:

  • lib,application libraries,也就是自己应用内的函数库
  • vendor,即third-party libraries,也就是第三方库

于是,第二种目录关系出现了,即:

|- lib
   |- plugin
   |- util
   |- ...
|- vendor
   |- jquery/
   |- boostrap/ 

这种目录结构是,lib 和 vendor 是同级的。

结论

经过仔细分析和实际项目使用,个人觉得第二种方案更适合项目规范和开发流程,我们目前就采用第二种方案。

参考

Yahoo!Yslow网站性能优化的23条军规

站在巨人的肩膀上,你才能走的更远!
但前提是你得思考,思考,再思考!重要的事情说三遍,不然你就不是站在肩膀上,而是跟在屁股上!

23条军规

目录

详情

个人觉得,里面的每一条都应该仔细研读!

参考

JS解惑-闭包(closure)

对于JS的闭包(Closure)的概念,以及闭包的作用域具有全局性。我针对我自己的实战感受,这里简单谈谈。

概念

闭包(Closure),是指有权访问另一个函数作用域中的变量的函数。

创建闭包,常见的一种方式就是在一个函数内部创建另一个函数。

概念引自:JavaScript 高级程序设计(第三版)7.2 闭包 一节,178页

示例

举个例子来解释下这个概念:

var name = 'window';
function outerFun(){
    var name = 'night';
    console.log('1',this.name);
    var innerFun = function(){
        console.log('2',name);
        console.log('3',this.name);
    };
    return innerFun;
};

outerFun()();

结果:

1 window
2 night
3 window

分析

示例代码在执行的时候,可以进行如下分解:

var name = 'window';
var fun1 = outerFun();
var fun2 = fun1();//fun2是fun1的闭包
fun2();//最终调用

再来继续加工下:

window.name = 'window';
window.fun1 = window.outerFun();
window.fun2 = window.fun1();//fun2是fun1的闭包
window.fun2();//最终调用

为什么能这么变形呢?因为在最外层,其实所有变量都是window对象的!

经过上面的分解,我们知道最终调用 fun2() 的时候,其实fun2函数,已经相当于是window环境了。

所以,我们知道任何闭包,都是最终都相当于在window环境下执行的,这也就是为什么闭包的作用域具有全局性了。

深究

接下来再来看单独拆分下每个函数:

fun1的函数:

var name = 'window';
function fun1(){
    var name = 'night';
    console.log('1',this.name);
}

为什么 fun1 中的结果是:window 而不是 night?

知识点:

  1. 我们知道,任何一个函数在执行的时候,首先会为其创建一个上下文环境,接着初始化2个变量:thisarguments,fun1中的this(调用它的对象,也叫活动对象),即:window
  2. 全局作用域局部作用域 的最大区别就是,局部作用域在函数内部定义,并且在非闭包的情况下,函数运行完毕就释放掉了。

所以:

console.log('1',this.name);其实就是在window中寻找 name 属性,这个肯定是 window 了,因为在函数之前,我们定了 var name= window.name

至于 fun1 中的 name 其实在函数中,根本就没有被使用,而如果我们不明确指定 this.name,即:

var name = 'window';
function fun1(){
    var name = 'night';
    console.log('1',name);
}

那么结果肯定是:night,再如果fun1内部未定义name,那么最终还是会通过原型链(或者叫上下文)找到全局的name。

fun2的函数:

function fun2(){
   console.log('2',name);
   console.log('3',this.name);
}

fun2所处的活动对象其实有3个:

  1. 自身函数
  2. fun1函数
  3. window函数

console.log('2',name); 会优先从内部寻找 name 变量,没找到!那么就从为其提供闭包的父函数 fun1 中找,找到了,即:night

console.log('3',this.name); 这里明确指定了 this.name 那么,系统就会从原型链开始寻找 this.name ,由于 this 指向的是 window ,那么结果自然就是:window

扩展

其实闭包函数,在我们的日常工作中经常用到。

定义对象过程中经常会涉及到

//通过字面量定义一个对象obj
var obj = {
    name : 'night',
    bindEvent : function(){
        var _self = this;
        $('#btn').click(function(){
            console.log('hello ' + _self.name);
        });
    }
};

方法中包含setTimeout

//3s后显示:hello night
function delayDisplay(){
    var name = 'night';
    setTimeout(function(){
        console.log('hello ' + name);
    },3000);
};

其实,还有很多这方面的例子,但是不管怎么样,你要记住一个概念:函数中的函数。

而且闭包有一个优势:就是能访问创建闭包对象的变量。

Google搜索和手气不错的区别

要了解 Google搜索和手气不错的区别 ,请大家跟着我的思路来慢慢分析。

揭秘

我自从2013年开始,零星时间做 歌德书签 的过程中想了很多很多, 歌德书签 也经历了3个大的版本调整,而大家现在能看到的页面上的所有功能,也是我认为最应该保留的。

注意: 小白用户就可以略过了,因为我建议你使用 hao123。或者如果您的网址收藏不超过10个,那么建议你使用浏览器自带的书签收藏功能。

在做产品的过程中,我对一个观点非常认同,即:“一个产品不在于你能增加多少功能,而在于你能舍弃多少功能。”

歌德书签 中要不要搜索的功能的问题困扰了我很久,最终决定一定要保留!因为既然歌德书签是入口,那么对于已收藏的书签,点击后直接打开目标页;而当用户在书签主页,突然想打开一个未知网站,或者搜素一个关键词的时候,你没有入口,难道让用户再在新标签页打开百度或者Google,搜索吗?那这就会让用户很忧虑,只有收藏的东西才能快速访问,没收藏的还得借助外力。

歌德书签 主要功能在于书签分类收藏,而搜索并非其使命!但是这个又是用户一个附加必要功能。如果搜索做的太厚?自己有点得不偿失,做薄了,用户可能还是得打开百度或者Google搜索。

周鸿祎说过,“自己一定要是自己产品的忠实粉丝,不然做不好产品。”,这点我深有体会。我自己在用的过程中发现,我收藏的网址,只是在一天工作开始的时候点击打开,或者零星工作中打开。而搜索这个功能,在我工作中占了很大一部分比例,也就是这是一个高频的事情。所以,搜索这个功能一定要有,而且要极力做到好用。

再确定了搜索要保留到 歌德书签 中,而且要做好的情况下,我就一直思考这个事情。其中,有一个点是这样的,我后台调用的是百度或者Google的API搜索,这个我只能这么做。但是从体验上,我每次搜索结果后,总是跳转到百度或者Google的搜索列表页面,然后再点击具体结果进入目标地址。

这里就有一个问题,比如我想进入一些官网,如:新浪网、新网微博、腾讯官网、人人网等等已非常明确关键词的网址,我搜索后依然需要先打开列表页,然后这些官网一般都是搜索结果的第一个,再点击进入。

也许你习惯了这个流程,反而没什么感觉,不就是多了一步嘛!但是,当你一旦觉察到这个事情后,你越这么搜索越觉得啰嗦!不信你现在去百度下 歌德书签,再打开 歌德书签,是不是觉得很麻烦?

说到这里,大家应该知道了Google的 手气不错 是干什么用的了吧?它就是直接打开你搜索关键词的最匹配网页,而不需要再看到中间的搜索结果。

话又说回来,怎么百度没有这个功能?肯定不是百度不知道这个事情,而是这个它根本不培养用户的这个使用习惯,为什么呢?

  1. 如果有搜索列表页,是不是可以在列表页上增加广告?
  2. 技术上,不一定能做到你搜索的关键词一定能打开你期望的网址!这也是Google为什么把这个按钮叫 手气不错 了。而如果一旦达不到期望,用户还是要搜索,那还不如直接搜索呢,绕着弯子干嘛?

体会

什么是用户体验?个人理解:舍身处地的使用,触类旁通的思考,持续不断的改进。

反思

关于 歌德书签 的搜索功能,目前很多没做到位!

  1. 为什么让用户切换搜索引擎? 相当然的把判断权利交给用户,让用户埋单。
  2. 用户关心搜索引擎是 百度 Or Google 吗? 其实TA需要的是结果!

改进

用户期望的是最终结果,而不在乎这个结果是通过 百度Google 还是 站内。只要抓住这个点,其实接下来就是实现方式而已。

源码目录树骨架模板

源码目录树构建技巧

曾几何时看到别人的代码目录展示如下,非常的漂亮,于是我就把别人的这棵树拷贝过来,一个个修改变成自己的树,如下:

project
├── dist
   ├── css
      └──  ...
   
   └── js
       ├── ...
          ├── ...
          ├── ...
          └── ...
       
       ├── ...
          ├── ...
          ├── ...
          └── ...
       
       ├── ...
       └── ...

└── src
    ├── css
       ├── ...
          ├── ...
          ├── ...
          └── ...
       
       ├── ...
       └── ...
    
    └── js
        ├── ...
           ├── ...
           ├── ...
           ├── ...
           ├── ...
           └── ...
        
        ├── ...
           ├── ...
           ├── ...
           └── ...
        
        ├── ...
           ├── ...
           ├── ...
           ├── ...
           └── ...
        
        ├── ...     // ...
        └── ...    // ...

现在告诉大家,一个命令就可以搞定了!无论是windows和linux,在shell窗口直接输入:

> tree

关于这个命令的参数,请参考:

HTML解惑-include方法

html为什么要使用include?如何include?

一、为什么要include

我们知道,web的三驾马车:html, js和css,其中:

  • js 我们由传统的所有逻辑写到一个js文件中,现在我们都提倡分模块开发,于是涌现出来 require.jssea.jsCommonJs 等模块加载框架,以及 AMDCMD 等模块加载机制,同时ES6也提供了 classmodules 等机制,目的只有一个:模块化开发。

  • css 我们在页面上通过 link 可以引入各个css文件,在css里面可以使用 import 来引入其他css文件,为什么要这么做?如果所有文件都写在一个里面,那要这么引入干嘛?目的只有一个:模块化开发。

  • html 如何模块化开发?(⊙o⊙)…?顿时脑子里有点懵!其实,html模块化开发,可以有很多办法,比如:

    • 使用js模板引擎,将html模块化为模板,然后在使用的时候通过ajax载入,但是这个不利于SEO;
    • 另一个办法是分模块开发,使用gulp等工具打包的同时,使用gulp的一些include的插件,重新编译html文件;
    • 且看下文!

二、html如何include

通过上面的分析,我们至少有了2种办法,来分模块开发html,但是我们不使用js,也不用gulp等编译工具,有木有办法 include 一个模板?答案是:有。

我们都知道动态脚本语言都有include方法,比如:

  • JSP
<jsp:include>
<jsp:forward>
  • ASP
<!--# include file="file.tpl" -->

但是 html 这种静态的页面,如何实现include呢?答案是:SSI技术。

  • 什么是SSI

SSI 全称是 Server Side Include,也就是服务端引入技术,引入的是什么呢?是CGI。

  • 什么是CGI

CGI 全称是 Common Gateway Interface,也就是通用网关接口,一个在Web服务器中使用的技术。

通过SSI引入CGI就可以实现服务器端Include,那具体怎么做呢?

看看Apache服务器的SSI,你就全然知晓啦!

移步至:https://httpd.apache.org/docs/current/howto/ssi.html

现在知道html中如何使用include了吧,说白了,只要web服务器支持的语法,html就可以写。

以后在html中见到如下语句,不要惊慌,那是SSI,而不是什么PHP语法,也不是JSP语法

<!--# include virtual="xxx" -->

SSI的问题

  1. 特定语法对于特定的WEB容器有强依赖,以后更换WEB容器可能导致程序无法运行;
  2. 性能问题
  3. 安全问题

参考:http://www.t086.com/code/apache2.2/misc/security_tips.html

经验分享-提高生产效率的一些编码技巧(1)

经验分享-提高生产效率的一些编码技巧(1)

本文包含了一些个人开发经验总结和设计模式的总结,希望能给大家有所帮助。

条件语句if使用技巧

将代码少的逻辑放到if中,而更多的逻辑当成正常系处理,更有利于阅读和理解。

方法

原始代码:

if(condition){
    //中间有100行代码
}else{
    //这里只有1行代码!
};

以上逻辑最好的书写规范是:

if(!condition){
    //1行代码!
    return;
};

//剩余100行代码

案例

原始代码:

selectCategory : function(categoryId, isRedirect) {
    if (categoryId) {
        $('#userPersonasTagDrop .input-group').hide();
        $('#tagSubCategory').addClass('subcategory-not-all').show();
        
        walleUtilStorage.setItem(this.storageLocalCategoryId, categoryId);
        walleUtilStorage.setItem(this.storageLocalTagId, '');
        
        isRedirect      // 比如从收藏夹跳转过来
            ? $('#tagSubCategory span:first').text(this.model.queryParam.tagName)
            : $('#tagSubCategory span:first').text('请选择标签');
        isRedirect || this._getPersonasTagList(categoryId, '', true);
        
    } else {
        $('#userPersonasTagDrop .input-group').show();
        $('#tagSubCategory').hide();
        $('#collectBtn').attr('disabled');
        $('.item-group-search').attr('disabled');
    }
}

优化后:

selectCategory : function(categoryId, isRedirect) {
    //优化1
    if (!categoryId) {
        $('#userPersonasTagDrop .input-group').show();
        $('#tagSubCategory').hide();
        $('#collectBtn').attr('disabled');
        $('.item-group-search').attr('disabled');
        return;
    };
    
    $('#userPersonasTagDrop .input-group').hide();
    $('#tagSubCategory').addClass('subcategory-not-all').show();
    
    walleUtilStorage.setItem(this.storageLocalCategoryId, categoryId);
    walleUtilStorage.setItem(this.storageLocalTagId, '');
    
    //优化2
    if(!isRedirect){
        $('#tagSubCategory span:first').text('请选择标签');
        return;
    };
    
    $('#tagSubCategory span:first').text(this.model.queryParam.tagName)
    this._getPersonasTagList(categoryId, '', true);
}

参考

  • 代码大全,第15章

重复语句for循环化(表驱动法)

代码中发现有太多重复的语句,看似相同,但是又有些不同,于是乎就本能的习惯使用if语句写一大片!

其实最好的做法是代码去重!

核心的**: 就是想尽一切办法将相似的部分放到一个对象或者数组中,然后循环处理。

示例1

显示一年内各个月的天数。

  • 最low的写法:
function getDaysByMonth(month){
    var days = 0;    
    if(month == 1){
        days = 31;
    }else if(month == 2){
        days = 28; // 不用考虑闰年的情况
    }else if(month == 3){
        days = 31
    }else if(...){
        ...
    };
    return days;
};
  • 采用表驱动法的思路
function getDaysByMonth(month){
    //创建一张表
    var daysAry = [31,28,31,30,31,30,31,31,30,31,30,31];
    return daysAry[month];
};

示例2

原始代码:

if (currentIdx === model.TAB_SYSTEM) { model.searchKeyWd.systemKeyWd = $searchInput.val(); }
if (currentIdx === model.TAB_MODULE) { model.searchKeyWd.moduleKeyWd = $searchInput.val(); }
if (currentIdx === model.TAB_AUTH) { model.searchKeyWd.authKeyWd = $searchInput.val(); }

优化:

var mapper = [{
    key : model.TAB_SYSTEM,
    model : model.searchKeyWd.systemKeyWd
},{
    key : model.TAB_MODULE,
    model : model.searchKeyWd.moduleKeyWd
},{
    key : model.TAB_AUTH,
    model : model.searchKeyWd.authKeyWd
}];

for(var i=0,size=mapper.length;i<size;i++){
    var obj = mapper[i];
    if(currentIdx === obj.key){
        obj.model = $searchInput.val();
    };
};

示例3

原始代码:

function fun1(){
    confirmTodoObj.todo === todoType.SYSTEM_UPDATE && result && result();
    confirmTodoObj.todo === todoType.MODULE_UPDATE && result && result();
    confirmTodoObj.todo === todoType.AUTH_UPDATE   && result && result();
    confirmTodoObj.todo === todoType.SYSTEM_ADD    && result && result();
    confirmTodoObj.todo === todoType.MODULE_ADD    && result && result();
    confirmTodoObj.todo === todoType.AUTH_ADD      && result && result();
}

优化1:

function fun1(){
    if(!result){
        return;
    };
    
    if(confirmTodoObj.todo === todoType.SYSTEM_UPDATE
        || confirmTodoObj.todo === todoType.MODULE_UPDATE
        || confirmTodoObj.todo === todoType.AUTH_UPDATE
        || confirmTodoObj.todo === todoType.SYSTEM_ADD
        || confirmTodoObj.todo === todoType.MODULE_ADD
        || confirmTodoObj.todo === todoType.AUTH_ADD){
    
        result();
    };
}

优化2:

function fun1(){
    if(!result){
    return;
};

switch(confirmTodoObj.todo){
    case todoType.SYSTEM_UPDATE:
    case todoType.AUTH_UPDATE:
    case todoType.SYSTEM_ADD:
    case todoType.MODULE_ADD:
    case todoType.AUTH_ADD:
        result();
        break;
};

最终优化:

function fun1(){
    if(!result){
        return;
    };
    
    var checkKeyList = [
        'SYSTEM_UPDATE',
        'AUTH_UPDATE',
        'SYSTEM_ADD',
        'MODULE_ADD',
        'AUTH_ADD'
    ];
    
    for(var i=0,size=checkKeyList.length; i<size; i++){
        if(confirmTodoObj.todo === todoType[checkKeyList[i]]){
            result();
        };
    };
}

参考

  • 代码大全,第18章

巧用 Array.joinString.split 函数

  • Array.join - 将所有数组或者类数组的元素拼接为一个字符串并返回这个字符串,默认的连接符为 ,
  • String.split - 将一个字符串通过特定的字符串分割为一个数组并返回这个数组,如果不指定分隔符则默认返回一个字符,里面只有一个元素就是当前字符串;

示例

//join的用法

var elements = ['Fire', 'Wind', 'Rain'];

console.log(elements.join()); //expected output: Fire,Wind,Rain
console.log(elements.join('-')); //expected output: Fire-Wind-Rain
//split用法
var elements = 'Jan,Feb,Mar,Apr';

console.log(elements.split());//["Jan,Feb,Mar,Apr"]
console.log(elements.split(','));//["Jan","Feb","Mar","Apr"]
console.log(elements.split(''));//["J", "a", "n", ",", "F", "e", "b", ",", "M", "a", "r", ",", "A", "p", "r"]

案例一

假如后台需要传一个字段,该字段是所有页面上选中的元素的id,通过逗号拼接,如:102,235,356,213

  • 最low的做法
//通过代码将所有元素放到一个数组中
var idAry = [102,235,356,213];

//循环拼接数组
var result = '';
for(var i=0;i<idAry.length;i++){
    result += idAry[i] + ',';
};

//发现最后多了一个逗号,去掉!
result = result.substring(0,result.length-1);
  • 推荐的办法
//通过代码将所有元素放到一个数组中
var idAry = [102,235,356,213];
var result = idAry.join();

案例二

页面需要循环一个数组,动态拼接为html语句 <option> 最后填充到页面上的 <select> 中。

  • 最low的做法
var objAry = [{
    id : 1,
    name : '张三'
},{
    id : 2,
    name : '李四'
}];

var html = '';
for(var i=0;i<objAry.length;i++){
    result += '<option id="'+ objAry[i].id +'">'+ objAry[i].name +'</option>';
};

$('select').html(html);
  • 推荐的办法
var objAry = [{
    id : 1,
    name : '张三'
},{
    id : 2,
    name : '李四'
}];

var html = $.map(objAry,function(d){
    return '<option id="'+ d.id +'">'+ d.name +'</option>';
}).join('');

$('select').html(html);

什么是类数组 Array-Like?

(jQuery)避免手动拼接URL参数

get 请求时,很多人习惯手动拼接url参数,比如:

var queryParam = 'val1='+val1+'&val2='+val2+'&val3='+val3;
var getUrl = url + '?' + queryParam;

推荐使用 jQuery$.param() 方法,或者其它库提供的类似方法。

var queryParam = {
    val1 : val1,
    val2 : val2,
    val3 : val3
};

var getUrl = url + '?' + $.param(queryParam);

详细说明:http://api.jquery.com/jquery.param

(jQuery)对象复制或合并

假如有对象 a 和 对象 b,要实现如下功能:

  1. a 的属性复制到 b 对象上;
  2. ab 合并成新对象 c;

推荐使用 jQuery$.extend() 方法,或者其它库提供的类似方法。

ab 对象分别如下:

var a = {
    apple: 0,
    banana: { 
        weight: 52, 
        price: 100 
    },
    cherry: 97
};

var b = {
    banana: {
        price: 200
    },
    durian: 100
};

目标1:将 a 的属性复制到 b 对象上:

//通过递归的方式,将a对象的所有属性复制到b对象上
$.extend(true, b, a);

//如果不使用递归,而是浅拷贝,则:
$.extend(b, a);

目标2:将 ab 合并成新对象 c;

//通过递归的方式,将a对象的所有属性复制到b对象上
var c = $.extend(true, {}, b, a);

//如果不使用递归,而是浅拷贝,则:
var c = $.extend({}, b, a);

详细说明:http://api.jquery.com/jquery.extend

思考:如果自己实现以上方式,该怎么写?PS:需要考虑深拷贝和浅拷贝的问题;写出来可以与jQuery源码对比下。

三元表达式(Conditional (ternary) Operator)

三元表达式规则:

condition ? expr1 : expr2 

使用三元表达式能简化代码结构,能少写一组 if/else

示例

var str = ture ? 'yes' : 'no';

OR 短路操作符 Short-Circuits

短路操作符规则:

//If exp1 is true then exp2
exp1 || exp2
var a = b || 1;

翻译过来如下:

if(b){
    a = b;
}else{
    a = 1;
};

只所以可以这么写,原因是js是一门弱类型语言,每个变量默认都默认继承一个 true 或者 false 值,称之为:truthy 或者 falsy!
参考:https://www.sitepoint.com/javascript-truthy-falsy/

&& 条件判断

与短路操作类似,还有一种写法如下:

var a = b && 1;

翻译过来如下:

if(b) {
    a = 1;
}else{
    a = b;
};

更多JS中的一些常用的快速写法

  • 获取时间戳
//传统写法
var timestamp = new Date().getTime(); //1512113674165

//快速写法
var timestamp = +new Date(); //1512113674165
  • 浮点数取整
//传统写法
var var1 = parseInt(11.6); //11

//快速写法
var var1 = ~~11.6; //11

//不常用,但是可以
var var1 = 11.6 | 0; //11

位操作符 ~https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT
位操作符 |https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_OR
~~解惑:http://rocha.la/JavaScript-bitwise-operators-in-practice

  • 科学计数法
//传统写法
for(var i=0;i<10000;i++){};

//快速写法
for(var i=0;i<1e4;i++){};
  • 获取随机码
Math.random().toString(16).substring(2); //13位,如:7c4a0278b4509
Math.random().toString(36).substring(2); //11位,如:5b1chmfvhmo
  • 合并两个数组
var a = [1,2,3];
var b = [4,5,6];
Array.prototype.push.apply(a, b);
console.log(a); //[1,2,3,4,5,6]
  • 不增加变量交换两个值
a= [b, b=a][0];
  • 获取数组的最大和最小值
Math.max.apply(Math, [1,2,3]) //3
Math.min.apply(Math, [1,2,3]) //1

推荐书籍

虽然这本书的示例代码都是VB写的,但是**非常实用,建议大家都看下。

敬请期待

下一篇:提高生产效率的编程**之抽象能力(二)

JS谜题-函数中的函数

请问以下结果能否在点击button后,延迟3秒打印结果?

<html>
<body>
<button id="btn">Click</button>
<script src="jquery.js"></script>
<script>
(function(){
    var delayFun = function () {
       alert("This is sample function");
    };

    $("#btn").click(function(){
        setTimeout("delayFun",3000);
    });
})();
</script>
</body>
</html>

答案

不能。

原因

由于delayFun在一个匿名函数中定义,外部无法访问。而setTimeout已经脱离了当前执行环境,它的环境是window,所以3S后从window中找不到delayFun。

延伸

如果这里去掉setTimeout,即:

$("#btn").click(function(){
    delayFun();
});

点击button能立即打印结果吗?

答案:可以。

原因:click回调函数是在最外层的匿名函数中,也就是函数中的函数(即:闭包),闭包有访问外层函数中变量的权利。

经验分享-Array.sort高级用法

最近在使用数组排序的时候,一不小心犯了一个低级错误,分享一下,大家引以为戒。同时,提供一种能应对数组中含有各种特殊符号的排序方式。

问题

在项目中需要将一个数组排序,于是我大致就是这么写的:

//示例,由大到小排序
var ary = [1,3,5,4];
ary.sort(function(a,b){
    return a < b;
});
//结果:[5,4,3,1]

由于忘记自定义sort函数了,简单在控制台Console里跑了一下,这个是OK的,于是代码就这么去写了,没想到就出问题了。。。

问题是什么呢?看下面的例子:

var ary = [5, 8, 7, 1, 2, 3, 4, 6, 9, 10, 11, 12, 13];
ary.sort(function(a,b){
    return a < b;
});
//结果:[4, 13, 6, 7, 12, 11, 8, 10, 9, 5, 3, 2, 1]

结果就开始出问题了!

原因

于是仔细看 Array.sort 的API才发现自定义sort函数的返回值并不是 true or false

  • > 0 when a is considered larger than b and should be sorted after it
  • == 0 when a is considered equal to b and it doesn't matter which comes first
  • < 0 when a is considered smaller than b and should be sorted before it

也就是说返回:正数、负数和0!

解决

var ary = [5, 8, 7, 1, 2, 3, 4, 6, 9, 10, 11, 12, 13];
ary.sort(function(a,b){
    if (a < b) return 1;
    if (a > b) return -1;
    /* else */ return 0;
});
//结果:[4, 13, 6, 7, 12, 11, 8, 10, 9, 5, 3, 2, 1]

上面还有一种简写方法,就是:

ary.sort(function(a,b){
   return b-a;
});

但是这种做法一定要保证数组中全部是 number 类型的,才可以这么简写,要不然最好在function中判断下再处理。

比如:

["5", "8", "7", "string", undefined, "3", {}, "6", null, "10", "11", "12", "13"].sort(function(a, b) {
    return a - b;
});
//结果:[null, "3", "5", "6", "7", "8", "10", "11", "12", "string", "13", {…}, undefined]

深入

其实,一般的排序都是针对 数字 的,也就是可以相加减,那么比较起来就比较直观,但是有时候我们排序的内容有可能不只是纯数字,里面可能有字母,甚至中文,这时候怎么办?

JS有个方法:localeCompare,用法参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare

这个方法就是提供本地化的比较方案,详细的使用说明,大家可以看看上面的文档,这里给出一种数组中含有中文、英文、数字、汉字等字符时的通用比较方案。

先给出一组比较复杂的数组:

let testAry = [100,'汉字','中文','澳门',200,'Admin',30,'Lisa',undefined,null,'','%','&'];

先使用传统的方式:

testAry.sort(function(a,b){
    return a - b;
});
//["", null, 30, "澳门", 100, "Admin", "汉字", "Lisa", "&", "中文", 200, "%", undefined]

当各种情况混合到一起的时候,传统的相减的比较,完全不能达到要求,下来我们看看使用 localeCompare的方案:

testAry.sort(function(a,b){
    var r = a - b;
    if(isNaN(r)){
        r = String(a).localeCompare(String(b), 'zh-CN', {sensitivity: 'accent'});
    };
    return r;
});
//["", "&", "%", null, 30, 100, 200, "澳门", "汉字", "中文", "Admin", "Lisa", undefined]

结果明显要好很多,至少除了特殊字符,中英文和数字都是合理的排序,我们稍微改进下,让其按照如下的规则排序,就完美了:

  • 排序顺序:数字 —> 英文 —> 中文 —> 特殊字符 —> 其它
  • 数字:从小到大
  • 英文:按字母顺序
  • 中文:按拼音的字母顺序

其实,就是将排好序的数组,重新处理下就好了,增加一个方法:

function arySort(ary){
    ary.forEach(function(d){
        if(!d && d !== 0){
            ary.a = ary.a || [];
            ary.a.push(d);
            return true;
        };
        if(/\d+/.test(d)){
            ary.b = ary.b || [];
            ary.b.push(d);
            return true;
        };

        if(/[a-zA-Z]+/.test(d)){
            ary.c = ary.c || [];
            ary.c.push(d);
            return true;
        };

        //注意:汉字的正则不推荐:/[\u4e00-\u9fa5]/
        //参考:https://zhuanlan.zhihu.com/p/33335629
        if(/\p{Unified_Ideograph}/u.test(d)){
            ary.d = ary.d || [];
            ary.d.push(d);
            return true;
        };

        ary.e = ary.e || [];
        ary.e.push(d);
    });
    
    return [].concat(ary.b || [])
        	.concat(ary.c || [])
        	.concat(ary.d || [])
        	.concat(ary.e || [])
        	.concat(ary.a || [])
};

完整的解决方案代码:

let testAry = [100,'汉字','中文','澳门',200,'Admin',30,'Lisa',undefined,null,'','%','&'];

function arySort(ary){
    //重新定义ary,不污染外部数组
    ary = [].concat(ary);
    
    //使用localeCompare排序
    ary.sort(function(a,b){
        var r = a - b;
        if(isNaN(r)){
            r = String(a).localeCompare(String(b), 'zh-CN', {sensitivity: 'accent'});
        };
        return r;
    });
    
    //将排序后的数组重新分类
    ary.forEach(function(d){
        if(!d && d !== 0){
            ary.a = ary.a || [];
            ary.a.push(d);
            return true;
        };
        if(/\d+/.test(d)){
            ary.b = ary.b || [];
            ary.b.push(d);
            return true;
        };

        if(/[a-zA-Z]+/.test(d)){
            ary.c = ary.c || [];
            ary.c.push(d);
            return true;
        };

        if(/\p{Unified_Ideograph}/u.test(d)){
            ary.d = ary.d || [];
            ary.d.push(d);
            return true;
        };

        ary.e = ary.e || [];
        ary.e.push(d);
    });
    
    return [].concat(ary.b || [])
        	.concat(ary.c || [])
        	.concat(ary.d || [])
        	.concat(ary.e || [])
        	.concat(ary.a || [])
};

let result = arySort(testAry);
//[30, 100, 200, "Admin", "Lisa", "澳门", "汉字", "中文", "&", "%", "", null, undefined]

下面再验证几个典型的场景:

  1. 纯数字

    arySort([100,22,0,3,10001])
    //[0, 3, 22, 100, 10001]

    注意:这里容易出现的错误就是 22 > 100
    原因是把这2个按照字符串比较,这样100就排在22前面了。

  2. 纯英文

    arySort(['hello','world','array','alert','boy','girl'])
    //["alert", "array", "boy", "girl", "hello", "world"]

    注意:alert 和 array,首字母相同时,比较第2个字符,依次类推,这就是字典顺序。

  3. 纯中文

    arySort(['你好','我们','**人','比较','腼腆','害羞','奥运'])
    //["奥运", "比较", "害羞", "腼腆", "你好", "我们", "**人"]
  4. 数字和英文

    arySort([100,22,'array','sort',35,'lisa','vivo'])
    //[22, 35, 100, "array", "lisa", "sort", "vivo"]
  5. 中英文和数字

    [22, 35, 100, "array", "lisa", "sort", "vivo", "日本", "**"]

感悟

  • 基础很重要,要时不时翻翻API,看看书;
  • 遇到模糊的概念,最好先翻翻API再下手。

参考

JS最佳实践-代码书写时异常情况先行

JS最佳实践-代码书写时异常情况先行

最佳实践

在书写代码时,优先处理特殊情况或者异常情况,其次再处理正常情况。

好处

这么做的好处就是如果遇到特殊或者异常情况能快速执行完代码,同时书写更清晰。

示例

//原始代码
if(condition){
    //中间有100行代码
}else{
    //这里只有1行代码!
};

以上逻辑最好的书写规范是:

if(!condition){
    //1行代码!
    return;
};

//剩余100行代码

(全文完)

JS解惑-Gulp中Babel用法

JS解惑-Gulp中Babel用法

ES6转ES5的语法,我们使用到了Babel,但是关于Babel的包一大堆,你能分清吗?

基本用法

  • gulp-babel
  • babel-preset-es2015

安装上面的2个基本的Babel组件:

npm install --save-dev gulp-babel babel-preset-es2015

然后在与 package.json 同级的目录中新建文件: .babelrc,内容如下:

{
  "presets": ["es2015"]
}

最后在 gulpfile.js 中,按照如下方法使用即可:

var gulp = require('gulp'),
	babel = require('gulp-babel');

gulp.src('src/**/*.js')
	.pipe(babel())
	.pipe(gulp.dest('build'));

Babel插件

以上基本用法只适用于一些基本的Es6的语法,但是不全,具体有哪些稍后我再研究下放出来,但是至少如:箭头函数之类的是可以转换的。
但是涉及到Map、Set等语法就无法转换了,这时候我们可能还需要一些plugin来进一步转换。

  • babel-plugin-transform-runtime
  • gulp-browserify

继续安装上面的2个Babel插件:

npm install --save-dev babel-plugin-transform-runtime gulp-browserify

然后更新 .babelrc 内容如下:

{
  "presets": ["es2015"],
  "plugins": ["transform-runtime"]
}

最后在 gulpfile.js 中,按照如下方法使用即可:

var gulp = require('gulp'),
	babel = require('gulp-babel'),
	browserify = require('gulp-browserify');

gulp.src('src/**/*.js')
	.pipe(babel())
	.pipe(browserify())
	.pipe(gulp.dest('build'));

这样,大部分的一些ES6语法都能够转换为ES5了。

特别说明:

babel-plugin-transform-runtime:是Babel的插件,使得更多的Es6语法得到转换支持;
gulp-browserify:是将 babel-plugin-transform-runtime 转换后的Nodejs的 CommonJs 语法转换为浏览器支持的语法,大体就是在js最顶部加入了 UMD 的书写方式。

什么是 UMD传送门

LAST EDIT DATE : 2017年9月8日17:52:36

(未完待补充)

CSS解惑-rem和em

css3中新增单位 rem,那么如何区分和何时使用 remem 呢?

一、定义

1、rem

相对于document根目录字体而言的相对单位,默认是相对于浏览器默认字体(16px),即:1rem = 16px。

2、em

相当于使用它的容器的字体而言,如果使用者的字体定义单位也是 em ,那么就有继承关系。

如:

|-- div1(font-size:12px;)
    |-- div2(font-size:2em;padding:3em;)

div2的padding = 3 x 2 x 12px = 72px。

既然都是相对单位,那么在不考虑浏览器兼容的情况下,如何选择使用这2个单位呢?

二、使用

一般有2种情况可以考虑使用 em ,其余全部使用 rem

哪两种呢?

  1. menu中,下一级菜单依赖于上一级菜单的字体大小。比如:二级菜单字体比一级菜单小。
  2. 按钮里面的图标,图标的大小跟文字的大小密切相关。

当然以上两种只是比较典型的代表,只要是有严重依赖相关元素尺寸的,使用 em 比较合理而已。

参考

如果大家看了以上文章,依然迷迷糊糊,说明你对这2个概念的基础只是掌握还不够,那么仔细阅读以下文章。

JS解惑-跨域cookie文件

本文主要说一个容易理解错误的地方,和常见的2种跨域方式。

概念

经常有人会说,如何解决跨域cookie携带的问题,具体指哪里的cookie呢?

我们知道,既然跨域了,那么前台和后台肯定不在同一个域上,也就是出现了两个域。

比如:

  1. 前端的域为:www.page.com
  2. 后台的域为:www.back.com

那么,使用www.page.com调用www.back.com的接口时,是携带哪个的cookie呢?

明确告诉你,是www.back.com。

这个写出来大家可能都知道,但是实际中有时候就搞混了,比如,你通过chrome F12看到当前页面www.page.com下有cookie,但是就是请求后台接口www.back.com的时候没携带上,你就说跨域了,没带cookie有问题。

其实,你应该是去看www.back.com这个域下本身有没有cookie!!!

对于这个问题,我也犯了一次混。

就是在手机H5页面上,H5是通过Webview打开的,H5所在的域下面手机客户端写了一堆cookie。但是与后台通信的时候,命名配置了跨域处理,而且允许携带cookie了,但是就是没带到后台。现在大家知道为什么了吧?

场景

为什么跨域操作要携带参数给后台,有一种场景可能经常用。

比如:前台后完全分离的系统,前台登录后,后台会写入一些登录的凭证到cookie,以后前台再调用后台接口的时候,如果后台接口的cookie的数据能在请求的时候自动携带上,那么后台就可以校验,请求是否合法。

跨域的2种常见处理

  1. 使用CORS跨域处理,参考:阮一峰-跨域资源共享 CORS 详解
  2. jsonp处理(这个不能带cookie,而且其实全部是get请求)

补充

对于无法携带cookie的情况,前台可以用参数的形式将后台需要的数据,再每次调用接口时传递,但是这种情况,安全性就是个考研。

移动端H5解惑-概念术语(一)

最近H5的事情做的多一些,但是有一些移动端的概念还是有些模糊,索性就仔细研究了一下,拿出来共同提高。

物理像素(physical pixel)

也叫做:设备像素(device pixel)。

可以理解为屏幕上的点,这个是跟硬件有关系,跟Web软件语言没一毛钱关系。

你见过霓虹灯吗?LED灯?对!就是那上面一个个会发光的颗粒。

这个概念大家一般喊中文,貌似没见过缩写(当然你可以叫它:pp),因为在软件方面我们很少关注硬件嘛!自然这个概念也就没多大用啦!

分辨率(resolution)

这也是一个物理概念,其实就是指一个设备上横竖的点数。

比如:一个LED灯上面,横着放3个灯泡,竖着放4个灯泡,那么这个LED设备的分辨率就是:3 x 4。

当然对应到PC上的显示器上,或者手机端的屏幕行,这一个个的小点,就不是灯泡啦,而是一种新型的发光原件,而且由于排列密集,所以你肉眼根本看不见具体的一个个的点啦而已。

举个栗子:

iPhone手机的主屏:4.7英寸1334x750,就是指:对角线4.7英寸长,高1334个物理像素数,宽750个物理像素数。

CSS像素(css pixel)

是Web编程的概念,指的是CSS样式代码中使用的逻辑像素(或者叫虚拟像素)。

而我们知道,软件的开发离不开硬件的支持,就比如你要在浏览器看到显示效果,就得浏览器支持你呀!

在CSS规范中,长度单位可以分为绝对单位和相对单位,比如:px 是一个 相对单位 ,相对的是 物理像素(physical pixel),这也就是说到底我们平常开发中的 1px 在每个屏幕上怎么显示,完全取决于这个设备!

所以,问题就来了,到底CSS像素如何在硬件设备的物理像素上显示呢?往下看。

先纠正一个概念(dips)

  • 有些人把 CSS像素 又叫做 设备无关像素(dips)(比如:这里),又说这个概念跟真正的 dip/dp 不是一回事!真实气愤!就是这个概念的含糊不清,搞的我也头晕目眩的。
  • 这里纠正一下,以后大家说到 设备无关像素 就是指 dip/dp ,是我下文专门有一节讲述的概念,它就是以 160ppi 为基准的一个相对单位,用来解决Android上面屏幕不统一问题的。
  • 其余的说法,就是 物理像素CSS像素,分别代表 硬件软件的单位,别再搞错了。

捅破这层窗户纸

在以前的显示设备上,一个物理像素就显示一个CSS像素,这没什么好争议的,大家也理所当然的认为该这么处理,于是大家都在噪点中度过。

然而在2010年,搭载 Retina(高清屏) 的 iPhone4 出现了!也就是从这时候起,手机屏幕的竞赛才真正开始啦,大家都争先空后的朝着更大尺寸、更高分辨率的方向前进。

那么 Retina 到底有什么突破呢?原来,它增加了一倍的手机屏幕的物理像素,用2个物理像素点,显示一个CSS像素。OMG,多么有创意的想法!于是还是原来的样子,还是那时的模样,但是由于屏幕点数增多了,所以看着就更加清晰啦。

没有对比就没有伤害:

看出来为什么上面的明显比底下的清晰,噪点少吗?因为它的网格更密集,其实也就是物理像素更多,但是CSS像素没变(比如:苹果的LOGO大小没变,文字大小没变)。

好了,弄明白了这点,我们再往下看一些其它的概念。

设备像素比(device pixel ratio)

简写:dpr

公式:物理像素数(硬件) / 逻辑像素数(软件),它是设备的属性,而不是一个单位。

其实,比值就是前面说的 物理像素数 除以 CSS像素数。

在javascript中,可以通过 window.devicePixelRatio 获取到当前设备的 dpr

在css中,可以通过 -webkit-device-pixel-ratio-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio 进行媒体查询,对不同dpr的设备,做一些样式适配。

举个栗子:

dpr=2 时,表示:1个CSS像素 = 4个物理像素。因为像素点都是正方形,所以当1个CSS像素需要的物理像素增多2倍时,其实就是长和宽都增加了2倍。

没有对比就没有伤害:

像素密度(pixers per inch)

简写:ppi,当然也叫做:dpi

  • ppi pixers per inch,出现于计算机显示领域(当然也是Android中的习惯叫法)
  • dpi dots per inch,出现于打印或印刷领域(当然也是iOS中的习惯叫法)

其实,从概念中大家也能知道,它表示了一种从逻辑单位到实际单位的换算。这句话怎么理解呢?

因为大家在实际生活中,已经大体知道1米是多长,1斤是多重,而这种单位就是实际单位。

  • 那么我告诉你:我手机屏幕是4.7英寸的,你大概有点感觉是吧?(1英寸=2.54厘米)!
  • 但是如果为告诉你:我手机屏幕是320像素的,你是不是就懵逼了?

所以,有了像素密度这个说法,你用英寸来说明屏幕尺寸是一样的,不信,看看你能理解不?

  • 我的iphone5屏幕上每2.54厘米上有320个小灯泡~(ppi=320)
  • 我的iphone6屏幕上每2.54厘米上就有375个小灯泡呢!(ppi=375)

你是不是明显觉得iphone6更亮!

记住:ppi 说的都是物理像素。

那么,ppi 如何计算呢?因为行业内大家说的手机屏幕都是对角线,比如:4.7英寸,指的是手机屏幕对角线的长度(仅仅是显示的屏幕哦,不包括边框),大家都并没有说手机的宽是多少英寸,高是多少英寸。所以,你计算 ppi 只能用对角线的物理像素数来除以对角线的实际单位啦。

那么计算ppi,首先得计算出对角线的物理像素数,使用勾股定理,即:ppi = 根号下(1920平方+1080平方)/5.2 约等于 294。

那么是不是ppi越大,越清晰呢?理论上是!但是,这得有个取舍。

没有对比就没有伤害:

  • 2英寸1920x1080的主屏,ppi=765(瞎写的)
  • 4英寸1920x1080的主屏,ppi=382(YY的)
  • 4.7英寸1920x1080的主屏,ppi=326(vivo x7)
  • 5.2英寸1920x1080的主屏,ppi=294(HTC One)

来吧,感受下2英寸上,给你放1920x1080个物理像素,是不是一团漆黑了?有人喜欢大屏,也有人喜欢小屏,所以嘛这个值只是个参考,你可以对比相同尺寸下的ppi,但是千万不要对比不同尺寸下的,这样你会受伤的!

设备独立像素(density-independent pixel)

也叫做:密度无关像素。

指Google提出的用来适配Android海量奇怪屏幕的,之前iOS没有设备独立像素一说,因为之前它的CSS像素都是320px,但是随着iPhone6(375px)、iPhone6 Plus(414px)等不同手机型号出现,导致了手机上能看到的CSS像素点也增加的情况下,也得考虑了。

简写:dips or dp

为什么Google提出这个概念能解决不同设备(或不同密度)下的显示问题呢?
原因是:不同的手机屏幕上 CSS像素 个数可能不一样,虽然大多数是320px!

举个栗子:

  • iPhone3GS,物理像素320,dpr=1,所以决定了它的CSS像素320;
  • iPhone4,物理像素640,dpr=2,所以,决定了它的CSS像素还是320;

假如这时候我们有个正方形是 30x30px,这是CSS像素,再上面的2个机器上看到的大小都一样,只是在iphone4上更清晰些,因为它用4个物理像素显示1个CSS像素。换句话说,如果大家都是手机都是320的CSS像素,那么我们就只管用 px 这个单位就可以了,在每个设备上的看到的大小都一样。

如果在iphone5s之前,iOS都不需要关心这个概念,因为你能看到的手机屏幕的CSS像素都是320px。但是,随着iPhone6/plus的出现,就让我们心塞了。

举个栗子:

  • 你有个 160px x 160px 的元素,在iphone5s下面看起来宽度正好是 半个屏幕 大小;
  • 在iphone6plus下面,看起来宽度貌似只占了屏幕宽度的 不到一半屏幕 大小!Why?因为人家像素数量多啊!

明白了吗?就是由于不同的设备屏幕,它所支持的屏幕显示的 CSS像素 个数不同(跟物理像素无关),所以,我们如果用 像素(px) 这个单位去WEB开发的话,在不同的手机下面就显得元素不一样大,这就是 dip 的作用,它的出现也就是为了解决这个问题的。

那么,dip 如何解决这个问题呢?

Google规定:ppi = 160时,1px = 1dip(dip在Android下面是一个单位哦!)
那么,我们就能知道,像素数px = dip * ppi / 160

那么,回到刚才的那个栗子:

  • 80px x 80px 的元素,单位换为 80dip x 80dip ,在iphone5s下宽度是 163px x 163px(由前2行公式得,163px = 80 * 326 / 160);
  • 而这个元素在iphone6plus下,宽度是 207px x 207px(163px = 80 * 414 / 160);

如果我们使用 dip 作单位,那么在2台机器上,显示出来的效果,差不多都等于2台机器宽度的一半!目的就达到了。

另外,dip 作为 单位 仅仅适用于Android,但是**可以用到 iOSWindows 等平台。比如:Windows中修改屏幕分辨率,其实修改的是 dpr ,又由于物理像素不变,其实就等于修改了屏幕中 CSS像素 的个数,这也就是为什么修改了分辨率后,有些东西看着明显变大或者变小的原因了。

其它

设计稿

就是做UI的MM或者GG给你的PS或者切图图片或者标注图片,拿着这个你就可以开始照着勾勒页面啦。当然,小公司可能没有这一步,也许就是产品拿着Axure画的原型直接输出给你,让你照着做页面,那么这个也就算是设计稿啦!相当于要求没辣么高啦。

负责输出设计稿的岗位,叫:UI(User Interface 用户界面)。

有些公司把设计稿也叫做:视觉稿 或者 高保真 图。

交互稿

在大一些的公司,岗位分的可能更细,在开始做页面之前,除了会输出上面提到的 视觉稿 ,还会同时给你输出一份具有动态效果示意的文件,一般可能是 GIF 文件。主要是告诉你,页面上某个元素的出现、消失等动画效果。尤其是做一些活动页面,动画要求多的,可能交互稿就很有必要,不然很多时候,你做出来的效果,未必是产品需要的,那就尴尬了。

负责输出交互稿的岗位,叫:UE(User Experience 用户体验),有的也叫 UX

UE用的比较多的工具是:Adobe After Effects CC 2015

更多

移动端H5解惑-页面适配(二)

参考

  1. 淘宝网触屏版为什么rem跟px混用?有什么好处?为什么不都用rem?
  2. 完全理解px,dpr,dpi,dip
  3. 【原创】移动端高清、多屏适配方案
  4. Android上dip、dp、px、sp等单位说明
  5. Meta viewport
  6. A tale of two viewports — part one
  7. A tale of two viewports — part two
  8. 两个viewport的故事(第一部分)
  9. 两个viewport的故事(第二部分)
  10. Android上常见度量单位【xdpi、hdpi、mdpi、ldpi】解读

jQuery解惑-attr()、prop()、data()

jQuery中提供了3个方法,.attr().prop().data(),本文就介绍下这3者之间的不同。

.attr 和 .prop

关于这组概念在 jquery-1.6.1 的升级日志里说的太清楚不过了,看:这里,这里我大体总结下。

其实有一句话非常关键,大家只要理解了它,就自然明白了:

property 是元素的固有特性,体现了它相关的 attribute(如果存在的话)

比如:对于 checkbox 而言 checked 是其固有特性。怎么理解这句话呢?

看看示例:

<input type="checkbox" id="checkbox">
  • 这里没有checked这个属性(attribute),即:$('#checkbox').attr('checked') 的值是 undefined
  • 但是我可以通过js,获取其是否选中的特性,即:$('#checkbox').prop('checked') 的值是 false
  • 原生js的写法是:document.getElementById('checkbox').checked //结果是:false

看看效果:

那么,为什么在1.6.1开始就区分了 attrprop 这2个概念了呢?

以下阐述可能就是原因:

对于 documentwindow 这2个js对象,他们是没有 attributes 的(可以看到的属性),因为这是2个隐藏于页面内部的2个对象。
但是,window却拥有类似于 loationscreenproperties。明白了吧?

也就是说,直到1.6.1开始,人们才认清了属性和特性之间的区别,应该区分来处理。

看看示例:

什么时候用 prop ,什么时候用 attr

The .prop() method should be used for boolean attributes/properties and for properties which do not exist in html (such as window.location). All other attributes (ones you can see in the html) can and should continue to be manipulated with the .attr() method.

.prop() 是用在具有 Boolean 类型属性/特性上,而且对于特性而言它们并不在html上存在(比如:window.location),其它你能在页面上看到的所有的属性,都可以使用 .attr()

**说明:**在 jquery-1.6.1之后,你也可以使用 .attr() 来获取 document/window的一些特性,但是对于类似于 checked 之类的,如果页面上没有这个属性,你获取到的结果是 undefined,并不能很好的反应其是否被选中!所以,该用 .prop() 的时候还是要用。

.attr 和 .data

jquery-1.4.3 开始支持 .data() 来获取元素上绑定的数据。

为什么jQuery要增加这个方法?因为HTML5新增了一种属性结果:data-*

HTML5增加 data-* 属性的背景

比如,我们在Bootstrap中就可以随处看到这种直接在html上增加数据绑定,比如:popover

<button type="button" class="btn btn-default" data-container="body" data-toggle="popover" data-placement="top" data-content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">
    Popover on top
</button>

那么,HTML5为什么要增加这种属性呢?

Before HTML5, working with arbitrary data sucked. To keep things valid, you had to stuff things into rel or class attributes. Some developers even created their own custom attributes. Boy, was it a mess.

But that all changed with the introduction of HTML5 custom data attributes. Now you can store arbitrary data in an easy, standards-compliant way.

在HTML5之前,开发者为了临时保存一些与标签有关的数据时,往往把数据临时保存到 relclass 这些标签上,甚至有些人就自己造一些标签来保存,简直混乱不堪。

所有HTML5就增加了 data-* 的标签,以专门用来缓存标签数据。

.data() 有哪些特别之处

1、获取数据时不需要加前缀 data-

栗子:

<a id="foo"
    href="#"
    data-str="bar">foo!</a>
$('#foo').data('str');  //"bar"

而通过 .attr() 获取的话,需要携带上。

$('#foo').attr('data-str');  //"bar"

2、获取的数据会被自动转换格式

栗子:

<a id="foo"
    href="#"
    data-str="bar"
    data-bool="true"
    data-num="15"
    data-json='{"fizz":["buzz"]}'>foo!</a>
$('#foo').data('str');  //string "bar"
$('#foo').data('bool'); //boolean true
$('#foo').data('num');  //numuber 15
$('#foo').data('json'); //object  {fizz:['buzz']}

而通过 .attr() 获取的数据,全部是 string 类型。

3、获取数据时可以使用 驼峰式 取值

data-* 的格式规定:data-之后所有单词之间用中划线 -,这样再取值的时候,js可以直接用驼峰取值。

栗子:

	<input type="checkbox" id="checkbox" data-name-width="1080" data-name_height="1920">
	console.log($("#checkbox").attr('data-name-width'));//string "1080"
    console.log($("#checkbox").data('name-width'));//integer 1080
    console.log($("#checkbox").data('nameWidth'));//integer 1080

    console.log($("#checkbox").attr('data-name_height'));//string "1920"
    console.log($("#checkbox").data('name_height'));//integer 1920
    console.log($("#checkbox").data('nameHeight'));//undefined

说明:

  • data-之后当然也可以使用下划线 _,但是就不支持驼峰取值了;
  • 使用驼峰式,还可以这么取值:$("#checkbox").data()['nameHeight'],但是 $("#checkbox").data()['name-height'] 就获取不到值,可能的原因是:js中不推荐使用中划线。

4、与 .prop() 有几分相似

我们通过上文知道 prop 是元素固有特性,体现了 attr (如果存在的话)。意思就是,prop 可以不在 html 标签上出现,但其实它是存在的。

data 也是一样的!如果你体现到标签上,那么就可以通过 .attr() 获取,但是没有体现出来,就获取不到,只能通过 .data() 获取。

栗子:

通过js动态给元素增加data,这时候就只能通过 .data() 获取到,而通过 .attr() 是获取不到的。

//动态增加data
$("#checkbox").data('name-width2',222);

//获取值
$("#checkbox").data('name-width2');//222
$("#checkbox").attr('data-name-width2');//undefined

反过来:

通过 .attr() 修改了标签上data的值,通过 .data() 获取的值其实没变。

//修改值
$("#checkbox").attr('data-name-width',2000);

//获取值
$("#checkbox").attr('data-name-width');//"2000"
$("#checkbox").data('name-width');//1080,注意:这里是没有改变的。

所以,大家在使用 .attr().data() 时,一定不要混合使用,不然可能就会出错,也就是赋值和取值用同一种标签,就肯定没错。

其它说明

其实在HTML5中已经有API提供了类似于jquery的方法,就是 dataset

栗子:

// Here's our button
var button = document.getElementById('your-button-id');

// Get the values
var cmd = button.dataset.cmd;
var id = button.dataset.id;

// Change the values
button.dataset.cmd = yourNewCmd;
button.dataset.id = yourNewId;

(全文完)

参考

  1. .prop() vs .attr()
  2. jQuery 1.6.1 RC 1 Released - What’s Changed
  3. jQuery Data vs Attr?
  4. Working with HTML5 data attributes

跨域解决方案推荐

背景

跨域问题对于前端来说,是一个老生常谈的问题了,无论是平时工作中,以及面试中基本都会问到。本文将梳理常见的几种跨域的优缺点,以及建议的跨域解决方案。

基础

什么是跨域?跨域有哪些解决方案?等等基础知识,请参考这篇文章,讲的比较细致,这里就不在啰嗦。

https://segmentfault.com/a/1190000011145364

对比

我们在工作中,用的比较多的三种解决方案:

方案 优点 缺点 适用场合
JSONP 1、使用简单;
2、不需要任何配置;
1、GET请求;
2、数据不安全,任何人都可以调用接口;
3、存在XSS攻击
1、安全系数要求低;
2、临时使用的接口;
3、应急接口解决方案;
CORS 1、相对安全,但是如果后台配置 Access-Control-Allow-Origin = * 就不那么安全了;
2、后台或者Nginx需要配置,如果需要携带Cookie,则前端Ajax也需要配置参数;
1、需要配置跨域的相关参数,比较麻烦;
2、如果跨域的 Origin 配置为 * , 不安全
1、运行环境可以自主灵活配置;
2、跨域只能代码层面处理理;
PROXY代理 1、非常安全,同域请求;
2、前后端代码不需要任何配置
1、对运行环境要求较高,需要配置代理;
2、开发时也需要配置代理才能连调接口
1、运行环境可以自主灵活配置;
2、安全系数要求比较高;

PROXY 代理,就是通过代理软件或者Nginx将地址进行转发,以达到请求接口的地址在同一个域的目的。

JSONP

最主要的缺点就是 不安全,所以公司里面建议不要使用!

CORS

CORS,在公司里面用的比较多,对于CORS的服务端配置,一般有以下两种方式:

  1. 在后台代码里统一拦截处理reponseHeader,设置CORS协议头;
  2. 在Nginx配置中,统一拦截返回response,设置CORS协议头;

这两种方案,第一种不需要依赖外部环境,纯粹在代码层面就可以解决问题。对于第二种方案,就比较依赖外部环境。那么,一般这两种方案如何选择呢?我个人给如下建议:

  • 推荐 如果代码层面分层合理,建议直接在代码层面设置拦截器,统一拦截处理CORS。这么做的好处就是,一套代码无论以后部署在哪里都可以,不需要对外部环境提太多要求,自身系统健壮性比较强。另外,在代码层面处理CORS还有个好处,就是在开发连调、测试阶段,也依然可以方便进行。不然如果太依赖外部环境的话,开发连调环节,也需要搭建一套环境做为中间转发,比较麻烦。
  • 不推荐 当然,也有人在Nginx中配置CORS,这么做的话,对代码的要求就降低了,不需要考虑跨域问题,但就是多了一个中间环节,无论在开发、测试还是线上,都需要这个中间层,这一点不是很方便。所以,如果是用CORS,不建议使用Nginx配置。

实际项目中,使用CORS典型的场景:H5代码部署在CDN,而后台部署在某后台服务器上。

这种场景下,典型的CDN的环境很难自由配置,而又必须依赖后台接口数据,这时候最好的 办法就是后台代码开发时,就支持跨域。

PROXY代理

PROXY代理 的方法就是通过代理层做一次中间的转发,使得对于浏览器来说,就不存在跨域的问题。

这种方式最大的好处就是绝对安全,不会由于跨域引来一些安全风险,但是缺点就是中间需要做一层代理转发。

实际项目中,使用PROXY代理典型的场景:给客户或者用户使用的后台系统,原因主要在于:

  • 对外的一些后台系统,对于安全性和稳定性要求较高;
  • 同时,管理后台对于Cookie等状态依赖比较强;
  • 后台的一些特殊组件,比如上传组件,有时候对于跨域是比较敏感,所以最好是不跨域,一劳永逸;

如果采用这种方式,那在开发和测试阶段,分别怎么做呢?

本地开发连调

本地 + MockServer

前端代码不需任何配置,只需要MockServer配置 CORS 支持跨域请求就可以。

本地 + 他人机器Server

本地需要安装代理软件,如:Charles

假设,上线后真实的访问路径和接口为:

假设,本地和他人机器Server真实的服务地址为:

这时候需要配置 Charles 路由 Map Remote规则:

Imgur

注意:后台接口的路由规则一定要放在前面,即:/api/* 的路由放在页面。

配置好上面的代理后,地址栏直接访问:http://abcd.vivo.com.cn 即可。

生产环境Nginx配置

我们在本地是通过代理软件转发,而测试或者生产环境,则直接通过Nginx的规则,就可以将上述的路由规则做转发,达到同样的目的。

配置 nginx.conf 文件:

server {
    listen  80;
    server_name abcd.vivo.com.cn;
    
    location / {
        proxy_pass  http://172.25.122.85:3000;
    }
    
    location ~ /api/ {
        proxy_pass  http://172.25.122.86:8080;
    }
}

上述配置可以理解为:

  • 监听80端口,将 http://abcd.vivo.com.cn 的所有请求服务转发到 172.25.122.85:3000
  • http://abcd.vivo.com.cn/api/ 或者 http://abcd.vivo.com.cn/api/list 等请求转发到http://172.25.122.86:8080

总结

本文主要对比了3种常见的解决跨域的方案,同时重点介绍了 CORSPROXY代理的一些优缺点,以及最适合的场景,归纳总结一下,就是:

  • JSONP,能不用就不用,完全不推荐;
  • CORS,最适合用于部署在CDN上面的项目,比如:H5项目;
  • PROXY代理,最合适管理后台,比如:对外的给客户或者用的管理系统;

(全文完)

JS解惑-hoist-函数OR变量提前

JavaScript中,函数OR变量提前(Hoist)是什么意思?它在Js中发挥着什么作用呢?本文结合自身经验来解释下这个概念。

其实要说这个概念,需要用到很多JS的概念,如果本文中未讲解的,建议大家先弄懂这些概念。

一、定义

Hoist,[跟我读:好意思特],升起、吊起的意思。

在JS中的解释是:在作用域(Scope)内变量或者函数会提前到作用域顶部执行。

示例:

// step 1
var myvar = 'my value'; 
alert(myvar); // my value

// step 2
var myvar = 'my value'; 
  
(function() { 
  alert(myvar); // my value 
})();

// step 3
var myvar = 'my value'; 
  
(function() { 
  alert(myvar); // undefined 
  var myvar = 'local value'; 
})();

示例解释:

  • step 1 ,在全局作用域(Global)范围内,定义变量并初始化,再alert,正常显示。
  • step 2 ,在()的包含的匿名函数的作用域内,alert变量myvar,由于当前作用域未定义变量myvar,于是js解析器,会沿着原型链([ScopeType])继续向其指向的父类的原型函数(Prototype)寻找,而父类是window,其中定义的所有变量默认都在其原型函数上,找到了,于是正常显示。
  • step 3 ,这里使用到了JS的另一个概念(变量提前),下文解释这个问题。

扩展阅读:

  1. JavaScript中圆括号() 和 方括号[] 的特殊用法疑问?
  2. JavaScript 其它几个概念:作用域、原型链、原型函数、匿名函数等,自行Google,基础知识,当然我的后续文章也会分析讲解。

二、求解

我们通过 Hoist 的定义可以知道,在作用域范围内,变量和函数提前执行,那么上题可以变形为:

var myvar = 'my value'; 
  
(function() { 
  var myvar;
  alert(myvar); // undefined 
  myvar = 'local value'; 
})();

而我们知道JS在当前作用域内是自上而下顺序执行的。所以,当执行到alert的时候,myvar还未初始化,所以结果就是undefined。

这里其实有2个隐身概念,即:

  1. JS对函数解析执行的顺序;
  2. 变量定义和变量初始化;

本节就说到这里,下节继续烧脑。

三、探秘

首先针对上节遗留2个问题,进行回答。

3.1 JS对函数解析执行的顺序

记忆中应该是这个样子,如果有问题,欢迎指正。

  1. 为该函数构建执行的作用域(又叫上下文);
  2. 为该函数分配this和arguments参数;
  3. 变量和函数提前执行;
  4. 剩余代码分块执行;

3.2 变量定义和变量初始化

先解释下这2个概念的专业术语:

  • 变量定义,Declaration
  • 变量初始化,Initialization

示例:

var myvar = 'my value'; 

解释:

其实以上代码,在JS内部可是执行了2步,即:先定义,再初始化。
以上代码在JS内部执行的时候,是将定义和初始化分开执行:

var myvar;
myvar = 'my value';

四、升华

在上文中有个细节,我一直提起,就是:变量和函数提前。
我们知道函数其实也有两种(不止)定义方式:函数申明和函数表达式。

其中,函数表达式,又分为:匿名函数表达式 和 函数表达式。

顺便补下基础:

//函数声明
function functionName(arg1, arg2){
    //function body
}

//匿名函数表达式(常用的)
var functionName = function(arg1, arg2){
    //function body
};

//函数表达式
var functionName = function functionNameIgnored(arg1, arg2){
    //function body
};

那对于 函数申明函数表达式 是不是都会提前呢?看下例子。

foo(); // TypeError "foo is not a function"
bar(); // valid
baz(); // TypeError "baz is not a function"
spam(); // ReferenceError "spam is not defined"

//匿名函数表达式,foo会被hoisted
var foo = function () {}; 
//函数申明,bar和函数体都会被hoisted
function bar() {}; 
//函数表达式,只有baz会被hoisted,spam就当没看见,因为解析器解析时会忽略这个无用变量
var baz = function spam() {}; 

foo(); // valid
bar(); // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"

补充说明下:

  1. 函数表达式,再未初始化之前,就当成是一个普通的变量定义而已,没有任何状态、类型,就是undefined;
  2. 函数申明,函数定义和函数体都会被提前执行;
  3. 函数表达式中,如果不是匿名函数,那么js解析器也会忽略这个无用函数变量,所以上面的例子中,即时函数都初始化完毕,最后执行spam(),依然会报错,因为这个变量就相当于没有。

五、扩展

上文中提到了作用域(Scope),这里总结一个知识点,就是一个变量进入作用域的4种方式:

  1. JS默认的2个变量(this和arguments),函数初始化就存在,会自动进入作用域;
  2. 作为正常的参数,如function(arg1),这里的arg1在函数执行时就进入了当前函数作用域;
  3. 函数申明,如function myfun(),这里myfun以函数的形式进入了全局作用域;
  4. 变量申明,如var obj,这里obj就进入了当前作用域;

六、习题

好了,所有概念全部梳理完了,以下经常在面试中出现的习题,大家不放围观下。

1、开篇的题目

var myvar = 'my value'; 
  
(function() { 
  alert(myvar);
  var myvar = 'local value'; 
})();

答案:undefined

2、触类旁通

var x = y, y = 'A';
console.log(x + y);

答案:undefinedA

分析:

这个题目,如果大家按照变量hoist的方式拆分下,可能就明白了。

var x;
var y;
x = y;//初始化x的时候,y还是undefined
y = 'A';
console.log(x + y);

3、再补充一个

var x = 0;

function f(){
  var x = y = 1;
}
f();

console.log(x, y);

答案:0, 1

分析:

我们要牢记hoist是在当前作用域内被提前。

var x = 0;

function(){
    var x;//当前作用域内定义x
    x = y;//由于测试y未定义,所以当前作用域内的x=undefined,脱离当前作用域,别的地方获取的还是全局的x
    y = 1;//由于y之前未添加var,其实相当于:window.y = 1,是全局变量,所以下方能访问到
}
f();
console.log(x, y);

七、参考

  1. http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
  2. https://www.nczonline.net/blog/2010/01/26/answering-baranovskiys-javascript-quiz/
  3. https://developer.mozilla.org
  4. http://code.tutsplus.com/tutorials/javascript-hoisting-explained--net-15092

JS解惑-那些经常用但是又叫不起名字的知识

JS解惑-那些经常用但是又叫不起名字的知识

Vanilla JS

世界上最轻量的 JavaScript 框架!

字面量(Literal Syntax)

字面量就是变量创建的一种简写方式。

var test = new String("hello world!");
var test = "hello world!";

var ary = new Array();
var ary = [];

匿名函数(Anonymous Function)

//eg1: 函数定义
var fun1 = function(){};

//eg2: 立即执行函数
(function(){
	//...
})();

三元表达式(Conditional (ternary) Operator)

condition ? expr1 : expr2 
//示例
var str = ture ? 'yes' : 'no';

OR 操作符 Short-Circuits

  • || is the or operator short-circuits。
//If exp1 is true then exp2
exp1 || exp2
//传统写法
var a = "something";
if(condition){
	a = condition;
};

//Short-Circuits
var a = condition || "something";

只所以可以这么写,原因是js是一门弱类型语言,每个变量默认都默认继承一个 true 或者 false 值,称之为:truthy 或者 falsy

立即执行函数(IIFE)

即函数的定义和执行同时进行。

  • IIFE - Immediately Invoked Function Expression

  • 首尾加括号包裹函数体

(function(){
console.log('here');
}());
  • function外面加括号
(function(){
console.log('here');
})();
  • function前面加运算符
!function(){
console.log('here');
}()

或者

void function(){
console.log('here');
}()

尾逗号(Trailing Comma)

{
    "id" : 1,
    "name" : "张三", // 注意这个逗号!
}

UMD

// Uses CommonJS, AMD or browser globals to create a jQuery plugin.

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node/CommonJS
        module.exports = function( root, jQuery ) {
            if ( jQuery === undefined ) {
                // require('jQuery') returns a factory that requires window to
                // build a jQuery instance, we normalize how we use modules
                // that require this pattern but the window provided is a noop
                // if it's defined (how jquery works)
                if ( typeof window !== 'undefined' ) {
                    jQuery = require('jquery');
                }
                else {
                    jQuery = require('jquery')(root);
                }
            }
            factory(jQuery);
            return jQuery;
        };
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function ($) {
    $.fn.jqueryPlugin = function () { return true; };
}));

参考对比:CMD、AMD

相对协议URL(Protocol-Relative URL)

翻译成中文可以称之为:相对协议URL,即:省略路径前面的具体协议名,只保留 //

  • 示例:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>

TypeScript

TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上,TypeScript 编译工具可以运行在任何服务器和任何系统上。

//TypeScript 语法
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}
//JavaScript 语法
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());

(全文完)

HTML解惑-浏览器渲染流程

浏览器如何展示页面的?

一、浏览器工作流程

1、加载

根据请求的URL进行域名解析,向服务器发起请求,接收文件(HTML、JS、CSS、图象等)。

2、解析

对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(比如HTML的DOM树,JS的(对象)属性表,CSS的样式规则等等)

3、渲染

构建渲染树,对各个元素进行位置计算、样式计算等等,然后根据渲染树对页面进行渲染(可以理解为“画”元素)

这几个过程不是完全孤立的,会有交叉,比如HTML加载后就会进行解析,然后拉取HTML中指定的CSS、JS等。

其中,仅针对浏览器拿到资源后的渲染这一部分,可以包含两部分流程。

二、渲染流程

1、排版

根据文档流,加上浮动、定位等属性,确定各个盒子的位置还有尺寸。

2、绘制

将css的属性例如字体、背景色、圆角等等按照给定的方式呈现出来。

注意: 这里涉及到2个性能优化的概念:

  1. Repaint:重绘
  2. Reflow:重新渲染

三、性能优化之Repaint和Reflow

1、Repaint

对页面进行重绘,这时候不会更改页面布局,性能变化没那么明显。

比如:修改 font-colortext-align 等修改

2、Reflow

对页面进行重新渲染,这个是要包含 Repaint。也就是页面布局不仅要重新调整,样式也要重新调整。

对于引发这种重新渲染的情况,主要有:

  • 新增/删除HTML节点、修改容器尺寸等
  • 查询一些尺寸,如:clientHeightclientWidth等,因为计算尺寸的时候要重新渲染一次才能得到结果,这是浏览器的计算原理。

四、参考

JS解惑-shim、polyfill 和 vanilla

本文将帮助你理解这几个概念:shimpolyfillvanilla

shim

是一个小的类库(lib),提供独立的API,以弥补人们在不同的环境下使用原生语言需要考虑兼容性的问题。比如:使用js原生Ajax操作时,你用 XMLHttpRequest 创建xhr对象,但是在IE8上,你就得用 ActiveXObject,为了解决这些兼容问题,同时简化操作,jQuery出现了。jQuery的核心理念就是: write less,do more

所以,jQuery 就可以看成一个 shim

polyfill

是一个小的类库(lib),用于实现浏览器并不支持的原生API的代码。比如:IE9以下不支持html5新增的一些标签,于是 HTML5 shim 出现了;当前ES6,以及ES6+的一些语法并未完全普及时,如果你要使用,那么晚了可以使用 Babel 来编译成 ES5 的语法。

这里注意概念中的一点:原生API的代码,指能称得上polyfill的类库,解决方案一定是在不兼容的环境下,重新定义JS本身的语法,不会重新定义API。

比如:IE9以下不支持 <header> 标签,HTML5 shim 的核心原理就是通过js创建这样一个对象。

//@see [https://github.com/aFarkas/html5shiv/blob/master/src/html5shiv.js](https://github.com/aFarkas/html5shiv/blob/master/src/html5shiv.js)

//line 127
function createElement(nodeName, ownerDocument, data){
    if (!ownerDocument) {
        ownerDocument = document;
    }
    if(supportsUnknownElements){
        return ownerDocument.createElement(nodeName);
    }
	...
}

再比如:如果要让IE9以下支持 XMLHttpRequest,两种方案:

  • shim 的解决方案
function getXMLHttpRequest() {
   var xhr = null;

   if (window.XMLHttpRequest || window.ActiveXObject) {
      if (window.ActiveXObject) {
         try {
            xhr = new ActiveXObject("Msxml2.XMLHTTP");
         } catch(e) {
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
         }
      } else {
         xhr = new XMLHttpRequest();
      }
   } else {
      alert("Your browser doesn't support XMLHTTPRequest...");
      return null;
   }

   return xhr;

}
  • polyfill 的解决方案是:
if(!window.XMLHttpRequest){
	window.XMLHttpRequest = xxx;
};

参考实现:https://github.com/LuvDaSun/xhr-polyfill/blob/master/src/XMLHttpRequestProxy.js

shim 和 polyfill

  • shimpolyfill 的目的都是为了解决浏览器兼容性,并且给大家提供方便的,这也是类库(lib)的一个作用;
  • shim 包含了 polyfill,范围更广,可以重新定义API,而 polyfill 相对狭义一些,API的使用上一定是JS标准;

vanilla

这个概念一般说的少一些,了解下就可以了。

在计算机软件领域中,如果你对原生系统没有做任何定制性的修改,就是Vanilla。

比如,这个网站就跟我们开了个玩笑:http://vanilla-js.com/,下载下来是空的,其实看域名就知道了,作者建议你使用原生API!

参考

  1. https://en.wikipedia.org/wiki/Shim_(computing)
  2. https://en.wikipedia.org/wiki/Polyfill
  3. https://segmentfault.com/a/1190000002593432
  4. https://www.zhihu.com/question/22129715

WEB解惑-require用法

关于 require.js 使用上的一些说明。

常规用法

  • 页面仅保留一个 require.js 的js文件,并添加属性 data-main 关联入口js文件。
<!DOCTYPE html>
<html>
    <head>
        <title>My Sample Project</title>
        <!-- data-main attribute tells require.js to load
             scripts/main.js after require.js loads. -->
        <script data-main="scripts/main" src="scripts/require.js"></script>
    </head>
    <body>
        <h1>My Sample Project</h1>
    </body>
</html>

上面的 data-main 是一个入口js文件指向,这个main文件可以写成 scripts/main.js 也可以省略 .js 后缀。

  • 入口文件 main.js 中一般要包含 require的config设置,以及入口的一个require模块。
requirejs.config({
    //By default load any module IDs from js/lib
    baseUrl: 'js/lib',
    //except, if the module ID starts with "app",
    //load it from the js/app directory. paths
    //config is relative to the baseUrl, and
    //never includes a ".js" extension since
    //the paths config could be for a directory.
    paths: {
        app: '../app',
		jquery : 'jquery.min'
    }
});

// Start the main app logic.
requirejs(['jquery', 'canvas', 'app/sub'],
function   ($,        canvas,   sub) {
    //jQuery, canvas and the app/sub module are all
    //loaded and can be used here now.
});

注意:requirejs.configpaths 中一定不能带有文件后缀名,如 .js ,因为它有可能是一个目录!
如果带了,require请求的文件结果,可能会是这样 http://.../js/jquery.min.js.js

详见 http://requirejs.org/docs/api.html#config-paths

The path that is used for a module name should not include an extension, since the path mapping could be for a directory. The path mapping code will automatically add the .js extension when mapping the module name to a path. If require.toUrl() is used, it will add the appropriate extension, if it is for something like a text template.

多页面

以上常规用法,一般适合 SPA 即单页面应用,由一个主页面作为入口,全程AJAX无刷操作。但是,如果有多个页面跳转,而每个页面都想使用require,并且使用全局一个配置,怎么办呢?

思路:

  • 引入 require.js 的时候不指定 data-main 入口函数;
  • require.config 单独到一个文件中,在页面上加载完 require.js 后,接下来使用 require 命令先加载 require.config
  • 配置加载完成后,再初始化不同页面需要的不同配置入口模块。

示例:

  • config.js 仅仅包含 config 全局配置
requirejs.config({
    baseUrl: 'js',
    paths: {
		jquery : '../lib/jquery.min'
    }
});
  • main1.js 作为页面1的入口函数
requirejs(['jquery'],
function ($) {
    console.log('main1');
});
  • main2.js 作为页面2的入口函数
requirejs(['jquery'],
function ($) {
    console.log('main2');
});
  • page1.html

假设 config.jsmain1.jsmain2.js 都在 js 目录下。

<!DOCTYPE html>
<html>
    <head>
        <title>Page1</title>
    </head>
    <body>
        <h1>Page1</h1>

		<script src="scripts/require.js"></script>   <!-- 这里先加载requirejs的文件 -->
		<script>
			requirejs(['js/config'],function(){      <!-- 再加载requirejs的配置文件,其中:baseUrl:'js' 目录 -->
				requirejs(['main1'],function(){      <!-- 配置文件加载完成后,初始化main1模块 -->
					//TODO  
				})
			})
		</script>
    </body>
</html>
  • page2.html
<!DOCTYPE html>
<html>
    <head>
        <title>Page2</title>
    </head>
    <body>
        <h1>Page2</h1>

		<script src="scripts/require.js"></script>   <!-- 这里先加载requirejs的文件 -->
		<script>
			requirejs(['js/config'],function(){      <!-- 再加载requirejs的配置文件,其中:baseUrl:'js' 目录 -->
				requirejs(['main2'],function(){      <!-- 配置文件加载完成后,初始化main2模块 -->
					//TODO  
				})
			})
		</script>
    </body>
</html>

一个文件中多个define

requirejs其实提倡的做法是一个js文件中一个define定义,也就是一个文件一个模块。

但是,有时候我们又希望多个define声明在一个文件中,主要是用在打包资源的时候,将多个文件合并到一个文件中,这时候自然就是一个文件中多个define了。

比如:

我么知道每个js文件中,我们一般这么写:

//模块代码
define(['jquery'],function($){
	//TODO
});

合并后的文件:

//build.js

//模块1的代码
define([],function(){
	//TODO
});

//模块2的代码
define([],function(){
	//TODO
});

...

如果你单纯这么压缩代码,压缩后使用requirejs肯定无法运行的。

后来 require.js 提供了一个 r.js 的优化打包工具和命令:https://github.com/requirejs/r.js

使用它打包后,直接将 requirejsdata-main 主入口文件指向这个打包好的文件就可以了。

比如:

下面的 build 就是我打包好的js文件。

<!DOCTYPE html>
<html>
    <head>
        <script data-main="scripts/build" src="scripts/require.js"></script>
    </head>
    <body>
        <h1>My Sample Project</h1>
    </body>
</html>

OK,到此为止,一个js中有多个define的问题也就解决了,但是新的问题又来了:

如果是项目级别的话,难不成如果要打包非得将所有模块打包到一个js文件中?

抱着这个疑问,继续窥探require的用法,找到了2个发现:

  1. requirejs 貌似 2.1.x 以上版本提供了 bundles;
  2. 通过 r.js 打包后的文件中,多个define并不是单纯的合并到一起了,而是多了个 define name;
  • 什么是 bundles

比如:

requirejs.config({
    bundles: {
        'build1': [
			'main1',
			'main2'
		],
        'build2': [
			'main3',
			'main4'
		],
    }
});

什么意思呢?

就是 build1 这个js 中,输出 main1main2 这2个模块。前提是这2个模块要都在 build1 中define,不然也不会报错。

那么,现在看看通过 r.js 压缩后的 build1.js 中的内容:

//build.js

//模块1的代码
define('main1',[],function(){
	//TODO
});

//模块2的代码
define('main2',[],function(){
	//TODO
});

再使用的时候,你不必关心 main1 或者 main2 到底定义在哪里,使用就好啦。

比如:

<!DOCTYPE html>
<html>
    <head>
        <title>Page2</title>
    </head>
    <body>
        <h1>Page2</h1>

		<script src="scripts/require.js"></script>
		<script>
			requirejs(['js/build'],function(){
				requirejs(['main1'],function(){
					//TODO  
				})
			})
		</script>
    </body>
</html>

关于 r.js 的使用

按照教程使用没有一点问题,但是结合项目,总是让你凌乱。

问题1

比如: r.js 打包的时候,必须有一个主入口文件,这个文件大致是这样的:

//package.js
require(['jquery','main1'],function(){
	//TODO
});

其中 main1 又依赖领导 main2

//main1.js
require(['main2'],function(){
	//TODO
});

打包以后,你就会发现打包的文件中,会将jquerymain1 以及 main1 依赖的其它的模块 main2 一起打包进去。

这么依赖,如果一个项目很大,有些模块只有再用到才会加载,这样的话,你为了保证打包优化的时候,全部压缩进去,需要在这个入口文件 package.jsrequire 所有的模块,写一个这里加载一个,是不是太麻烦了啊?

问题2

使用 r.js 打包好的文件,总是在生成文件的最后面把这个 package.js 也打进去。。。我去!通过问题1我们知道,我在 package.js 中引入了所有的包,那么你打进去后,我只要使用require调用打包好的文件,岂不是所有模块都要执行一遍?

Shit!

综合,以上问题,我们有木有解决办法呢?肯定的嘛!使用构建工具,比如:gulp

require 在 gulp 中的使用

问题1和问题2的处理

不使用 r.js 打包,而是通过 gulp 插件 gulp-concat 将多个文件压缩到一个文件中。

但是还有一个问题,这样只是单纯的合并,所以,你再自己开发的时候,每个define手动增加一个 defineName,因为 r.js 会自动帮你加,而gulp不会。

比如写main1.js的时候:

define('main1',[],function(){
	//TODO
});

而不是:

define([],function(){
	//TODO
});

这样压缩后,自然结果跟 r.js 的一致,而且不会引入 require 模块初始化。

新的问题

以上解决办法,如果我们自己写的js可以搞定,但是引用第三方 vendor 的js,打包压缩的时候,我们又不能修改源码,怎么办呢?

继续使用 r.js

这时候 问题1 不可避免,你必须在 package.js 中将需要打包的模块 require ,不然会不打进去。

问题2 也就是不希望生成的页面中,包含 package.js的代码。

我是这么处理的:

  • package.js 中代码压缩成一行;
  • 通过 gulp 的插件 gulp-remove-lines 将生成的文件中这一行删除掉。

大致代码如下:

rjs(rjsConfig)
    .pipe(removeLines({'filters': [
        /^require\(\[/
    ]}))

最后一个问题

使用 gulp 我们肯定会使用 gulp-rev 插件为文件增加 MD5 值,这时候,这个就会跟 gulp 中 requirejs的 r.js 插件 gulp-requirejs 冲突。

也就是说,生成的文件无法 MD5

原因是,看这里:

rjs(rjsConfig) //gulp中requirejs插件,使用了自己的流,所以传递到最后。
    .pipe()

举个例子:

不生效的代码:

rjs(rjsConfig)
    .pipe(removeLines({'filters': [
        /^require\(\['/
    ]}))
    .pipe(gulpIf(build,minJs()))
	.pipe(gulpIf(build,rev())) //这里生成MD5
    .pipe(gulp.dest(c.baseDest + '/vframe/'+c.version+'/js'))
	.pipe(gulpIf(build,rev.manifest()))//这里生成MD5文件和源文件的映射
    .pipe(gulpIf(build,gulp.dest(baseDest+'/rev/js.app')));//这里将MD5的映射文件保存

处理后生效的代码:

gulp.src(' ') //注意这里:引用一个空的路径
	.pipe(rjs(rjsConfig))//注意这里:将打包结果作为流传入
    .pipe(removeLines({'filters': [
        /^require\(\['/
    ]}))
    .pipe(gulpIf(build,minJs()))
	.pipe(gulpIf(build,rev())) //这里生成MD5
    .pipe(gulp.dest(c.baseDest + '/vframe/'+c.version+'/js'))
	.pipe(gulpIf(build,rev.manifest()))//这里生成MD5文件和源文件的映射
    .pipe(gulpIf(build,gulp.dest(baseDest+'/rev/js.app')));//这里将MD5的映射文件保存

希望以上总结对大家有用。

CSS解惑-font-face引用字体跨域

我们知道页面直接进入CSS文件是不存在跨域问题的,但是有没有遇到过CSS中依赖的字体会存在跨域问题?

问题

比如,现在矢量小图标非常流行,也有一些开源的矢量图库让你用,比如我们将 font-awesome 这个CSS部署到一台服务器上,在另一台服务器访问。

如:我们在 y.domain.com 地址中请求 x.domain.com 的这个资源。

<link href="//x.domain.com/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet">

你会看到类似的跨域的错误:

原因

font-awesome.css 依赖了字体文件:

@font-face {
  font-family: 'FontAwesome';
  src: url('../fonts/fontawesome-webfont.eot?v=4.6.3');
  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');
  font-weight: normal;
  font-style: normal;
}

原因就在这里了, font-face 本身是有跨域问题的!

也就是说,使用 font-face 所使用的字体一定要与当前页面在同一个域下,要么就看下面的解决办法。

解决

解决方案,就是使用 HTTP access controls 来处理。

具体办法,就是将部署当前CSS的服务器做跨域支持,比如对于Apache的 .htaccess 中增加以下配置:

AddType application/vnd.ms-fontobject .eot
AddType font/truetype .ttf
AddType font/opentype .otf
AddType font/opentype .woff
AddType image/svg+xml .svg .svgz
AddEncoding gzip .svgz
<FilesMatch "\.(ttf|otf|eot|woff|svg)$">
	<IfModule mod_headers.c>
	      Header set Access-Control-Allow-Origin "*"
	</IfModule>
</FilesMatch>

如果你是其它的服务器,那么请自行处理下字体资源的跨域问题就行了。

参考

(全文完)

日常解惑-手表上的日期如何设置

背景

作为男人,可以没有车、没有房,但是怎么能没有自己的一块手表呢?但是当你花个几千甚至上万块买个牌子的表,第一次设置了日期后,第二天一看?时间没变!哇靠,是不是坏啦?莫慌,我来告诉你怎么设置这个日期。

原理

在告诉你怎么设置这个日期前,我告诉你一下手表上的时间和日期的关系后,你肯定就会设置啦!

原理是这样的:时针走2圈,日期加1天,也就是每24小时日期会跳一下。

还有一点要注意:手动调整了日期,是不会影响到时针走两圈,日期跳一下这个逻辑的。

如何设置

知道了上面的原理,现在告诉大家该如何设置啦。

步骤如下:

  1. 先不要手动调整日期,而是先调整时间,将时针调整过12点,看看日期跳了没?如果没有跳,就再转一圈,那么日期肯定会跳一下,因为上面的原理嘛!
  2. 当日期跳了以后,你就知道了,日期将要在时针转2圈以后会自动再跳一次!
  3. 这一步比较关键:这时候如果是上午,也就是12点以前,比如现在是早上9点多,那么你就调整时间到对应的时间即可。如果是下午,比如现在是15点(下午3点),这时候你就需要将时针多转一圈,在第二圈的3点停下即可。为什么要多转一圈呢?因为刚才说了时针走两圈,日期加一天,如果是上午,那么时针到晚上24点,正好可以转两圈;如果是下午,那么你就需要人工走一圈,保证到晚上24点可以转2圈,这样才能保证日期到晚上24点,能自动跳。
  4. 当时间设置好了以后,接下来就是手动设置日期了,今天是几号就设置几号就行了。

后续

以上第3步,如果现在是下午的时候,你没多转一圈时间,而是直接设置了时间。那么,日期将会在第2天中午12点才能自动加一天,因为这时候才转了2圈。

不知道我上面讲的原理,你搞清楚了没?

什么是行业标准?如何获取它?

昨天开小组例会时,我们收集到一个组内同事提的议题:怎么判断一个东西是否达到了行业标准,怎么获取一个东西的行业标准?

初次看到这个话题,我首先想到的是行业标准就是 参考别人怎么做的,至于怎么获取,要么就是自己已经明确知道行业内的老大都有哪些了,要么就是初次进入这个行业,那么就只能多走走市场,多在百度上 Google 一下喽!但是,后来仔细想了下,觉得这个话题并没有这么简单,我的回答也并没有令人满意,疑点颇多。

再加上我们不是修改了我们的中期愿景么?简单点说就是:只做第一!,这个口号的潜台词就是,在这个行业内,我们:只做第一!,不是么?到别的领域这个口号也就没意义了。

人常说 知己知彼,百战不殆!,我们现在处于 什么行业?行业内我们现在排行第几?这个排行如何定义的?,搞不清这些何谈 只做第一?

本文就来谈谈自己的理解和认识。

注:由于本人知识面浅薄,如果您觉得我的言论片面,欢迎批评指正。

什么是行业

行业,指主要根据职业、性质或具体事物,对社会各个领域的称呼。—— (维基百科)

行业的特点

既然我们知道了行业只是一个称呼,那么这里面就会有太多的不确定性,随机性,只是这个称呼在特定时间特定场合下大家一致认可了而已。

比如,我们经常说的:

  1. 传统行业,说到这个词我们从不同的维度,如:衣食住行相关的、吃喝玩乐相关的等,举出一些如:服装行业、餐营业、酒店行业、旅游业、土木水电行业、煤炭行业等等。
  2. IT行业,这个就是相对于 传统行业 最近些年才出现的行业,也慢慢的被大家所认可,有些人一提到这个行业,就会默默的给它打个标签:高薪加班程序猿,甚至 猝死 之类。
  3. 共享单车行业,这是去年才兴起的一个 新型行业 ,我们对它的认识,可能就是停留在一堆共享单车的品牌上,如:摩拜单车ofo小鸣单车等,除此之外对这个行业,还是有点陌生。

看了这些例子,我们大致可以总结行业有以下特点:

  • 时间性,行业是在特定的时间下出现的;
  • 可变性,可能一直存在,可能慢慢消失;
  • 目标性,行业是有明确的目标人群的;
  • 多样性,可以在任何领域、任何时间、对于任何人群产生一种行业;
  • 存在性,行业本身就存在,不是人类创造的,只是之前未被公开认可;(PS:我们不制造水,我们只是大自然的搬运工!)

行业的标准

当我们了解了 行业的特点 之后,再来看看什么是 行业的标准 的时候,觉得这个说法非常难给它下个准确的定义,不过,我们可以给它一个宽泛的定义:

行业的标准指,在一定的时间范围内,针对一些特定的目标人群,他们共同玩的游戏规则。

总结为一句话,行业的标准就是指:游戏规则!

这样说来,大家可能就好理解了,举些例子:

  1. 几年前,你想做一部手机,必定会做 实体按键 到手机上!因为行业内都是这么干的。。。行业的标准是什么呢?就是:诺基亚、摩托罗拉、索尼爱立信等手机大佬都是这么干的。现在呢?你绝对不会这么干了,行业的标准又是什么呢?就是:苹果、三星、vivo都是这么干的!
  2. 我们要做广告业务,广告如何去做?我们初出茅庐,前途一片迷茫呀!不要紧,参考行业标准啊,典型的就是:腾讯、百度、小米等等,人家的广告售卖方式、人家的广告营销模式,要想入行,SO EASY!
  3. 现在我要创办一家 DVVR 公司,我就是对这个感兴趣,而且我有钱,但是就是不知道怎么起步!(⊙o⊙)…,啥是 DVVR?行业内有人做了么?标准是啥?前途一片迷茫!(PS:举个例子而已,你没听过正常,因为 DVVR 是我瞎YY的一个行业,现在还没出现呢!)

看了这些例子,我想分享几点个人看法:

  1. 行业标准不是你想定,想定就能定,而是随着时间的积累,越来越多的人这么玩了,大家才潜移默化的认同了;
  2. 行业标准在开始的时候,一定是培养了用户的,也就是说以后用户对这个标准就没有学习成本了,更容易认同你,靠近你;
  3. 你的游戏规则,只所以能得到大家的认同,尊称为标准,一定是你的用户群体很多呀!
  4. 一个行业刚起步时,是没有标准的,因为用户对这个标准的认可程度还不够高,你可以大胆创新!
  5. 行业标准,一定是大多数认可的,大多数用户接受的游戏规则。PS:来,乐视,在你的生态中,给我造个2个轮子的电动车!

针对以上最后一点,举个例子:

摩拜单车,让大家认识了共享单车这个行业,它的自行车有这些特征:实心轮胎、无链条、不可拆座椅等,但是ofo就是传统自行车!这个行业刚开始的时候,大家都是走从0到1的过程,随着时间的挪动,谁生谁死不一定呢!我干嘛要遵守你?

SO,如果你现在进军共享单车行业,你该怎么做呢?也许你就是下一个 Steve Jobs,让后人记住类似的名言:Stay hungry,stay foolish!

如何获取行业标准

我们先来说一个话题:如何获取知识?

如何获取知识

面试的时候,我们经常会问一些面试者:你都是怎么保持学习能力的?通过哪些渠道学习的?,当然得到的答案基本都是一致的,如:看书、看视频、问人、关注牛人博客、GITHUB等

但是,依据我的期望这些都只能算是最基本答案,也就是说你踏入这一行,不用说都应该这么做!不是么?

那么,我们到底该 如何获取知识?

个人理解,欢迎批评指正:

  • 通过哪些渠道看书?看视频?有什么收获? 也就是说,不要流于形式,别人工作3年是经验,你只是经历过!
  • 如何将别人的东西转换为自己的? 多思考,多总结!
  • 自己有哪些缺点,如何弥补的? 比如:患有懒癌是你的缺点,你是如何治的?

总结一句话:充分认识自己,多总结不要流于形式!

如何获得标准

当一个人知道了如何获取知识的时候,那么获取行业标准,自然就不是问题了。

我的建议是:

  1. 作为用户,舍身处地体验、了解;
  2. 先了解行业大牛,再了解行业标准;
  3. 其他传统方法。

只做第一

最后,希望我们每个人都能在 只做第一! 的中期愿景的指导下,低头做事,抬头看路,恪守本分,做正确的事情,把事情做正确。

2017 vivo加油!

(全文完)

NODEJS解惑-exports/module.exports/export/export default

初学者可能对NodeJs中的Module的导出傻傻分不清楚,我们来进行解惑。

exports/module.exports

这组概念是 CommonJs 的标准。

最终模块生效的是 module.exports ,而 exports 只是 module.exports 的一个默认简写引用而已。

它们的关系是:module.exports = exports => [内存堆栈]。

上面的表达式隐含了以下含义:

  • 默认情况下,module.exports = exports,二者指向同样的内存指向;
  • 如果修改了 exports,那么 module.exports 也将同样得到修改;
  • 但是修改了 module.exports(指向新的内存堆栈),exports 并不会得到修改,还是旧的内容,这时候二者将不相同。

举些栗子:

  1. 可以用 exports 简写,最终的结果就相当于 module.exports 的结果。
//A.js
exports.name = 'zhangsan';
exports.getId = function(){
	return 1;
};

//B.js
let a = require('A.js');
console.log(a.name);//zhangsan
  1. 文件中既有 exports 也有 module.exports,那么最终以 module.exports 结果为主。
//A.js
exports.name = 'zhangsan';
module.exports.getId = function(){
	return 1;
};

//B.js
let a = require('A.js');
console.log(a.name);//error

因为在 A.js 中,module.exports 不等于 exports,二者已指向不同的内容。

export/export default

这组概念是 ES6 的语法。

作用同 CommonJs 的规范一样,都是模块化导出模块的作用。

二者的关系是: export defaultexport 的一种特例。

  1. 相同点:二者都可以导出基本数据类型、对象、类等等。
  2. 不同点:
    • 在一个js文件或者类中,export 可以使用多次(导出多个元素),但是 export default 只能有一个。
    • 是引用这个js对象时,如果没有 export default,则必须这么写: import {xx,yy} from 'zzz.js';如果有,则可以这么写:import zzz from 'zzz.js'

其实,最主要的作用就相当于是给导出的模块有个默认的值一样。

补充一点

其实在Node.js中,这2组概念是可以混合着用的,但是为了保证概念的统一性,建议还是结对使用。

建议:

  • 使用ES6的语法,就用 export + import
  • 使用默认语法,就用 exports + require

(全文完)

CSS解惑-selectors

css中有诸多选择器,运用得当的情况下,能让你少些不少的js,同时从容应对浏览器兼容的烦恼。

手册

大家先看看 阮一峰 老师,在2009年总结的css选择器的文章:css选择器笔记

解惑

一、CSS通用选择器及其优先级

我们知道 基本选择器 包括:通用选择器元素选择器类选择器ID选择器

这些这是概念,告诉你css有这些选择器,但是你知道他们在实际生产中如何使用,且有哪些规则吗?

1.1 基本用法

1、通用选择器

//对所有元素都生效
*{
	padding:0;
	margin:0;
	box-sizing:border-box;
}

2、元素选择器

//仅对p元素生效
p{
	margin: 5px 0;
}

3、类选择器

//仅对某一类元素生效
//这个与【元素选择器】有点类似,只是【类选择器】中的【类class】是自己定义的,而【元素选择器】中的【元素element】是html自带的。
.demo{
	color : blue;
}

4、ID选择器

//仅对某一个元素生效
#demo1{
	color : red;
}

1.2 组合套路

在面试的时候,面试官可能会问你选择器的优先级?为什么选择器还有优先级呢?因为css也是被顺序执行的,同时对某一个元素而言,它身上可能有多个选择器同时作用,那么自然而然就需要有个优先级来处理,这样我们最终看到的效果,就是这些共同作用的结果。

比如:

p{
   color : red;
}

#p1{
   color : blue;
}

.demo #p1{
   color : green;
}

看看以上的例子,你知道最终 #p1 这个元素会显示什么颜色吗?这里就涉及到选择器的组合使用和优先级的问题了。

单优先级

!important > ID选择器 > 类选择器 > 元素选择器 > 通用选择器

组合优先级

  • !important 权重:1000
  • ID选择器 权重:100
  • 类选择器 权重:10
  • 元素选择器 权重:1
  • 通用选择器 权重:0(就是最后被执行的,相当于没权重)

如何通过权重来知道最终生效的样式呢?看看上面的示例。

  • p 元素选择器,结果是:1
  • #p1 ID选择器,结果是:100
  • .demo #p1 类选择器 + ID选择器,结果是:10 + 100 = 110

所以,最终的结果是:green

二、组合选择器及其特殊技巧

2.1 两个易混淆的概念

  • E F,后台选择器
  • E > F,儿子选择器

这2个区别就在于,作用的程度不一样,你说儿子是不是后台呢?当然是,举个例子就明白了。

<ul class='list1'>
	<li>a1</li>
	<li>
		<ul class='list2'>
			<li>b1</li>
		</ul>
	</li>
</ul>

以上代码,如果使用儿子选择器,则list2中的li不会生效。

.list > li{
	color : red;
}

但是,如果使用后台选择器,则代码中所有li生效,因为list2中的li相当于孙子,依然是后代嘛!

.list li{
	color : red;
}

2.2 相邻选择器的妙用

有这样一个需求,下来菜单中,每个菜单之间有一条横线。怎么实现呢?

<ul class='nav'>
	<li>menu1</li>
	<li>menu2</li>
	<li>menu3</li>
</ul>

你可能这么写:

.nav li{
	border-bottom : 1px solid #ccc;
}

这个问题在哪里呢?就是最后一个li底部也有一条线,这个是我们不想看到的。那你可能会用 border-top,那不一样么?第一个顶部也会有一条线。

好了,聪明一点的你,可能会用伪类:

.nav li{
	border-bottom : 1px solid #ccc;
}

.nav li:last-child{
	border-bottom:none;
}

问题在chrome上解决了,但是到IE8以下的浏览器一看,没生效!肿么办?继续优化呗!

.nav li{
	border-bottom : 1px solid #ccc;
}

.nav .last{
	border-bottom:none;
}

然后在最后一个li上增加一个class:

<ul class='nav'>
	<li>menu1</li>
	<li>menu2</li>
	<li class='last'>menu3</li>
</ul>

好的,问题总算解决了,但是如果需求说还要在最后增加一个菜单,于是乎,又得增加一行li,同时把之前的class,挪到最后一个上写。

写了这么多,你知道我想表达什么意思么?这里就是 相邻选择器 的妙用的地方,以下这个写法,相当漂亮:

//与li相邻的li
.nav li+li{
	border-top : 1px solid #ccc;
}

以上代码就是除了第1个li元素外,其余li都顶部有border。因为相当于第1个li,第2个是它相邻的;相对于第2个li,第3个是它相邻的。

当然这种写法的含义就是:除第1个外其余的

JS解惑-如何阻止事件冒泡和取消默认行为

JS解惑-如何阻止事件冒泡和取消默认行为

什么是事件冒泡?

要了解事件冒泡,我们就得把它跟事件捕获来一起交代!

一组栗子

<div id="d1">
    <p id="p1">
        <span id="s1">
            Click Here
        </span>
    </p>
</div>

加入我给如上HTML的三级标签上都加上Click事件:

//注意这里的最后一个参数,默认也是false
document.getElementById('d1').addEventListener('click',function(e){
    console.log('d1');   
},false);

document.getElementById('p1').addEventListener('click',function(e){
    console.log('p1');   
},false);

document.getElementById('s1').addEventListener('click',function(e){
    console.log('s1');   
},false);

当点击页面上的 Click Here 的文字时,控制台会依次打印:

s1
p1
d1

也就是这3个事件的触发是由内层标签开始朝外依次进行的。

如果这时候修改一下事件绑定的方式,如下:

//最后一个参数修改为true
document.getElementById('d1').addEventListener('click',function(e){
    console.log('d1');   
},true);

document.getElementById('p1').addEventListener('click',function(e){
    console.log('p1');   
},true);

document.getElementById('s1').addEventListener('click',function(e){
    console.log('s1');   
},true);

再次点击页面上的 Click Here 的文字时,控制台会依次打印:

d1
p1
s1

原因解释

如何阻止事件冒泡

先说明下,IE旧的浏览器对于事件的定义不同(<=IE8),所以当你使用旧浏览器的时候,务必考虑兼容性!

function stopPropagation(e){
    if(e && e.stopPropagation){//支持W3C的stopPropagation()方法
        e.stopPropagation();
        return;
    };

    if(window.event){//<=IE8,取消事件冒泡
        window.event.cancelBubble = true;
    };
}

什么是默认行为?

看一些栗子:

  • <a> 标签,我们点击以后默认会跳转到 href属性配置的地址去,这就是 <a> 的默认行为;
  • <input type="submit"> 如果放在 <form> 标签中,点击后默认会提交该 <form> 表单,这就是 <input type="submit"> 的默认行为;

如何阻止默认行为?

function stopDefault(e){
    if(e && e.preventDefault){//支持W3C的preventDefault()方法
        e.preventDefault();
        return;
    };

    if(window.event){//<=IE8,阻止默认行为
        window.event.returnValue = true;
    };
}

总结

支持W3C标准

  • 阻止冒泡:e.stopPropagation
  • 阻止默认行为:e.preventDefault

IE8及以下非W3C标准

  • 阻止冒泡:window.event.cancelBubble = true
  • 阻止默认行为:window.event.returnValue = true

补充

使用jQuery的事件方法,直接使用 return false 既可以阻止冒泡也可以阻止默认行为

$(ele).click(function(){
    //...

    //阻止冒泡和默认行为
    return false;
});

(全文完)

JS解惑-setTimeout

关于 setTimeout 的一些理解。

背景

js不像java一样拥有sleep的功能,也就是将当前线程暂停一段时间后执行,因为js是基于事件机制工作的,所以它提供了 setTimeout 定时任务。

也就是说,如果你要实现一个sleep的功能,那也就只能将sleep后的任务,放到 setTimeout 的异步回调函数中执行吧!

本文就简单介绍一下 setTimeout 的原理,以及实际工作中的作用。

基础

JS是单线程

为什么js要搞成单线程?而不像java一样可以并发呢?根源在于js前期是嵌套在浏览器中的,而浏览器是直接跟用户打交道,对于人看到的结果而言,如果一次性并行执行多个任务,人!是会凌乱的。

当然,随着 node.js 的出现,js多线程不是梦,因为它的出现让大家知道:js不止能跑在浏览器上,还可以用在服务器端。所以,只要在服务器端,那么多线程的事情,终究会解决。虽然现在 node.js 全程异步机制已经很高效了!

JS是基于事件驱动的

为什么这么说呢?其实对于js而言,所以的任务,全部都进入队列,只不过它将当前执行的任务,叫做 主任务,而在 主任务 中产生的附加的动作全部进入 任务队列 ,当主线程执行完后,按照 FIFO 的原则,依次执行 任务队列 中的任务。

其实整个事件处理过程 = Main Task + Event Loop[**注意:**loop其实是个while(true),一直无线循环]

setTimeout

setTimeout 的作用,就是改变上面 任务队列 中任务的执行顺序的,上文提到,队列中的任务是 FIFO(先进先出) 的。但是!!!如果你给这个任务增加延迟时间,那么情况就不一样啦。

也就是说上文提到的 Event Loop ,如果其中各任务都是立即执行的,那么按顺序来,如果有延迟,那么你就靠后一点吧。

示例

有了上面解释,大家看看两个例子:

示例1:

setTimeout(function(){
	console.log('等待2秒执行')
},2000);

console.log('立即执行');

结果:

立即执行
等待2秒执行

解释:

在执行这几行代码时,主任务(Main Task)上只有一行语句:

console.log('立即执行');

而开始出现的代码,由于设置了延迟,所以进入任务队列(Event Loop):

setTimeout(function(){
	console.log('等待2秒执行')
},2000);

所以,按照执行顺序 Main Task -> Event Loop,结果也就顺理成章了。

示例2:

setTimeout(function(){
	console.log('等待2秒执行')
},2000);

while(true){
	console.log('立即执行');
};

结果:

console.log('立即执行');
console.log('立即执行');
...

解释:

由于 Main Task 是一个死循环,一直执行不完,所以 Event Loop中的任务,就没有机会被执行了。

setTimeout(fn,0)

为什么要把延迟时间设置为0,单独拿出来说呢?因为这个在实际开发中,很有意义的!

setTimeout的一个作用,就是让其中执行的任务,脱离当前主任务,延后执行,所以 setTimeout(fn,0) 的一个作用,就是改变当前任务的执行顺序。

比如上面的 示例1,就是活生生的例子。

那么,好奇的你又可能要问题了,好生生的我为什么要改变顺序呢?如果要改,我把2个的顺序提前就调转一下就好了嘛,为什么还要 setTimeout 呢,多此一举。

你说的太对了, setTimeout(fn,0) 就是解决这个场景的,即:不能改变事件发生的顺序,而又希望事件按需要的顺序发生。

示例:

我们都知道,js的2个特征:事件捕获事件冒泡

  • 事件捕获,即:事件会先发生在父元素上,最后钻取到子元素上;
  • 事件冒泡,即:事件会先发生在子元素上,最后上升到父元素上;

我们就用 事件捕获 来打个比方:

document.onclick = function(){
	console.log('document click!');
};

document.getElementById('myEle').onclick = function(){
	console.log('element click!');
};

结果:

element click!
document click!

当你点击页面上的 #myEle元素的时候,必然会最后触发 'document click!',因为 事件冒泡

这时候,如果你希望 document.onclick 先执行,因为这里有可能有一些全局的事件过滤机制,就可以这么做:

document.onclick = function(){
	console.log('document click!');
};

document.getElementById('myEle').onclick = function(){
	setTimeout(function(){
		console.log('element click!');
	},0);
};

结果:

document click!
element click!

补充

  1. setTimeout(fn,2000),一定会在2s后执行fn函数吗?肯定不会。
    • setTimeout(fn,2000) 的意思是,主任务执行完2s执行任务,首先主任务执行需要耗时吧?而且是在它执行完之后呢!所以,即便主线程执行完任务队列没有其他任务,那么总时间也超过2s了。
  2. setTimeout 和 setInterval的区别?
    • 一个是定时执行,一个是定时循环执行。

参考

  1. 阮一峰:JavaScript 运行机制详解:再谈Event Loop

js解惑-Array.[method].call有什么作用

js解惑-Array.[method].call有什么作用

背景

最近在项目中,看到有同事写一个数组的方法的时候,习惯性的用以下写法:

* [].forEach.call(array,fn);
* [].map.call(array,fn);
* [].sort.call(array,fn);
...

我的第一个想法是,为啥对于一个数组不直接写成:

* array.forEach(fn)
* array.map(fn)
* array.sort(fn)

询问其原因后,同事的说法是:在有些书上说过,这么写保险,保证数组的方法万无一失可以调用,不报错

观点

同事的出发点没错,就是保证书写代码的健壮性。但我个人的观点是,这种不管三七二十一统一都调用 .call 来保证功能可用的方法,在一定程度上,牺牲了代码的可读性,同时给代码增加了一些冗余。

为什么这么说呢?我们先看看同事的这个 万无一失 的担忧在哪里。

问题

我们知道在js中有一种结构叫:类数组(array-like),这是怎么样的一种结构呢?就是:表现形式上像数组,但是却没有数组的方法。

比如:

  • 在函数体内有个参数 arguments 就是获取所有的参数,这个参数获取的结果像是一个数组,但是却没有数组的方法。

  • 在老的浏览器中,使用方法 document.querySelectorAll 获取到所有Dom节点,如果要遍历,就不能直接使用数组forEach方法(最新的Chrome、Firefox已经不存在这个问题了)。

结论

综上所述,只有我们遇到 类数组 的时候,再调用数组的 .call 将类数组转换为一个真正的数组,然后使用数组提供的方法。如果我们明确知道这就是一个数组,就没必要再 call 转换一次了,实在影响代码可读性。

基本功

  • callapply 都算是继承的一种写法,[].map.call(a1,fn) 就是将数组的map方法继承给a1,并且调用这个map方法;
  • [].map.callArray.prototype.map.call 是相同的,几乎没啥差异,性能上也差不多,见这里的测评;
  • 同上我们常用的 Object.prototype.toString.call 也可以写成 ({}).toString.call,都是可以的,只所以写成 ({}) 是由于不写的话,就成了 }. 而不是对象 {}

JS解惑-ajax文件上传

最近在用ajax进行上传的时候,用到了许多ajax上传的js插件,有一些坑,拿出来跟大家分享下。

常用的2款ajax上传插件

论功能来说,我是觉得 jQuery File Upload 要比 AjaxFileUpload 强大很多,比如:前者支持跨域。

当然,二者在处理ajax上传上面的**都是一样的,就是通过 iframe 的方式提交上传以及获取返回结果等。

基础知识

关于input:file的特殊限制

  1. 处于安全限制,浏览器禁止使用js修改input:file的值。比如你想清空file文本框:$('#file').val(''),是不行滴。
  2. 处于安全限制,对于input:file,clone()后的对象,value是空的。

jquery中的clone()方法

参考:JS解惑-jQuery.clone

遇到的问题

1、第一次上传正常,以后就无法上传

原因是:你将 fileonchange 事件绑定到了 file 本身了。而第一次上传完毕后,当前 file 就被销毁,你实际看到的是 clone() 以后的元素。而 clone() 又没有克隆绑定事件,所以就没效果了。

2、第一次上传正常,以后选择同一张图片就无法上传

原因是:上传以后没有清空前一次 file 选择的文件,所以就没有触发 onchange 事件。

3、校验框架失效,第一次未上传会提示:“请上传文件”,以后就不会提示了

原因是:同第一个问题,都是由于 clone() 以后之前绑定的校验方法丢失了。

当前框架的坑

AjaxFileUpload 框架

options

fileElementId 指定file的ID。

说明:

上传组件直接指定ID,明显不合理!因为看它源码里,使用了 clone(),所以,直接制定ID的话,上文中提到的3个问题,全部会遇到,不合理。

改进:

使用jquery的事件委托机制,通过指定file的父容器,来代理file进行操作,这样即使克隆了,都是同一个父元素,相关的事件依然生效。

比如:

$('#fileParent file').xxx
$('#fileParent').on(':file','xxx',function(){});

这时候的options就可以直接传递一个对象,或者一个file的父容器。

比如,我将 fileElementId 属性直接改为 $fileElementParent,就是一个父容器的jQuery对象。

有需要的可以:下载

jQuery File Upload 说明

总体而言,这个框架比 AjaxFileUpload 强大的很多。

options

  • replaceFileInput 是否替换当前file,默认:true,也就是默认情况下每次使用 clone() 替换旧的file。如果这个值设置为false,那么就会出现上文中 问题2 的情况,建议保持true。
  • formData 上传时的参数,默认取得file所在form所有的参数,可以自己定义仅仅取得自己想要的参数传递,看看API就可以了。

当然这个框架的功能还很多,比如:各种状态下的事件回调,跨域处理,进度条等等,大家去研究下API。

(全文完)

参考

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.