Welcome 🎉🎉🎉
本仓库用于博客记录,博客内容查看:Issues
blog
Home Page: https://github.com/sunmaobin/sunmaobin.github.io
本仓库用于博客记录,博客内容查看:Issues
访达
中的 文稿
对应Windows上的 我的电脑
启动台
对应Windows上的 所有程序
系统偏好设置
是Mac上设置所有选项的地方,相当于Windows上的 控制面板
等
在 文稿
中新建几个根文件夹,然后可以作为Windows上的C盘、D盘之类的,可以右键文件夹,标记为不同的颜色,以后操作的时候,可以通过颜色快速定位到想要的目录,不必要从 文稿
一级一级往下翻
在Mac中,所有APP共享顶部的导航栏,在不同App中,顶部的导航栏显示当前App的导航栏。比如:
在 Windows 上许多组件键 Ctrl + *
在 Mac 上可能都换成了 Command键 + *
比如:
Command键 + C
Command键 + V
系统偏好,打印机与扫描仪,点击 +
就可以扫描到网络打印机,点击确定即可添加。
Windows 和 Mac 上同时安装 Teamview
。
每次开会前,如果要远程主机的电脑,最好做如下的事情:
您的ID
和 密码
安装软件:Microsoft Remote Desktop
在 访达
界面,按快捷键 ⌘ + K
,地址栏输入:vnc://172.25.122.86
即可远程连接另一台Mac机器。
悲催的是 Mac 远程另一台电脑,另一个电脑无法息屏,相当于在同步直播。。。。
打开 访达
,顶部菜单 前往/连接服务器…
或者执行快捷键
⌘ + K
调出目录,输入共享目录地址,如:smb://192.168.2.114
, 连接即可。
⌃ + 空格
F3
平铺所有窗口F4
打开应用启动台触摸板两指按压
相当于右键(常用)触摸板两指上下滑动
滚动页面(常用)触摸板两指左右滑动
如果在浏览器中就是前进、后退(常用)触摸板三指下滑
平铺同类应用,可以快速切换触摸板三指上滑
平铺所有窗口(同F3)触摸板四指回拢
打开应用启动台(同F4)我的底部 Docker 栏
从左到右依次是:
在Mac下切换窗口,通用的命令:⌘+Tab
,但是这个命令有个缺陷,就是只能在不同的App直接切换。比如:Webstorm如果打开3个项目,其实就是同一个App打开了3个进程,这时候就不能切换。
如果要在这3个相同进程之间切换,如果不借助外力,目前我只找到如下两种切换方式:
那么如何能快速切换组内窗口呢? 安装应用:hyperswitch
- https://bahoom.com/hyperswitch
安装之后,可以设置组内应用切换的快捷键、全局应用切换快捷键,比如我个人设置如下:
⌥ + ~
组内应用切换;⌥ + TAB
所有应用切换(包括组内和组外的所有窗口)Mac屏幕的4个角,可以设置快捷方式,就是当鼠标放到4个角的时候触发的操作,个人觉得非常方便,我个人的设置如下:
尤其是右上角的启动屏幕保护程序
,每当我离开位置的时候,我顺手将鼠标挪到右上角,直接就进入屏保界面,同时配合从屏保唤醒桌面需要输入密码,简直比Windows上面的 Windos + L
还要方便。当然Mac上也有锁屏快捷键,即:
⌃ + ⌘ + Q
进入屏保界面(这种方式情况下,屏幕马上就熄灭,进入睡眠状态,但是上面进入屏保界面不会)⇧ + ⌘ + Q
进入关机流程如何进入设置触发角?
系统偏好
-> 桌面与屏幕保护程序
-> 触发角
Mac允许设置多个桌面,每个桌面都可以打开多个APP,这样更有助于你归类开发,默认情况下是有2个:
快速切换多个桌面,可以直接在鼠标上面2个或者3个指头同时左右滑动切换。
新增桌面,只需要打开 调度中心
(快捷键 F3
)窗口,右上角点击 +
即可。
本文不是讲history的replaceState和pushState这2个方法怎么用,而是要说在无法删除浏览器history的情况下,如何能正确回退页面的问题。
有个H5的需求,需要一屏一屏的切换页面,到最后一个结果页面时,如果点击了手机的返回键,或者浏览器自带的返回键,希望能回到主页。但是在中间的切屏过程中,点返回或者前进,就是这几个页面之间切换。
比如,总共有这么几个页面:
如果你在2/4页时,点击返回是要回到1/4页,如果你是在结果页,点击返回,是要回到首页。
典型的spa应用,通过改变 location.hash
并且监听 onhashchange
事件,处理对应的页面即可。
对于浏览器而言,改变了location.hash,其实也是给浏览器的history对象,写入了一条历史记录。
所以,对于我这个应用的处理,其实就生成了6条历史记录,可以通过:history.length查看(假如浏览器地址栏输入地址直接进入的首页)。
那么,对于需求来说,在中间的页面切换都没有问题,但是到了 结果页
时,默认返回是到了 4/4页
页,如果要回到 首页
需要回退好多次。更麻烦的问题是,如果这每一页有参数携带的话,比如我从1/4的参数一直携带到4/4页,最后提交的时候,才取出数据使用,但是如果我直接从3/4页开始下一步,那么前2页的数据就携带不上了,最终结果可能报错。
关于history的几个知识点:
我建立了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);
});
以下总结感觉纯属扯淡,讲的比较啰嗦,估计一般人没耐心看完,不过如果你恰好遇到这类型问题了,估计还是会有所启发吧。
如下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日
比如:
var str=str1=str2=123;
解析过程如下:
str
、str1
、str2
;var str
,表示这是一个局部变量,则直接在当前执行函数中定义变量str,默认值:undefined
;注意: str1
和 str2
,由于他俩前面并没有直接有 var
符号,但是又得为它们赋值,这时候JS引擎赋值时,就会把他们挂到全局对象 window
对象上,因为 window
对象已经存在,所以不会在定义了;而对象的属性,只有在赋值时,才会动态在堆内存中追加。所以,var1
和 var2
也不会创建。
记住:变量提升创建的,只包含:基本类型、对象和声明的函数,不包含对象上的属性。
接下来就是一步步赋值的过程了,见下面第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);//?
大家先自己给出一个答案,然后看看下面的分析跟你想的一样不?
第一步:先将代码中的变量提升,不存在的变量先定义。
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);//?
第二步:一步步执行代码片段并赋值。
a = {n:1}
将a赋值为一个对象 {n:1}
,这里发生了什么事情?
b = a
将a对象赋值给b,我们知道,对象赋值其实就是指针的指向问题,就是b的指针也指向了a的值;
a.x = a = {n:2}
,这一步我们按照上方的原理(并不是依次从右往左执行),把它拆解一下:
a.x = {n:2}
;a = {n:2}
先看前一步,a.x = {n:2}
,其实就是在a的堆内存中,再额外增加一个属性x,并且指向新的对象,如下:
再看后一步,a = {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个地方:
{n:2}
是个新的对象,注意的地方是对象赋值,一定是一个新的地址了。(全文完)
整理一些常见的浏览器兼容插件。
让一个函数页面加载后就运行(专业术语叫:IIFE),你的脑海中能想出几种办法呢?
本文就总结下一些常用的办法,同时一些不常用的,但是在一些牛人写的框架的源码中经常会出现的函数自执行的方法。
function autoFun(){};
autoFun();
function autoFun(){};
window.onload = function(){
autoFun();
};
<script>
function autoFun(){};
</script>
<body onload="autoFun()">
</body>
function autoFun(){};
$(function(){
autoFun();
});
jquery 插件就是这么用的。
(function(){})();
+function autoFun(){}();
bootstrap 组件就是这么用的。
!function autoFun(){}();
以上两种方法,都是在function前面,增加了操作符,于是函数就自动运行了,原理是什么呢?
答案是:通过操作符,将函数申明变为函数表达式,然后再执行。
2017年3月29日21:05:26 修正和补充以下内容。
比如:!function autoFun(){}();
可以这么理解:
之所以这么连起来,最主要的问题是以下这种写法是错误的:
function autoFun(){}();
因为受JS语法解析器的影响,函数的定义和执行不能同时进行的。
但是!如果是一个表达式,那么JS语法解析器会走另外的处理流程,这个流程就是上面提到的2步。
当然,只要是表达式就可以,所以函数的定义不行,但是函数的申明就是可以的:
var autoFun = function(){}();
这么写的结果就是function会执行,但是autoFun=undefined;原因是这时候autoFun的值等于function执行的结果,由于function是个空方法里面没有return值,所以结果就是未定义。
当然,你这么写的目的,可能只是为了快速执行function,所以最后的autoFun的结果你并不关心,那就无所谓了。
具体哪些操作符,可以达到这个目的,参考:IIFE
另外,如果不知道 函数申明
和 函数表达式
的,请自行Google去吧!
什么是语法糖?其实很简单。
什么是语法糖?
语法糖,英文是: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中为了使得程序开发更有效率,更加一致,所以增加了很多语法糖(简单语法)而已。
不要以为这是什么神秘技术,只是一种概念的称呼而已。
本文主要从Git原理、常用命令、分支管理等方面进行简单介绍说明。
注:此图片来自阮一峰博客
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
下载Git客户端并安装:
安装后进行如下操作:
在命令窗口分别执行以下命令:
注意:每个命令,一直回车即可。
git config --global user.name "<your username>"
git config --global user.email "<your email>"
ssh-keygen -t rsa -C "<your email>"
登录git网页版
id_rsa.pub
中的内容,到上面的窗口,并保存至此,git的环境已经配置好,现在查看自己有哪些git仓库目录。
在git的首页,能看到右侧有一些Project,这是你有权访问的一些项目。
比如:点击Administrator / gdd-doc
进入这项目后,点击SSH,并复制URL[email protected]:root/gdd-doc.git
在刚才的Git Bash 窗口,执行如下命令并回车,开始下载gdd-doc
的文件
文件下载完成后,就可以看到所有的项目资料了。
到此为止,git的安装和初次使用就结束了,还要进行其他的学习和操作,可以参考本文档其他章节,后者以下文档:
git clone url
克隆一个仓库git fetch origin
更新本地仓库文件跟远端一致git branch
查看本地有哪些分支git branch -r
查看远端有哪些分支git branch -a
查看本地和远端所有分支git branch -D feature1
删除本地分支feature1
,远端仓库一般不建议删除(管理员除外)git checkout develop
下载并切花到develop分支(如果develop分支本地已有,则直接切换过去)git checkout -b feature1
以当前分支为准,创建一个新的分支,名字叫feature1
,并切换到feature1
分支git pull origin develop
从远端develop分支更新内容到工作区git status
查看改动的文件(或者打开GUI查看)git add <file>
提交文件git add -A
提交改动的所有文件git commit -m 'comment'
添加评论提交代码到本地仓库git push origin develop
提交代码到develop分支(**注意:**前提示当前分支也是develop分支,保持一致。)git tag
查看所有taggit tag tag1
将当前分支的当前代码打一个标签,名称叫tag1
git push origin tag1
将打好的标签tag1
提交到远端git checkout tag1
临时切换到啊tag1
标签上git checkout -b feature2 tag1
从tag1
标签上创建一个分支并feature1
并切换到这个分支上git merge --no-ff feature1
当前分支合并feature1
分支,注意: --no-ff
要加。git log
查看提交日志,按:q
退出git reset --hard 'hash'
硬性回退到之前的一个版本。hash
是每个版本有一个hash码。稍后补充
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中查询abcgrep -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
查看详细性能%s/find/replace/g
替换文件中所有的find字符串为replacesplit -l 50
拆分原始文件screen 回车
进入一个后台界面screen -S name
创建一个名为name的screenscreen -r night
进入name为screen的窗口ctrl+A D
退出当前screenscreen -r
查看所有screenscreen -r pid
进入某一个screenctrl+Z
结束当前screenscreen -wipe
检查目前所有的screen作业,并删除已经无法使用的screen作业ctrl+s
锁屏ctrl+q
解锁unzip xxx.zip -d /home
解压到指定目录homegzip -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.gzps -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/
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(…)
$.clone()
可以传递2个参数:
这里就不区分jQuery的版本了,假设我们都 > 1.5。如果还有低版本的,可以看看 官网 的兼容性说明。
$.clone()
:仅仅克隆当前元素的html结构以及当前元素及其子元素html上绑定的事件和数据(下文解释);
数据
是指通过 data-*
属性赋值的属性。$.clone(true,false)
:克隆当前元素html结构、当前元素及其子元素html上绑定的事件和数据,但是不包括子元素上动态绑定的事件和数据;$.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"));
结果说明:
click
);oninput
)data
)click
);oninput
)data
)click
);oninput
)data
)clone()
方法其实包含了3部分内容:
+
动态绑定到html上的事件和数据(直接绑定,live/delegate/on等委托绑定除外)+
子元素及其事件和数据而以上3部分内容,分别对应以下用法:
处于性能的考虑 textarea
和 select
这2个标签动态增加的属性有点例外。
textara
克隆后,值不会带过去;select
选中的 option
克隆后,依然是未选择状态;如果你把一个含有ID的元素进行克隆,那么克隆以后就会出现ID重复!
为了避免这问题,建议你这么做:
class
而非 id
;id
;(全文完)
针对HTML中href的一些特殊用法的解惑。
<a href="http://www.night123.com">跳转到Night博客</a>
指定一个 hash
值,跳转到当前文档 id=hash
所在的位置。
比如:在 <body>
有如下标签。
<!-- 案例展示 -->
<div id="cases">
...
</div>
在 页面中间一个超链接,点击后定位到 cases
所在位置,则可以增加一个 a
标签操作。
<p>
<a href = "#header">查看案例</a>
</p>
<a href="mailto:[email protected]?Subject=hello%20night">发送邮件</a>
<a href="javascript:alert('Hello World!');">运行JS</a>
我们通过以上章节,知道了 a
标签的功能,但是在实际工作中,还有一些特殊用法,接下来我将一一为你解惑。
这个本来正常的做法,是 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个作用在解决相关问题,都不是很完美,但是还是有一些人一直在用。
补充说明,有人说
href
是a
标签必不可少的一个属性,其实不然。
延伸阅读请参考:http://w3c.github.io/html/links.html#attr-hyperlink-href
这种用法,现在也很常见,目的也是解决避免默认的 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>
个人觉得方法一是最完美的处理方式。
这是禁止页面跳转的一种方式。
看了这篇文章就知道了:
谈谈Javascript中的void操作符
此种写法在 SPA
应用中最为常见,原理就是通过监听js的 onStateChange
事件,来控制路由规则,加载不同的HTML模板,实现页面切换。
但是浏览器地址中的的 hash
不是 #
后面的参数吗?为什么要增加一个感叹号,不能直接这么写 #home
吗?
看看阮一峰大神的这篇文章,就明白了:
URL的井号
JS中经常能看到 (function(){...})()
,那么双括号()到底是什么意思呢?
我们先来看看一些实际工作中用到的()的例子。
形式是这样的:
(function(){
//TODO
})();
当然在jquery中,有时候为了避免变量$冲突,也经常这么用:
jQuery.noConflict();
(function($){
//TODO
})(jQuery);
引自 Ecma-262
第 12.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个作用:
关于立即执行函数,参考我之前写过的一片文章:
另外,我们知道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)本身就返回了一个引用对象,不能在()里面直接就运行哦,那你要两个()就没用了。
我还是分析下吧:
引申:
JavaScript 高级程序设计(第三版)
的 7.2.2 关于this对象
中有个例子,看了以上概念,大家肯定更好理解了。
例子在这本书的 183页
最终结果:My Object,大家自己去看。
(expression)
的理解:
(expression)()
的作用:
有没有发现,行内元素之间有一些空隙?
先举个栗子
<div>
<a href="#">1</a>
<a href="#">2</a>
<a href="#">3</a>
</div>
展示出来的结果,你发现1、2、3之间并未严格贴在一起,而是之间有一些间隙?
这就是行内元素之间默认的间隙!
本文简单的讲下出现这种问题的常见的场景!
如:a
、span
、img
等
inline
或者 inline-block
的div{
display : inline;/* inline-block */
}
参考:https://css-tricks.com/fighting-the-space-between-inline-block-elements/
你知道 +new Date()
结果等于多少吗?
昨天看到一种书写方式,当时就有点懵,写法如下:
var time = +new Date();
好奇之,就Google了一下,豁然开朗,于是分享之……
以上代码,等于如下代码:
function(){
return Number(new Date);
}
所以,结果其实就是 当前时间的时间戳
,也等于如下结果:
new Date().getTime();//20170329205038
+
可以与以下类型进行运算:
结果如下:
+undefined = NaN(Not As Number的意思)
扩展: undefined 和其它任何值进行任何运算结果都是NaN,比如:1+undefined,6*undefined等
扩展: 1+null = 1
扩展: +[2,3][1] = 3(相当等于 +3 = 3)
其实,+String我们经常见,就是把String转换为数子然后操作,如果无法转换为数字,那么结果就是NaN啦!
(全文完)
大家有没有发现,在一些免费的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
加载。
为什么要这么做呢?不是js和css可以随便引入吗?为啥还要分协议呢?我统一使用 http
开头来加载资源有啥问题呢?
如果你的站点就是 http
的,那么没一点问题。
如果你的站点是 https
的,那么默认是不建议引入 http
的资源的,资源包括:js、css、图片等。在Chrome、Firefox等浏览器下,控制台会打印警告!而在一些版本的IE浏览器下,会弹出如下警告:
如果你引入的 http
资源中又引入了别的资源,会直接被浏览器决绝掉,提示:跨域请求失败的错误信息。
知道了为什么会出现这个问题,那么我们经常引入一些外部资源的时候,就会有以下常见的办法。
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
也已经很稳定、高效,并且最关键的一点,在 http
网站中引入 https
是可以的!
所以,如果你现在引入资源的话,建议直接引入 https
的资源(如果支持),而且 https
网站也是趋势!
反过来看待上面的 //
的这种做法,许多人这么评价: protocol-relative URLs are an anti-pattern
,翻译过来就是:相对协议URL是一种反模式
。
意思就是说:其实 HTTPS
才是大势所趋,我们应该鼓励直接用 HTTPS
,而不是妥协它做一些兼容性的解决方案。
比如这篇文章的观点:Moving CDNs to HTTPS
(全文完)
总结下我最近对于知识的认知,希望能与君共勉。
JS基础知识温习。
分3个阶段:
本阶段就是书本中所说的Hoisting,包括:形参变量创建
、函数体内变量提升创建
和 函数申明创建
。
就是先把函数中所有的变量或者声明的函数名都先定义好,空间都开辟好。
关于准备阶段的特别说明:
这里装载填充的数据包括:形参
和 申明的函数体
。
也许你要问了,为什么一般的变量只是拿到前面定义好,此时值是 undefined
,填充数据需要等到执行那一行才进行,而 形参
和 申明的函数
在代码执行前就要装载好呢?
答: 我个人的理解是(有更专业的解释,欢迎批评指正):
形参
是外部传进来的,是函数在执行前就已经知道的数据,所以直接就装载上;而对于函数中普通的变量,受限于JS解析顺序的机制影响,只能等到具体执行到那一行时才能知道。函数申明
为什么要放到前面去呢?这应该也是JS的策略吧,不然函数表达式(var xxx=fn)为啥就没这个待遇呢?关于装载的特别说明:
函数形参
> 函数申明
;通过上面的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);
undefined
;1、a= undefined
;window
所对应的对象下面创建属性b,并且为其赋值为123,window.b=123;2、a= 123
;3、b= 123
;//7 console.log('4、b=',b);
//8 console.log('5、a=',a);
4、b= 123
;补充说明: 错误类型为 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);
};
装载完毕后,代码变为:
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);
找到所有的局部变量,注意包含形参中的,包括:
于是原函数就变为:
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);
这类题目,先不用管函数体外的代码,因为函数准备和装载,跟外部代码怎么执行没关系。
对于变量前没有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);
通过以上讲解,中间穿插了一些基础知识,这里跟大家简要总结分享下。
函数的定义有三种形式:
函数申明
function fun1(){};
函数表达式
var fun2 = function(){};
构造函数Function
var fun3 = new Function("a", "b", "return a * b");
就是将函数体内的 局部变量
和 函数申明
放到函数的最前面定义。
请移步: JS解惑-连等表达式
function fun4(var1,var2){//函数结构体括号内的变量,就叫做形参。
//TODO
};
fun4('abc',123);//调用函数时,实际传的值就叫做实参。
准备
阶段时,用来存放准备的数据的,这些数据包括3类:形参、变量和函数声明。
window
对象。(全文完)
在了解如何做H5页面适配前,大家都应该把移动端涉及的一些概念搞明白,比如:dpr
是什么意思?
因为在PC端,由于浏览器种类太多啦,比如几个常用的:IE、火狐、Chrome、Safari等。同时,由于历史原因,不同浏览器在不同时期针对当时的WEB标准有一些不一样的处理(虽然大部分一样),比如:IE6、IE8、IE10+等对于一些JS事件处理、CSS样式渲染有所不同。
而恰恰又有一些人在使用着不同类型的浏览器,以及不同浏览器的不同版本。所以,为了能让你的网站让这些不同的人看到效果一致,你就不得不做兼容,除非这些人不是你的目标用户。
在移动端虽然整体来说大部分浏览器内核都是webkit,而且大部分都支持CSS3的所有语法。但是,由于手机屏幕尺寸不一样,分辨率不一样,或者你需要考虑横竖屏的问题,这时候你也就不得不解决在不同手机上,不同情况下的展示效果了。
另外一点,UI一般输出的视觉稿只有一份,比如淘宝就会输出:750px
宽度的(高度是动态的一般不考虑)(详情),这时候开发人员就不得不针对这一份设计稿,让其在不同屏幕宽度下显示 一致
。
一致
是什么意思?就是下面提到的几个主要解决的问题。
举个栗子:
在 1080px
的视觉稿中,左上角有个logo,宽度是 180px
(高度问题同理可得)。
那么logo在不同的手机屏幕上等比例显示应该多大尺寸呢?
其实按照比例换算,我们大致可以得到如下的结果:
375px
的手机上,应该显示多大呢?结果是:375px * 180 / 1080 = 62.5px
360px
的手机上,应该显示多大呢?结果是:360px * 180 / 1080 = 60px
320px
的手机上,应该显示多大呢?结果是:320px * 180 / 1080 = 53.3333px
以下就是一些实现思路:
@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个比较突出的问题:
@media
查询块;@media
中定义一遍不同的尺寸,这个代价有点高。rem
单位注意我们的推导公式:
375px
的手机上,应该显示多大呢?结果是:375px * 180 / 1080 = 62.5px
360px
的手机上,应该显示多大呢?结果是:360px * 180 / 1080 = 60px
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 就可以大致解决问题了。
但是本方案仍然有以下问题:
@media
查询语句,多一种机型就需要多写一套查询语句,而且随着现在手机的层出不穷,这个页面很有可能在一些新出的机型上有问题。针对除以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中就只需要按设计稿的尺寸大小写就可以了。到底可不可以呢?
答案是:不可以。
主要原因是,浏览器有最小字体限制:
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);
}
以上方案虽然解决了问题,但任然有以下缺陷:
@media
calc()
,这个也挺麻烦的。由于方案2最主要的问题就是需要针对不同的屏幕尺寸,定义多个 @media
,所以我们先将这个字体设置改为动态设置。
注意我们的推导公式:
375px
的手机上,应该显示多大呢?结果是:375px * 180 / 1080 = 62.5px
360px
的手机上,应该显示多大呢?结果是:360px * 180 / 1080 = 60px
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);
}
如果不考虑别的因素,只是页面元素大体适配的话,该方案基本就满足要求了,但是现实中我们其实还有很多问题,所以我们的方案还需要继续优化。
文字也采用rem的单位主要有什么问题呢?
23.335px
这样的奇葩的字体大小,可能还会因此出现锯齿、模糊不清等问题;3.5 横竖屏显示问题
会仔细讲)。对于以上问题,我个人的建议:
如果一定要解决这个问题,那么字体就不要使用rem方案了,而是继续使用px单位。
我们上面提到 大屏
小屏
其实隐含的意思并不是手机屏幕大,而是手机的屏幕分辨率不一样,其实就是dpr不一样,所以我们针对不同的dpr设置具体的字体就可以了。
比如,我们针对页面的标题的字体大小就可以如下设置:
.title {
font-size: 12px;
}
[data-dpr="2"] .title {
font-size: 24px;
}
[data-dpr="3"] .title {
font-size: 36px;
}
先来看看 这里 这篇文章,有讲解了为什么在有些屏幕上要使用 @2x
@3x
的高清图。
再来看看 这里 讲解了具体的高清图的解决方案。
我这里简单归纳下。
<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对象
};
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屏幕] */
);
}
什么是 1像素问题 ?
我们说的1像素,就是指1 CSS像素。
比如设计师实际了一条线,但是在有些手机上看着明显很粗,为什么?
因为这个1px,在有些设备上(比如:dpr=3),就是用了横竖都是3的物理像素矩阵(即:3x3=9 CSS像素)来显示这1px,导致在这些设备上,这条线看上去非常粗!
其实在在中手机上应该是1/3px显示这条线。
关于 dpr
,不理解的,可以看看之前的 这篇 文章。
问题描述清楚了,我们该怎么处理呢?
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);
}
}
但是,这种方案只能解决直线的问题,涉及到圆角之类的,就无能为力!
我们先来讲讲页面缩放能解决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;
}
横竖屏问题,就是当你横屏手机、竖屏手机时看到的不一样的效果问题。
我这里要说的这个问题,就是设计师会针对横屏或者竖屏,做不一样的设计,比如:横屏时显示摘要,竖屏时只有标题,这种情况下,我们应该怎么适配的问题。
关于横竖屏问题,我将会分2个部分来说明:
我们知道横屏,相当于屏幕变宽了,这时候一行显示的内容就可以更多。所以,设计师可能会对横竖屏做2种不同的内容设计,如:
如果设计师本身就设计了2套样式,那么我们只需要准备2套css,依据横竖屏直接显示对应的样式,然后html中做好兼容即可。
下文会将如何判断横竖屏。
这里有个要说的就是,设计师没有设计横屏的样式,那么如果我们按照上文提到的方案去处理,那么就会发现在横屏模式下字体显得非常大(因为屏幕宽了),显示的内容就少了。
这种情况会显得很不协调,虽然还是等比例显示的,只不过需要多拖动下滚动条而已,但是你会觉得很怪。尤其是再有弹出框的时候,会更麻烦,弹出框有时候可能还会显示不完全。。。
比如,下面的样式:
像这种问题,我们上面提到的依据屏幕宽度自动调整根目录font-size的大小,就有点不合适了。这样虽然保证了横向的比例是严格按照设计搞来的,但是显示上非常丑。
所以,我们一般的做法就是在横屏的时候将 deviceWidth=deviceHeight
。
以上3组画面对比我们得到的效果是:
所以,经过我们的实际对比体验以后,一致认为横屏时让 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';
js获取屏幕旋转方向:window.orientation
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);
@media screen and (orientation: portrait) {
/*竖屏 css*/
}
@media screen and (orientation: landscape) {
/*横屏 css*/
}
<!-- 竖屏 -->
<link rel="stylesheet" media="all and (orientation:portrait)" href="portrait.css">
<!-- 竖屏 -->
<link rel="stylesheet" media="all and (orientation:landscape)" href="landscape.css">
手机字体缩放是什么问题呢?
就是当你在手机 设置 -> 字体设置
中将字体放大或者缩小,使得手机整体系统字体发生了变化,这时候可能就会影响到H5页面正常的显示。
经过实际测试,这个问题当前发生的概率不是很大,因为很多手机厂商都已经做了保护措施。但是为了保险起见,我们还是有必要进行检测,一旦不一样就要采取措施。
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 设置的字体和实际显示的字体不同
};
比如:你想设置的字体大小为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';
};
这么做理论上已经解决了问题,但是还有点瑕疵,问题就是:
如何解决这个问题?思路就是:
最终代码片段如下:
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';
除了上面一步步的分析中间的原理之外,我们可能还需要考虑或者遇到以下问题:
document.documentElement.clientWidth=0
的bug?所以,在尽可能的解决诸多问题后,最终的脚本如下:
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;
}
如果不太考虑老的手机型号,可以采用 viewport
单位。
由于我本人也没有在项目中使用这个方案,所以不过多发表言论,大家有兴趣的可以研究下。
具体方案细节参考:
讲了这么多,这里总结下,任何事情弄懂原理最重要!
比如,当你首次看 使用Flexible实现手淘H5页面的终端适配 这篇文章的时候你会很感慨,感觉很有收获,但是当你实际开始项目的时候,却不知道该怎么下手。
俗话说,台上一分钟,台下十年功。
为了写本文以及姊妹篇,我个人零零散散的时间加起来不下1个月,一直到(2016年12月2日
)才发表了本文的第一版。由于收到一些流言反馈和后续知识的积累,于是决定今天(2018年08月01日
)再把它重新整理一遍,以让大家更清楚这中间的原理。
因为中间涉及的东西太多,只要有一个知识有些不清楚,可能就会卡克!比如这个概念 dips
,不同的文章有不同的说法,而且还给你解释了它跟 dip
的不同,其实就是指 CSS像素
,这些人故意发明一些专业词汇,搞的你晕头转向,所以,当你看了我的这两篇文章,也许还是一知半解,这很正常,慢慢来,多多练习,相信你会明白的。
如果还有哪里不清楚,或者本文有错误的地方,感谢批评指正。
本文重新编辑于:2018年08月01日
- 针对大家的留言以及个人的反复推敲,重新整理了这遍文章;
- 增加针对手机本身字体放大、缩小的解决方案,以及一些新的替代方案思路。
(全文完)
undefined
和null
本质上没什么区别,只是用法上代表的含义不一样罢了。
为什么说,undefined
和null
本质上没什么区别?
通过这个来看看:
undefined == null //true
null == undefined //true
另外,再阅读本文之前,建议大家先看看阮一峰老师的博客,以便了解下这二者的历史。
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
作甚?”
好吧,你是一个充满好奇心的人,且听洒家慢慢道来。
在Js中,你可以这么归类:“除了基本数据类型外,其余变量全部是对象”。
关于这句话解释下:
如果按照这么分类,那么 null
的一个作用就凸显了。
给一个变量赋值,基本数据类型可以直接赋值,那么我要赋值一个对象,或者清空一个对象的赋值,该怎么办呢?
那就发面一个空的对象吧,即:null
。
当然变量被赋值为 null
后,还有另一层作用,那就是方便JS引擎对这个变量进行垃圾回收。
在JS中,凡是不用的变量,JS会定期清理掉。当我们把一个对象设置为 null
时,其实也就等于把这个对象标记为不用了,可以垃圾回收了。
最近项目中有个需求,就是查询条件可以动态追加,也就是有个按钮,点击后增加查询条件,比如:查询条件中有个select,点击添加,就多一个select。
于是出现的问题是,添加一个select,option的默认值就是上一个select中选中的值,而不是默认选择第一个!
实现思路大致为:
大致实现思路是:
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
对象的一级节点的引用!ary
对象。ary
模板的时候,其实已经被更改了。所以,这个问题最根本的解决办法就是使用深拷贝,即:
var vm1 = $.extend(true,{},ary);
Object.assign
也是浅拷贝,请注意使用。
Object.assign
的深拷贝,可以自己写方法
jQuery.extend
的源代码,jquery源码中搜索:jQuery.extend
web前端目录中的 lib
和 vendor
到底有啥区别?
先来看看定义:
从这个层面上来看,vendor应该是lib的一个子集。
于是,第一种目录关系出现了,即:
|- lib
|- plugin
|- util
|- ...
|- vendor
|- jquery/
|- boostrap/
以上目录结构也没有错,但是总感觉有些不妥,哪里不妥呢?
就是自己项目的函数库和第三方的混到一起,使得有以下问题:
鉴于以上分析,有人提出了这样的概念:
于是,第二种目录关系出现了,即:
|- lib
|- plugin
|- util
|- ...
|- vendor
|- jquery/
|- boostrap/
这种目录结构是,lib 和 vendor 是同级的。
经过仔细分析和实际项目使用,个人觉得第二种方案更适合项目规范和开发流程,我们目前就采用第二种方案。
站在巨人的肩膀上,你才能走的更远!
但前提是你得思考,思考,再思考!重要的事情说三遍,不然你就不是站在肩膀上,而是跟在屁股上!
个人觉得,里面的每一条都应该仔细研读!
对于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?
知识点:
this
和 arguments
,fun1中的this(调用它的对象,也叫活动对象),即:window全局作用域
和 局部作用域
的最大区别就是,局部作用域在函数内部定义,并且在非闭包的情况下,函数运行完毕就释放掉了。所以:
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个:
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);
});
}
};
//3s后显示:hello night
function delayDisplay(){
var name = 'night';
setTimeout(function(){
console.log('hello ' + name);
},3000);
};
其实,还有很多这方面的例子,但是不管怎么样,你要记住一个概念:函数中的函数。
而且闭包有一个优势:就是能访问创建闭包对象的变量。
要了解 Google搜索和手气不错的区别
,请大家跟着我的思路来慢慢分析。
我自从2013年开始,零星时间做 歌德书签 的过程中想了很多很多, 歌德书签 也经历了3个大的版本调整,而大家现在能看到的页面上的所有功能,也是我认为最应该保留的。
注意: 小白用户就可以略过了,因为我建议你使用 hao123。或者如果您的网址收藏不超过10个,那么建议你使用浏览器自带的书签收藏功能。
在做产品的过程中,我对一个观点非常认同,即:“一个产品不在于你能增加多少功能,而在于你能舍弃多少功能。”
歌德书签 中要不要搜索的功能的问题困扰了我很久,最终决定一定要保留!因为既然歌德书签是入口,那么对于已收藏的书签,点击后直接打开目标页;而当用户在书签主页,突然想打开一个未知网站,或者搜素一个关键词的时候,你没有入口,难道让用户再在新标签页打开百度或者Google,搜索吗?那这就会让用户很忧虑,只有收藏的东西才能快速访问,没收藏的还得借助外力。
而 歌德书签 主要功能在于书签分类收藏,而搜索并非其使命!但是这个又是用户一个附加必要功能。如果搜索做的太厚?自己有点得不偿失,做薄了,用户可能还是得打开百度或者Google搜索。
周鸿祎说过,“自己一定要是自己产品的忠实粉丝,不然做不好产品。”,这点我深有体会。我自己在用的过程中发现,我收藏的网址,只是在一天工作开始的时候点击打开,或者零星工作中打开。而搜索这个功能,在我工作中占了很大一部分比例,也就是这是一个高频的事情。所以,搜索这个功能一定要有,而且要极力做到好用。
再确定了搜索要保留到 歌德书签 中,而且要做好的情况下,我就一直思考这个事情。其中,有一个点是这样的,我后台调用的是百度或者Google的API搜索,这个我只能这么做。但是从体验上,我每次搜索结果后,总是跳转到百度或者Google的搜索列表页面,然后再点击具体结果进入目标地址。
这里就有一个问题,比如我想进入一些官网,如:新浪网、新网微博、腾讯官网、人人网等等已非常明确关键词的网址,我搜索后依然需要先打开列表页,然后这些官网一般都是搜索结果的第一个,再点击进入。
也许你习惯了这个流程,反而没什么感觉,不就是多了一步嘛!但是,当你一旦觉察到这个事情后,你越这么搜索越觉得啰嗦!不信你现在去百度下 歌德书签,再打开 歌德书签,是不是觉得很麻烦?
说到这里,大家应该知道了Google的 手气不错
是干什么用的了吧?它就是直接打开你搜索关键词的最匹配网页,而不需要再看到中间的搜索结果。
话又说回来,怎么百度没有这个功能?肯定不是百度不知道这个事情,而是这个它根本不培养用户的这个使用习惯,为什么呢?
手气不错
了。而如果一旦达不到期望,用户还是要搜索,那还不如直接搜索呢,绕着弯子干嘛?什么是用户体验?个人理解:舍身处地的使用,触类旁通的思考,持续不断的改进。
关于 歌德书签 的搜索功能,目前很多没做到位!
百度
Or Google
吗? 其实TA需要的是结果!用户期望的是最终结果,而不在乎这个结果是通过 百度
、Google
还是 站内
。只要抓住这个点,其实接下来就是实现方式而已。
曾几何时看到别人的代码目录展示如下,非常的漂亮,于是我就把别人的这棵树拷贝过来,一个个修改变成自己的树,如下:
project
├── dist
│ ├── css
│ │ └── ...
│ │
│ └── js
│ ├── ...
│ │ ├── ...
│ │ ├── ...
│ │ └── ...
│ │
│ ├── ...
│ │ ├── ...
│ │ ├── ...
│ │ └── ...
│ │
│ ├── ...
│ └── ...
│
└── src
├── css
│ ├── ...
│ │ ├── ...
│ │ ├── ...
│ │ └── ...
│ │
│ ├── ...
│ └── ...
│
└── js
├── ...
│ ├── ...
│ ├── ...
│ ├── ...
│ ├── ...
│ └── ...
│
├── ...
│ ├── ...
│ ├── ...
│ └── ...
│
├── ...
│ ├── ...
│ ├── ...
│ ├── ...
│ └── ...
│
├── ... // ...
└── ... // ...
现在告诉大家,一个命令就可以搞定了!无论是windows和linux,在shell窗口直接输入:
> tree
关于这个命令的参数,请参考:
html为什么要使用include?如何include?
我们知道,web的三驾马车:html, js和css,其中:
js 我们由传统的所有逻辑写到一个js文件中,现在我们都提倡分模块开发,于是涌现出来 require.js
、 sea.js
和 CommonJs
等模块加载框架,以及 AMD
、CMD
等模块加载机制,同时ES6也提供了 class
和 modules
等机制,目的只有一个:模块化开发。
css 我们在页面上通过 link
可以引入各个css文件,在css里面可以使用 import
来引入其他css文件,为什么要这么做?如果所有文件都写在一个里面,那要这么引入干嘛?目的只有一个:模块化开发。
html 如何模块化开发?(⊙o⊙)…?顿时脑子里有点懵!其实,html模块化开发,可以有很多办法,比如:
通过上面的分析,我们至少有了2种办法,来分模块开发html,但是我们不使用js,也不用gulp等编译工具,有木有办法 include 一个模板?答案是:有。
我们都知道动态脚本语言都有include方法,比如:
<jsp:include>
<jsp:forward>
<!--# include file="file.tpl" -->
但是 html
这种静态的页面,如何实现include呢?答案是:SSI技术。
SSI 全称是 Server Side Include,也就是服务端引入技术,引入的是什么呢?是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" -->
参考:http://www.t086.com/code/apache2.2/misc/security_tips.html
本文包含了一些个人开发经验总结和设计模式的总结,希望能给大家有所帮助。
将代码少的逻辑放到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);
}
代码中发现有太多重复的语句,看似相同,但是又有些不同,于是乎就本能的习惯使用if语句写一大片!
其实最好的做法是代码去重!
核心的**: 就是想尽一切办法将相似的部分放到一个对象或者数组中,然后循环处理。
显示一年内各个月的天数。
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];
};
原始代码:
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();
};
};
原始代码:
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();
};
};
}
Array.join
和 String.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
。
//通过代码将所有元素放到一个数组中
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>
中。
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);
在 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);
假如有对象 a
和 对象 b
,要实现如下功能:
a
的属性复制到 b
对象上;a
和 b
合并成新对象 c
;推荐使用 jQuery
的 $.extend()
方法,或者其它库提供的类似方法。
a
和 b
对象分别如下:
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:将 a
和 b
合并成新对象 c
;
//通过递归的方式,将a对象的所有属性复制到b对象上
var c = $.extend(true, {}, b, a);
//如果不使用递归,而是浅拷贝,则:
var c = $.extend({}, b, a);
思考:如果自己实现以上方式,该怎么写?PS:需要考虑深拷贝和浅拷贝的问题;写出来可以与jQuery源码对比下。
三元表达式规则:
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;
};
//传统写法
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写的,但是**非常实用,建议大家都看下。
下一篇:提高生产效率的编程**之抽象能力(二)
请问以下结果能否在点击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回调函数是在最外层的匿名函数中,也就是函数中的函数(即:闭包),闭包有访问外层函数中变量的权利。
最近在使用数组排序的时候,一不小心犯了一个低级错误,分享一下,大家引以为戒。同时,提供一种能应对数组中含有各种特殊符号的排序方式。
在项目中需要将一个数组排序,于是我大致就是这么写的:
//示例,由大到小排序
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]
下面再验证几个典型的场景:
纯数字
arySort([100,22,0,3,10001])
//[0, 3, 22, 100, 10001]
注意:这里容易出现的错误就是 22 > 100
原因是把这2个按照字符串比较,这样100就排在22前面了。
纯英文
arySort(['hello','world','array','alert','boy','girl'])
//["alert", "array", "boy", "girl", "hello", "world"]
注意:alert 和 array,首字母相同时,比较第2个字符,依次类推,这就是字典顺序。
纯中文
arySort(['你好','我们','**人','比较','腼腆','害羞','奥运'])
//["奥运", "比较", "害羞", "腼腆", "你好", "我们", "**人"]
数字和英文
arySort([100,22,'array','sort',35,'lisa','vivo'])
//[22, 35, 100, "array", "lisa", "sort", "vivo"]
中英文和数字
[22, 35, 100, "array", "lisa", "sort", "vivo", "日本", "**"]
在书写代码时,优先处理特殊情况或者异常情况,其次再处理正常情况。
这么做的好处就是如果遇到特殊或者异常情况能快速执行完代码,同时书写更清晰。
//原始代码
if(condition){
//中间有100行代码
}else{
//这里只有1行代码!
};
以上逻辑最好的书写规范是:
if(!condition){
//1行代码!
return;
};
//剩余100行代码
(全文完)
ES6转ES5的语法,我们使用到了Babel,但是关于Babel的包一大堆,你能分清吗?
安装上面的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'));
以上基本用法只适用于一些基本的Es6的语法,但是不全,具体有哪些稍后我再研究下放出来,但是至少如:箭头函数之类的是可以转换的。
但是涉及到Map、Set等语法就无法转换了,这时候我们可能还需要一些plugin来进一步转换。
继续安装上面的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
(未完待补充)
css3中新增单位 rem
,那么如何区分和何时使用 rem
和 em
呢?
相对于document根目录字体而言的相对单位,默认是相对于浏览器默认字体(16px),即:1rem = 16px。
相当于使用它的容器的字体而言,如果使用者的字体定义单位也是 em
,那么就有继承关系。
如:
|-- div1(font-size:12px;)
|-- div2(font-size:2em;padding:3em;)
div2的padding = 3 x 2 x 12px = 72px。
既然都是相对单位,那么在不考虑浏览器兼容的情况下,如何选择使用这2个单位呢?
一般有2种情况可以考虑使用 em
,其余全部使用 rem
。
哪两种呢?
当然以上两种只是比较典型的代表,只要是有严重依赖相关元素尺寸的,使用 em
比较合理而已。
如果大家看了以上文章,依然迷迷糊糊,说明你对这2个概念的基础只是掌握还不够,那么仔细阅读以下文章。
本文主要说一个容易理解错误的地方,和常见的2种跨域方式。
经常有人会说,如何解决跨域cookie携带的问题,具体指哪里的cookie呢?
我们知道,既然跨域了,那么前台和后台肯定不在同一个域上,也就是出现了两个域。
比如:
那么,使用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的数据能在请求的时候自动携带上,那么后台就可以校验,请求是否合法。
对于无法携带cookie的情况,前台可以用参数的形式将后台需要的数据,再每次调用接口时传递,但是这种情况,安全性就是个考研。
最近H5的事情做的多一些,但是有一些移动端的概念还是有些模糊,索性就仔细研究了一下,拿出来共同提高。
也叫做:设备像素(device pixel)。
可以理解为屏幕上的点,这个是跟硬件有关系,跟Web软件语言没一毛钱关系。
你见过霓虹灯吗?LED灯?对!就是那上面一个个会发光的颗粒。
这个概念大家一般喊中文,貌似没见过缩写(当然你可以叫它:pp),因为在软件方面我们很少关注硬件嘛!自然这个概念也就没多大用啦!
这也是一个物理概念,其实就是指一个设备上横竖的点数。
比如:一个LED灯上面,横着放3个灯泡,竖着放4个灯泡,那么这个LED设备的分辨率就是:3 x 4。
当然对应到PC上的显示器上,或者手机端的屏幕行,这一个个的小点,就不是灯泡啦,而是一种新型的发光原件,而且由于排列密集,所以你肉眼根本看不见具体的一个个的点啦而已。
举个栗子:
iPhone手机的主屏:4.7英寸1334x750,就是指:对角线4.7英寸长,高1334个物理像素数,宽750个物理像素数。
是Web编程的概念,指的是CSS样式代码中使用的逻辑像素(或者叫虚拟像素)。
而我们知道,软件的开发离不开硬件的支持,就比如你要在浏览器看到显示效果,就得浏览器支持你呀!
在CSS规范中,长度单位可以分为绝对单位和相对单位,比如:px
是一个 相对单位 ,相对的是 物理像素(physical pixel)
,这也就是说到底我们平常开发中的 1px
在每个屏幕上怎么显示,完全取决于这个设备!
所以,问题就来了,到底CSS像素如何在硬件设备的物理像素上显示呢?往下看。
CSS像素
又叫做 设备无关像素(dips)
(比如:这里),又说这个概念跟真正的 dip/dp
不是一回事!真实气愤!就是这个概念的含糊不清,搞的我也头晕目眩的。设备无关像素
就是指 dip/dp
,是我下文专门有一节讲述的概念,它就是以 160ppi
为基准的一个相对单位,用来解决Android上面屏幕不统一问题的。物理像素
和 CSS像素
,分别代表 硬件
和 软件
的单位,别再搞错了。在以前的显示设备上,一个物理像素就显示一个CSS像素,这没什么好争议的,大家也理所当然的认为该这么处理,于是大家都在噪点中度过。
然而在2010年,搭载 Retina
(高清屏) 的 iPhone4
出现了!也就是从这时候起,手机屏幕的竞赛才真正开始啦,大家都争先空后的朝着更大尺寸、更高分辨率的方向前进。
那么 Retina
到底有什么突破呢?原来,它增加了一倍的手机屏幕的物理像素,用2个物理像素点,显示一个CSS像素。OMG,多么有创意的想法!于是还是原来的样子,还是那时的模样,但是由于屏幕点数增多了,所以看着就更加清晰啦。
看出来为什么上面的明显比底下的清晰,噪点少吗?因为它的网格更密集,其实也就是物理像素更多,但是CSS像素没变(比如:苹果的LOGO大小没变,文字大小没变)。
好了,弄明白了这点,我们再往下看一些其它的概念。
简写: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倍。
简写:ppi
,当然也叫做:dpi
。
ppi
pixers per inch,出现于计算机显示领域(当然也是Android中的习惯叫法)dpi
dots per inch,出现于打印或印刷领域(当然也是iOS中的习惯叫法)其实,从概念中大家也能知道,它表示了一种从逻辑单位到实际单位的换算。这句话怎么理解呢?
因为大家在实际生活中,已经大体知道1米是多长,1斤是多重,而这种单位就是实际单位。
所以,有了像素密度这个说法,你用英寸
来说明屏幕尺寸是一样的,不信,看看你能理解不?
你是不是明显觉得iphone6更亮!
记住:ppi
说的都是物理像素。
那么,ppi
如何计算呢?因为行业内大家说的手机屏幕都是对角线,比如:4.7英寸,指的是手机屏幕对角线的长度(仅仅是显示的屏幕哦,不包括边框),大家都并没有说手机的宽是多少英寸,高是多少英寸。所以,你计算 ppi
只能用对角线的物理像素数来除以对角线的实际单位啦。
那么计算ppi,首先得计算出对角线的物理像素数,使用勾股定理,即:ppi = 根号下(1920平方+1080平方)/5.2 约等于 294。
那么是不是ppi越大,越清晰呢?理论上是!但是,这得有个取舍。
没有对比就没有伤害:
来吧,感受下2英寸上,给你放1920x1080个物理像素,是不是一团漆黑了?有人喜欢大屏,也有人喜欢小屏,所以嘛这个值只是个参考,你可以对比相同尺寸下的ppi,但是千万不要对比不同尺寸下的,这样你会受伤的!
也叫做:密度无关像素。
指Google提出的用来适配Android海量奇怪屏幕的,之前iOS没有设备独立像素一说,因为之前它的CSS像素都是320px,但是随着iPhone6(375px)、iPhone6 Plus(414px)等不同手机型号出现,导致了手机上能看到的CSS像素点也增加的情况下,也得考虑了。
简写:dips
or dp
为什么Google提出这个概念能解决不同设备(或不同密度)下的显示问题呢?
原因是:不同的手机屏幕上 CSS像素
个数可能不一样,虽然大多数是320px!
举个栗子:
假如这时候我们有个正方形是 30x30px
,这是CSS像素,再上面的2个机器上看到的大小都一样,只是在iphone4上更清晰些,因为它用4个物理像素显示1个CSS像素。换句话说,如果大家都是手机都是320的CSS像素,那么我们就只管用 px
这个单位就可以了,在每个设备上的看到的大小都一样。
如果在iphone5s之前,iOS都不需要关心这个概念,因为你能看到的手机屏幕的CSS像素都是320px。但是,随着iPhone6/plus的出现,就让我们心塞了。
举个栗子:
160px x 160px
的元素,在iphone5s下面看起来宽度正好是 半个屏幕
大小;不到一半屏幕
大小!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);207px x 207px
(163px = 80 * 414 / 160);如果我们使用 dip
作单位,那么在2台机器上,显示出来的效果,差不多都等于2台机器宽度的一半!目的就达到了。
另外,dip
作为 单位
仅仅适用于Android,但是**可以用到 iOS
和 Windows
等平台。比如:Windows
中修改屏幕分辨率,其实修改的是 dpr
,又由于物理像素不变,其实就等于修改了屏幕中 CSS像素
的个数,这也就是为什么修改了分辨率后,有些东西看着明显变大或者变小的原因了。
就是做UI的MM或者GG给你的PS或者切图图片或者标注图片,拿着这个你就可以开始照着勾勒页面啦。当然,小公司可能没有这一步,也许就是产品拿着Axure画的原型直接输出给你,让你照着做页面,那么这个也就算是设计稿啦!相当于要求没辣么高啦。
负责输出设计稿的岗位,叫:UI(User Interface 用户界面)。
有些公司把设计稿也叫做:视觉稿
或者 高保真
图。
在大一些的公司,岗位分的可能更细,在开始做页面之前,除了会输出上面提到的 视觉稿
,还会同时给你输出一份具有动态效果示意的文件,一般可能是 GIF
文件。主要是告诉你,页面上某个元素的出现、消失等动画效果。尤其是做一些活动页面,动画要求多的,可能交互稿就很有必要,不然很多时候,你做出来的效果,未必是产品需要的,那就尴尬了。
负责输出交互稿的岗位,叫:UE(User Experience 用户体验),有的也叫 UX
。
UE用的比较多的工具是:Adobe After Effects CC 2015
。
jQuery中提供了3个方法,.attr()
、.prop()
和 .data()
,本文就介绍下这3者之间的不同。
关于这组概念在 jquery-1.6.1
的升级日志里说的太清楚不过了,看:这里,这里我大体总结下。
其实有一句话非常关键,大家只要理解了它,就自然明白了:
property
是元素的固有特性,体现了它相关的 attribute
(如果存在的话)比如:对于 checkbox
而言 checked
是其固有特性。怎么理解这句话呢?
看看示例:
<input type="checkbox" id="checkbox">
看看效果:
那么,为什么在1.6.1开始就区分了 attr
和 prop
这2个概念了呢?
以下阐述可能就是原因:
对于 document
和 window
这2个js对象,他们是没有 attributes
的(可以看到的属性),因为这是2个隐藏于页面内部的2个对象。
但是,window
却拥有类似于 loation
、screen
等 properties
。明白了吧?
也就是说,直到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()
的时候还是要用。
从 jquery-1.4.3
开始支持 .data()
来获取元素上绑定的数据。
为什么jQuery要增加这个方法?因为HTML5新增了一种属性结果:data-*
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之前,开发者为了临时保存一些与标签有关的数据时,往往把数据临时保存到 rel
、class
这些标签上,甚至有些人就自己造一些标签来保存,简直混乱不堪。
所有HTML5就增加了 data-*
的标签,以专门用来缓存标签数据。
.data()
有哪些特别之处data-
栗子:
<a id="foo"
href="#"
data-str="bar">foo!</a>
$('#foo').data('str'); //"bar"
而通过 .attr()
获取的话,需要携带上。
$('#foo').attr('data-str'); //"bar"
栗子:
<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
类型。
驼峰式
取值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中不推荐使用中划线。.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;
(全文完)
跨域问题对于前端来说,是一个老生常谈的问题了,无论是平时工作中,以及面试中基本都会问到。本文将梳理常见的几种跨域的优缺点,以及建议的跨域解决方案。
什么是跨域?跨域有哪些解决方案?等等基础知识,请参考这篇文章,讲的比较细致,这里就不在啰嗦。
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将地址进行转发,以达到请求接口的地址在同一个域的目的。
最主要的缺点就是 不安全
,所以公司里面建议不要使用!
CORS,在公司里面用的比较多,对于CORS的服务端配置,一般有以下两种方式:
这两种方案,第一种不需要依赖外部环境,纯粹在代码层面就可以解决问题。对于第二种方案,就比较依赖外部环境。那么,一般这两种方案如何选择呢?我个人给如下建议:
推荐
如果代码层面分层合理,建议直接在代码层面设置拦截器,统一拦截处理CORS。这么做的好处就是,一套代码无论以后部署在哪里都可以,不需要对外部环境提太多要求,自身系统健壮性比较强。另外,在代码层面处理CORS还有个好处,就是在开发连调、测试阶段,也依然可以方便进行。不然如果太依赖外部环境的话,开发连调环节,也需要搭建一套环境做为中间转发,比较麻烦。不推荐
当然,也有人在Nginx中配置CORS,这么做的话,对代码的要求就降低了,不需要考虑跨域问题,但就是多了一个中间环节,无论在开发、测试还是线上,都需要这个中间层,这一点不是很方便。所以,如果是用CORS,不建议使用Nginx配置。实际项目中,使用CORS典型的场景:H5代码部署在CDN,而后台部署在某后台服务器上。
这种场景下,典型的CDN的环境很难自由配置,而又必须依赖后台接口数据,这时候最好的 办法就是后台代码开发时,就支持跨域。
PROXY代理
的方法就是通过代理层做一次中间的转发,使得对于浏览器来说,就不存在跨域的问题。
这种方式最大的好处就是绝对安全,不会由于跨域引来一些安全风险,但是缺点就是中间需要做一层代理转发。
实际项目中,使用PROXY代理典型的场景:给客户或者用户使用的后台系统,原因主要在于:
如果采用这种方式,那在开发和测试阶段,分别怎么做呢?
本地 + MockServer
前端代码不需任何配置,只需要MockServer配置 CORS
支持跨域请求就可以。
本地 + 他人机器Server
本地需要安装代理软件,如:Charles
假设,上线后真实的访问路径和接口为:
假设,本地和他人机器Server真实的服务地址为:
这时候需要配置 Charles
路由 Map Remote
规则:
注意:后台接口的路由规则一定要放在前面,即:/api/* 的路由放在页面。
配置好上面的代理后,地址栏直接访问:http://abcd.vivo.com.cn 即可。
我们在本地是通过代理软件转发,而测试或者生产环境,则直接通过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;
}
}
上述配置可以理解为:
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种常见的解决跨域的方案,同时重点介绍了 CORS
和 PROXY代理
的一些优缺点,以及最适合的场景,归纳总结一下,就是:
(全文完)
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的另一个概念(变量提前),下文解释这个问题。扩展阅读:
我们通过 Hoist
的定义可以知道,在作用域范围内,变量和函数提前执行,那么上题可以变形为:
var myvar = 'my value';
(function() {
var myvar;
alert(myvar); // undefined
myvar = 'local value';
})();
而我们知道JS在当前作用域内是自上而下顺序执行的。所以,当执行到alert的时候,myvar还未初始化,所以结果就是undefined。
这里其实有2个隐身概念,即:
本节就说到这里,下节继续烧脑。
首先针对上节遗留2个问题,进行回答。
记忆中应该是这个样子,如果有问题,欢迎指正。
先解释下这2个概念的专业术语:
示例:
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"
补充说明下:
上文中提到了作用域(Scope),这里总结一个知识点,就是一个变量进入作用域的4种方式:
好了,所有概念全部梳理完了,以下经常在面试中出现的习题,大家不放围观下。
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);
世界上最轻量的 JavaScript
框架!
字面量就是变量创建的一种简写方式。
var test = new String("hello world!");
var test = "hello world!";
var ary = new Array();
var ary = [];
//eg1: 函数定义
var fun1 = function(){};
//eg2: 立即执行函数
(function(){
//...
})();
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
- Immediately Invoked Function Expression
首尾加括号包裹函数体
(function(){
console.log('here');
}());
(function(){
console.log('here');
})();
!function(){
console.log('here');
}()
或者
void function(){
console.log('here');
}()
{
"id" : 1,
"name" : "张三", // 注意这个逗号!
}
// 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
,即:省略路径前面的具体协议名,只保留 //
。
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
TypeScrip
t 是 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;
}());
(全文完)
浏览器如何展示页面的?
根据请求的URL进行域名解析,向服务器发起请求,接收文件(HTML、JS、CSS、图象等)。
对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(比如HTML的DOM树,JS的(对象)属性表,CSS的样式规则等等)
构建渲染树,对各个元素进行位置计算、样式计算等等,然后根据渲染树对页面进行渲染(可以理解为“画”元素)
这几个过程不是完全孤立的,会有交叉,比如HTML加载后就会进行解析,然后拉取HTML中指定的CSS、JS等。
其中,仅针对浏览器拿到资源后的渲染这一部分,可以包含两部分流程。
根据文档流,加上浮动、定位等属性,确定各个盒子的位置还有尺寸。
将css的属性例如字体、背景色、圆角等等按照给定的方式呈现出来。
注意: 这里涉及到2个性能优化的概念:
对页面进行重绘,这时候不会更改页面布局,性能变化没那么明显。
比如:修改 font-color
、text-align
等修改
对页面进行重新渲染,这个是要包含 Repaint
。也就是页面布局不仅要重新调整,样式也要重新调整。
对于引发这种重新渲染的情况,主要有:
clientHeight
、clientWidth
等,因为计算尺寸的时候要重新渲染一次才能得到结果,这是浏览器的计算原理。本文将帮助你理解这几个概念:shim
、polyfill
和 vanilla
。
是一个小的类库(lib),提供独立的API,以弥补人们在不同的环境下使用原生语言需要考虑兼容性的问题。比如:使用js原生Ajax操作时,你用 XMLHttpRequest
创建xhr对象,但是在IE8上,你就得用 ActiveXObject
,为了解决这些兼容问题,同时简化操作,jQuery出现了。jQuery的核心理念就是: write less,do more
!
所以,jQuery
就可以看成一个 shim
。
是一个小的类库(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
的目的都是为了解决浏览器兼容性,并且给大家提供方便的,这也是类库(lib)的一个作用;shim
包含了 polyfill
,范围更广,可以重新定义API,而 polyfill
相对狭义一些,API的使用上一定是JS标准;这个概念一般说的少一些,了解下就可以了。
在计算机软件领域中,如果你对原生系统没有做任何定制性的修改,就是Vanilla。
比如,这个网站就跟我们开了个玩笑:http://vanilla-js.com/,下载下来是空的,其实看域名就知道了,作者建议你使用原生API!
关于 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.config
的paths
中一定不能带有文件后缀名,如.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.js
、main1.js
和 main2.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>
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
使用它打包后,直接将 requirejs
的 data-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个发现:
requirejs
貌似 2.1.x
以上版本提供了 bundles
;r.js
打包后的文件中,多个define并不是单纯的合并到一起了,而是多了个 define name
;bundles
?比如:
requirejs.config({
bundles: {
'build1': [
'main1',
'main2'
],
'build2': [
'main3',
'main4'
],
}
});
什么意思呢?
就是 build1
这个js 中,输出 main1
和 main2
这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
的使用按照教程使用没有一点问题,但是结合项目,总是让你凌乱。
比如: r.js
打包的时候,必须有一个主入口文件,这个文件大致是这样的:
//package.js
require(['jquery','main1'],function(){
//TODO
});
其中 main1
又依赖领导 main2
:
//main1.js
require(['main2'],function(){
//TODO
});
打包以后,你就会发现打包的文件中,会将jquery
、main1
以及 main1
依赖的其它的模块 main2
一起打包进去。
这么依赖,如果一个项目很大,有些模块只有再用到才会加载,这样的话,你为了保证打包优化的时候,全部压缩进去,需要在这个入口文件 package.js
中 require
所有的模块,写一个这里加载一个,是不是太麻烦了啊?
使用 r.js
打包好的文件,总是在生成文件的最后面把这个 package.js
也打进去。。。我去!通过问题1我们知道,我在 package.js
中引入了所有的包,那么你打进去后,我只要使用require调用打包好的文件,岂不是所有模块都要执行一遍?
Shit!
综合,以上问题,我们有木有解决办法呢?肯定的嘛!使用构建工具,比如:gulp
。
不使用 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文件是不存在跨域问题的,但是有没有遇到过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小时日期会跳一下。
还有一点要注意:手动调整了日期,是不会影响到时针走两圈,日期跳一下这个逻辑的。
知道了上面的原理,现在告诉大家该如何设置啦。
步骤如下:
以上第3步,如果现在是下午的时候,你没多转一圈时间,而是直接设置了时间。那么,日期将会在第2天中午12点才能自动加一天,因为这时候才转了2圈。
不知道我上面讲的原理,你搞清楚了没?
昨天开小组例会时,我们收集到一个组内同事提的议题:怎么判断一个东西是否达到了行业标准,怎么获取一个东西的行业标准?
。
初次看到这个话题,我首先想到的是行业标准就是 参考别人怎么做的
,至于怎么获取,要么就是自己已经明确知道行业内的老大都有哪些了,要么就是初次进入这个行业,那么就只能多走走市场,多在百度上 Google
一下喽!但是,后来仔细想了下,觉得这个话题并没有这么简单,我的回答也并没有令人满意,疑点颇多。
再加上我们不是修改了我们的中期愿景么?简单点说就是:只做第一!
,这个口号的潜台词就是,在这个行业内
,我们:只做第一!
,不是么?到别的领域这个口号也就没意义了。
人常说 知己知彼,百战不殆!
,我们现在处于 什么行业?
,行业内我们现在排行第几?
,这个排行如何定义的?
,搞不清这些何谈 只做第一?
。
本文就来谈谈自己的理解和认识。
注:由于本人知识面浅薄,如果您觉得我的言论片面,欢迎批评指正。
行业,指主要根据职业、性质或具体事物,对社会各个领域的称呼。—— (维基百科)
既然我们知道了行业只是一个称呼,那么这里面就会有太多的不确定性,随机性,只是这个称呼在特定时间特定场合下大家一致认可了而已。
比如,我们经常说的:
传统行业
,说到这个词我们从不同的维度,如:衣食住行相关的、吃喝玩乐相关的等,举出一些如:服装行业、餐营业、酒店行业、旅游业、土木水电行业、煤炭行业等等。IT行业
,这个就是相对于 传统行业
最近些年才出现的行业,也慢慢的被大家所认可,有些人一提到这个行业,就会默默的给它打个标签:高薪
、加班
、程序猿
,甚至 猝死
之类。共享单车行业
,这是去年才兴起的一个 新型行业
,我们对它的认识,可能就是停留在一堆共享单车的品牌上,如:摩拜单车
、ofo
、小鸣单车
等,除此之外对这个行业,还是有点陌生。看了这些例子,我们大致可以总结行业有以下特点:
时间性
,行业是在特定的时间下出现的;可变性
,可能一直存在,可能慢慢消失;目标性
,行业是有明确的目标人群的;多样性
,可以在任何领域、任何时间、对于任何人群产生一种行业;存在性
,行业本身就存在,不是人类创造的,只是之前未被公开认可;(PS:我们不制造水,我们只是大自然的搬运工!)当我们了解了 行业的特点
之后,再来看看什么是 行业的标准
的时候,觉得这个说法非常难给它下个准确的定义,不过,我们可以给它一个宽泛的定义:
行业的标准
指,在一定的时间范围内,针对一些特定的目标人群,他们共同玩的游戏规则。
总结为一句话,行业的标准
就是指:游戏规则!
这样说来,大家可能就好理解了,举些例子:
实体按键
到手机上!因为行业内都是这么干的。。。行业的标准是什么呢?就是:诺基亚、摩托罗拉、索尼爱立信等手机大佬都是这么干的。现在呢?你绝对不会这么干了,行业的标准又是什么呢?就是:苹果、三星、vivo都是这么干的!DVVR
公司,我就是对这个感兴趣,而且我有钱,但是就是不知道怎么起步!(⊙o⊙)…,啥是 DVVR
?行业内有人做了么?标准是啥?前途一片迷茫!(PS:举个例子而已,你没听过正常,因为 DVVR
是我瞎YY的一个行业,现在还没出现呢!)看了这些例子,我想分享几点个人看法:
针对以上最后一点,举个例子:
摩拜单车,让大家认识了共享单车这个行业,它的自行车有这些特征:实心轮胎、无链条、不可拆座椅等,但是ofo就是传统自行车!这个行业刚开始的时候,大家都是走从0到1的过程,随着时间的挪动,谁生谁死不一定呢!我干嘛要遵守你?
SO,如果你现在进军共享单车行业,你该怎么做呢?也许你就是下一个 Steve Jobs
,让后人记住类似的名言:Stay hungry,stay foolish!
我们先来说一个话题:如何获取知识?
面试的时候,我们经常会问一些面试者:你都是怎么保持学习能力的?通过哪些渠道学习的?
,当然得到的答案基本都是一致的,如:看书、看视频、问人、关注牛人博客、GITHUB等
。
但是
,依据我的期望这些都只能算是最基本答案,也就是说你踏入这一行,不用说都应该这么做!不是么?
那么,我们到底该 如何获取知识?
个人理解,欢迎批评指正:
通过哪些渠道看书?看视频?有什么收获?
也就是说,不要流于形式,别人工作3年是经验,你只是经历过!如何将别人的东西转换为自己的?
多思考,多总结!自己有哪些缺点,如何弥补的?
比如:患有懒癌是你的缺点,你是如何治的?总结一句话:充分认识自己,多总结不要流于形式!
。
当一个人知道了如何获取知识的时候,那么获取行业标准,自然就不是问题了。
我的建议是:
最后,希望我们每个人都能在 只做第一!
的中期愿景的指导下,低头做事,抬头看路
,恪守本分,做正确的事情,把事情做正确。
2017 vivo加油!
(全文完)
初学者可能对NodeJs中的Module的导出傻傻分不清楚,我们来进行解惑。
这组概念是 CommonJs
的标准。
最终模块生效的是 module.exports
,而 exports
只是 module.exports
的一个默认简写引用而已。
它们的关系是:module.exports
= exports
=> [内存堆栈]。
上面的表达式隐含了以下含义:
module.exports
= exports
,二者指向同样的内存指向;exports
,那么 module.exports
也将同样得到修改;module.exports
(指向新的内存堆栈),exports
并不会得到修改,还是旧的内容,这时候二者将不相同。举些栗子:
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
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
,二者已指向不同的内容。
这组概念是 ES6
的语法。
作用同 CommonJs
的规范一样,都是模块化导出模块的作用。
二者的关系是: export default
是 export
的一种特例。
export
可以使用多次(导出多个元素),但是 export default
只能有一个。export default
,则必须这么写: import {xx,yy} from 'zzz.js'
;如果有,则可以这么写:import zzz from 'zzz.js'
。其实,最主要的作用就相当于是给导出的模块有个默认的值一样。
其实在Node.js中,这2组概念是可以混合着用的,但是为了保证概念的统一性,建议还是结对使用。
建议:
export + import
exports + require
(全文完)
css中有诸多选择器,运用得当的情况下,能让你少些不少的js,同时从容应对浏览器兼容的烦恼。
大家先看看 阮一峰
老师,在2009年总结的css选择器的文章:css选择器笔记
我们知道 基本选择器
包括:通用选择器
、元素选择器
、类选择器
和 ID选择器
。
这些这是概念,告诉你css有这些选择器,但是你知道他们在实际生产中如何使用,且有哪些规则吗?
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;
}
在面试的时候,面试官可能会问你选择器的优先级?为什么选择器还有优先级呢?因为css也是被顺序执行的,同时对某一个元素而言,它身上可能有多个选择器同时作用,那么自然而然就需要有个优先级来处理,这样我们最终看到的效果,就是这些共同作用的结果。
比如:
p{
color : red;
}
#p1{
color : blue;
}
.demo #p1{
color : green;
}
看看以上的例子,你知道最终 #p1
这个元素会显示什么颜色吗?这里就涉及到选择器的组合使用和优先级的问题了。
单优先级
!important
> ID选择器
> 类选择器
> 元素选择器
> 通用选择器
组合优先级
!important
权重:1000ID选择器
权重:100类选择器
权重:10元素选择器
权重:1通用选择器
权重:0(就是最后被执行的,相当于没权重)如何通过权重来知道最终生效的样式呢?看看上面的示例。
p
元素选择器,结果是:1#p1
ID选择器,结果是:100.demo #p1
类选择器 + ID选择器,结果是:10 + 100 = 110所以,最终的结果是:green
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;
}
有这样一个需求,下来菜单中,每个菜单之间有一条横线。怎么实现呢?
<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个外其余的
。
要了解事件冒泡,我们就得把它跟事件捕获来一起交代!
<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
addEventListener
的参数说明:https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
如文档中所说的,其实就是 useCapture
这个参数:true
表示事件捕获, false
表示事件冒泡,默认是冒泡
如下图是对 事件捕获
和 事件冒泡
的进一步说明
先说明下,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;
};
}
e.stopPropagation
e.preventDefault
window.event.cancelBubble = true
window.event.returnValue = true
使用jQuery的事件方法,直接使用 return false
既可以阻止冒泡也可以阻止默认行为
$(ele).click(function(){
//...
//阻止冒泡和默认行为
return false;
});
(全文完)
关于 setTimeout
的一些理解。
js不像java一样拥有sleep的功能,也就是将当前线程暂停一段时间后执行,因为js是基于事件机制工作的,所以它提供了 setTimeout
定时任务。
也就是说,如果你要实现一个sleep的功能,那也就只能将sleep后的任务,放到 setTimeout
的异步回调函数中执行吧!
本文就简单介绍一下 setTimeout
的原理,以及实际工作中的作用。
为什么js要搞成单线程?而不像java一样可以并发呢?根源在于js前期是嵌套在浏览器中的,而浏览器是直接跟用户打交道,对于人看到的结果而言,如果一次性并行执行多个任务,人!是会凌乱的。
当然,随着 node.js
的出现,js多线程不是梦,因为它的出现让大家知道:js不止能跑在浏览器上,还可以用在服务器端。所以,只要在服务器端,那么多线程的事情,终究会解决。虽然现在 node.js
全程异步机制已经很高效了!
为什么这么说呢?其实对于js而言,所以的任务,全部都进入队列,只不过它将当前执行的任务,叫做 主任务
,而在 主任务
中产生的附加的动作全部进入 任务队列
,当主线程执行完后,按照 FIFO
的原则,依次执行 任务队列
中的任务。
其实整个事件处理过程 = Main Task + Event Loop[**注意:**loop其实是个while(true),一直无线循环]
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
中的任务,就没有机会被执行了。
为什么要把延迟时间设置为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!
最近在项目中,看到有同事写一个数组的方法的时候,习惯性的用以下写法:
* [].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
转换一次了,实在影响代码可读性。
call
和 apply
都算是继承的一种写法,[].map.call(a1,fn)
就是将数组的map方法继承给a1,并且调用这个map方法;[].map.call
和 Array.prototype.map.call
是相同的,几乎没啥差异,性能上也差不多,见这里的测评;Object.prototype.toString.call
也可以写成 ({}).toString.call
,都是可以的,只所以写成 ({})
是由于不写的话,就成了 }.
而不是对象 {}
;最近在用ajax进行上传的时候,用到了许多ajax上传的js插件,有一些坑,拿出来跟大家分享下。
论功能来说,我是觉得 jQuery File Upload
要比 AjaxFileUpload
强大很多,比如:前者支持跨域。
当然,二者在处理ajax上传上面的**都是一样的,就是通过 iframe
的方式提交上传以及获取返回结果等。
$('#file').val('')
,是不行滴。clone()
后的对象,value是空的。原因是:你将 file
的 onchange
事件绑定到了 file
本身了。而第一次上传完毕后,当前 file
就被销毁,你实际看到的是 clone()
以后的元素。而 clone()
又没有克隆绑定事件,所以就没效果了。
原因是:上传以后没有清空前一次 file
选择的文件,所以就没有触发 onchange
事件。
原因是:同第一个问题,都是由于 clone()
以后之前绑定的校验方法丢失了。
options
fileElementId
指定file的ID。
说明:
上传组件直接指定ID,明显不合理!因为看它源码里,使用了 clone()
,所以,直接制定ID的话,上文中提到的3个问题,全部会遇到,不合理。
改进:
使用jquery的事件委托机制,通过指定file的父容器,来代理file进行操作,这样即使克隆了,都是同一个父元素,相关的事件依然生效。
比如:
$('#fileParent file').xxx
$('#fileParent').on(':file','xxx',function(){});
这时候的options
就可以直接传递一个对象,或者一个file的父容器。
比如,我将 fileElementId
属性直接改为 $fileElementParent
,就是一个父容器的jQuery对象。
有需要的可以:下载
总体而言,这个框架比 AjaxFileUpload
强大的很多。
options
replaceFileInput
是否替换当前file,默认:true,也就是默认情况下每次使用 clone()
替换旧的file。如果这个值设置为false,那么就会出现上文中 问题2
的情况,建议保持true。formData
上传时的参数,默认取得file所在form所有的参数,可以自己定义仅仅取得自己想要的参数传递,看看API就可以了。当然这个框架的功能还很多,比如:各种状态下的事件回调,跨域处理,进度条等等,大家去研究下API。
(全文完)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.