目录
标准模式(Standards Mode)和怪异模式 (Quirks Mode)
javascript中强制类型转换是一个非常易出现bug的点,知道强制转换时候的规则吗?
require() 加载文件机制
当 Node 遇到 require(X) 时,按下面的顺序处理。
(1)如果 X 是内置模块(比如 require('http'))
a. 返回该模块。
b. 不再继续执行。(2)如果 X 以 "./" 或者 "/" 或者 "../" 开头
a. 根据 X 所在的父模块,确定 X 的绝对路径。
b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
- X
- X.js
- X.json
- X.node
c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
- X/package.json(main字段)
- X/index.js
- X/index.json
- X/index.node
(3)如果 X 不带路径
a. 根据 X 所在的父模块,确定 X 可能的安装目录。
b. 依次在每个目录中,将 X 当成文件名或目录名加载。(4) 抛出 "not found"
请看一个例子。
当前脚本文件 /home/ry/projects/foo.js 执行了 require('bar') ,这属于上面的第三种情况。Node 内部运行过程如下。
首先,确定 x 的绝对路径可能是下面这些位置,依次搜索每一个目录。
/home/ry/projects/node_modules/bar /home/ry/node_modules/bar /home/node_modules/bar /node_modules/bar
搜索时,Node 先将 bar 当成文件名,依次尝试加载下面这些文件,只要有一个成功就返回。
bar bar.js bar.json bar.node
如果都不成功,说明 bar 可能是目录名,于是依次尝试加载下面这些文件。
bar/package.json(main字段) bar/index.js bar/index.json bar/index.node
如果在所有目录中,都无法找到 bar 对应的文件或目录,就抛出一个错误。
线程和进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器(来自百科)。我们启动一个服务、运行一个实例,就是开一个服务进程,例如Node.js 里通过 node app.js
开启一个服务进程,多进程就是进程的复制(fork),fork 出来的每个进程都拥有自己的独立空间地址、数据栈,一个进程无法访问另外一个进程里定义的变量、数据结构,只有建立了 IPC 通信,进程之间才可数据共享。
关于进程通过一个简单的 Node.js Demo 来验证下,执行以下代码
node process.js
,开启一个服务进程
// process.js
const http = require('http');
http.createServer().listen(3000, () => {
process.title = '测试进程 Node.js' // 进程进行命名
console.log(`process.pid: `, process.pid); // process.pid: 20279
});
线程
线程是操作系统能够进行运算调度的最小单位,首先我们要清楚线程是隶属于进程的,被包含于进程之中。一个线程只能隶属于一个进程,但是一个进程是可以拥有多个线程的。
同一块代码,可以根据系统CPU核心数启动多个进程,每个进程都有属于自己的独立运行空间,进程之间是不相互影响的。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage),线程又有单线程和多线程之分,具有代表性的 JavaScript、Java 语言。
单线程
单线程就是一个进程只开一个线程
Javascript 就是属于单线程,程序顺序执行,可以想象一下队列,前面一个执行完之后,后面才可以执行,当你在使用单线程语言编码时切勿有过多耗时的同步操作,否则线程会造成阻塞,导致后续响应无法处理。你如果采用 Javascript 进行编码时候,请尽可能的使用异步操作。
单线程使用总结
- Node.js 虽然是单线程模型,但是其基于事件驱动、异步非阻塞模式,可以应用于高并发场景,避免了线程创建、线程之间上下文切换所产生的资源开销。
- 如果你有需要大量计算,CPU 耗时的操作,开发时候要注意。
Nodejs的线程与进程
Node.js 是 Javascript 在服务端的运行环境,构建在 chrome 的 V8 引擎之上,基于事件驱动、非阻塞I/O模型,充分利用操作系统提供的异步 I/O 进行多任务的执行,适合于 I/O 密集型的应用场景,因为异步,程序无需阻塞等待结果返回,而是基于回调通知的机制,原本同步模式等待的时间,则可以用来处理其它任务,在 Web 服务器方面,著名的 Nginx 也是采用此模式(事件驱动),Nginx 采用 C 语言进行编写,主要用来做高性能的 Web 服务器,不适合做业务。Web业务开发中,如果你有高并发应用场景那么 Node.js 会是你不错的选择。
Node.js 中的进程 Process 是一个全局对象,无需 require 直接使用,给我们提供了当前进程中的相关信息。官方文档提供了详细的说明,感兴趣的可以亲自实践下 Process 文档。
- process.env:环境变量,例如通过 process.env.NODE_ENV 获取不同环境项目配置信息
- process.nextTick:这个在谈及 Event Loop 时经常为会提到
- process.pid:获取当前进程id
- process.ppid:当前进程对应的父进程
- process.cwd():获取当前进程工作目录
- process.platform:获取当前进程运行的操作系统平台
- process.uptime():当前进程已运行时间,例如:pm2 守护进程的 uptime 值
- 进程事件:process.on('uncaughtException', cb) 捕获异常信息、process.on('exit', cb)进程推出监听
- 三个标准流:process.stdout 标准输出、process.stdin 标准输入、process.stderr 标准错误输出
关于 Node.js 进程的几点总结
- Javascript 是单线程,但是做为宿主环境的 Node.js 并非是单线程的。
- 由于单线程原故,一些复杂的、消耗 CPU 资源的任务建议不要交给 Node.js 来处理,当你的业务需要一些大量计算、视频编码解码等 CPU 密集型的任务,可以采用 C 语言。
- Node.js 和 Nginx 均采用事件驱动方式,避免了多线程的线程创建、线程上下文切换的开销。如果你的业务大多是基于 I/O 操作,那么你可以选择 Node.js 来开发。
网络模型
大多数同学对于 HTTP、HTTPS 会很熟悉,通常用于浏览器与服务端交互,或者服务端与服务端的交互,另外两个 Net 与 Dgram 也许会相对陌生,这两个是基于网络模型的传输层来实现的,分别对应于 TCP、UDP 协议,下面一图看明白 OSI 七层模型 与 TCP/IP 五层模型之间的关系,中间使用虚线标注了传输层,对于上层应用层(HTTP/HTTPS等)也都是基于这一层的 TCP 协议来实现的,所以想使用 Node.js 做服务端开发,Net 模块也是你必须要掌握的,这也是我们本篇要讲解的重点。
初识 TCP 协议
TCP 是传输控制协议,大多数情况下我们都会使用这个协议,因为它是一个更可靠的数据传输协议,具有如下三个特点:
- 面向链接: 需要对方主机在线,并建立链接。
- 面向字节流: 你给我一堆字节流的数据,我给你发送出去,但是每次发送多少是我说了算,每次选出一段字节发送的时候,都会带上一个序号,这个序号就是发送的这段字节中编号最小的字节的编号。
- 可靠: 保证数据有序的到达对方主机,每发送一个数据就会期待收到对方的回复,如果在指定时间内收到了对方的回复,就确认为数据到达,如果超过一定时间没收到对方回复,就认为对方没收到,在重新发送一遍。
三次握手
上面三个特点说到 TCP 是面向链接和可靠的,其一个显著特征是在传输之前会有一个 3 次握手,实现过程如下所示:
在一次 TCP 三次握手的过程中,客户端与服务端会分别提供一个套接字来形成一个链接。之后客户端与服务端通过这个链接来互相发送数据。
先清楚一个概念http请求与tcp链接之间的关系,在客户端向服务端请求和返回的过程中,是需要去创建一个TCP connection,因为http是不存在链接这样一个概念的,它只有请求和响应这样一个概念,请求和响应都是一个数据包,中间要通过一个传输通道,这个传输通道就是在TCP里面创建了一个从客户端发起和服务端接收的一个链接,TCP链接在创建的时候是有一个三次握手(三次网络传输)这样一个消耗在的。
第一次握手: 建立连接,客户端A发送SYN=1、随机产生Seq=client_isn的数据包到服务器B,等待服务器确认。
第二次握手: 服务器B收到请求后确认联机(可以接受数据),发起第二次握手请求,ACK=(A的Seq+1)、SYN=1,随机产生Seq=client_isn的数据包到A。
第三次握手: A收到后检查ACK是否正确,若正确,A会在发送确认包ACK=服务器B的Seq+1、ACK=1,服务器B收到后确认Seq值与ACK值,若正确,则建立连接。
TCP标示:
- SYN(synchronous建立联机)
- ACK(acknowledgement 确认)
- Sequence number(顺序号码)
总结:
至于为什么要经过三次握手呢,是为了防止服务端开启一些无用的链接,保证双方的有效率传输。网络传输是有延时的,中间可能隔着非常远的距离,通过光纤或者中间代理服务器等,客户端发送一个请求,服务端收到之后如果直接创建一个链接,返回内容给到客户端,因为网络传输原因,这个数据包丢失了,客户端就一直接收不到服务器返回的这个数据,超过了客户端设置的时间就关闭了,那么这时候服务端是不知道的,它的端口就会开着等待客户端发送实际的请求数据,服务这个开销也就浪费掉了。只有经过三次握手,才能确定双方的收、发功能都是正常的,那么彼此之间的通讯也就得到保障了,也就不存在上面的开销浪费了。
I/O
I/O 是 Input/Ouput 的缩写,即输入输出端口,是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据。
I/O 先修知识
I/O 也是一个很宽泛的词,每个设备都会有一个专用的 I/O 地址,用来处理自己的输入输出信息。对于服务端研发的童鞋相信 网络 I/O、磁盘 I/O 这些词,也需并不陌生,一次 API 接口调用、向磁盘写入日志信息,其实就是在跟 I/O 打交道。一次 I/O 操作分为等待资源、使用资源两个阶段,以下分别进行介绍。
阻塞与非阻塞 I/O
阻塞与非阻塞 I/O 是对于操作系统内核而言的,发生在等待资源阶段,根据发起 I/O 请求是否阻塞来判断。
阻塞 I/O:这种模式下一个用户进程在发起一个 I/O 操作之后,只有收到响应或者超时才可进行处理其它事情,否则 I/O 将会一直阻塞。以读取磁盘上的一段文件为例,系统内核在完成磁盘寻道、读取数据、复制数据到内存中之后,这个调用才算完成。阻塞的这段时间对 CPU 资源是浪费的。 非阻塞 I/O:这种模式下一个用户进程发起一个 I/O 操作之后,如果数据没有就绪,会立刻返回(标志数据资源不可用),此时 CPU 时间片可以用来做一些其它事情。
同步与异步 I/O
同步与异步 I/O 发生在使用资源阶段,根据实际 I/O 操作来判断。
同步 I/O:应用发送或接收数据后,如果不返回,继续等待(此处发生阻塞),直到数据成功或失败返回。 异步 I/O:应用发送或接收数据后立刻返回,数据写入 OS 缓存,由 OS 完成数据发送或接收,并返回成功或失败的信息给应用。Node.js 就是典型的异步编程例子。
OSI 七层模型与网际网协议族图
可以看到这里以传输层做了边界划分,传输层之上为用户空间(Web 客户端、浏览器、FTP 这些都属于上三层),下四层为内核空间,例如传输层的 TCP、UDP 协议就对应到了内核空间。
Git
Git 当下最流行的版本管理工具
基础命令
git init
初始化本地仓库git add -A .
来一次添加所有改变的文件git add -A
表示添加所有内容git add .
表示添加新文件和编辑过的文件不包括删除的文件git add -u
表示添加编辑或者删除的文件,不包括新添加的文件git commit -m '版本信息'
提交的版本信息描述git status
查看状态git push -u origin master
推送到远程仓库看git pull
拉取远程仓库代码到本地git branch -av
查看每个分支的最新提交记录git branch -vv
查看每个分支属于哪个远程仓库git reset --hard a3f40baadd5fea57b1b40f23f9a54a644eebd52e
代码回归到某个提交记录
分支操作
- 查看本地都有哪些分支
git branch -a
- 新建分支
git branch dev
- 查看当前分支
git branch
- 切换分支
git checkout dev
- 删除本地分支
git branch -d dev
- 同步删除远程分支
git push origin :dev
修改远程仓库地址
- 方法1,先删后加:
git remote rm origin
先删除git remote add origin 仓库地址
链接到到远程git仓库
- 方法2,修改命令:
git remote set-url origin 仓库地址
远程分支获取最新的版本到本地
-
执行
git pull
命令 -
如果以上命令还是失败尝试以下步骤:
-
首先从远程的origin的master主分支下载最新的版本到origin/master分支上
git fetch origin master
-
比较本地的master分支和origin/master分支的差别
git log -p master..origin/master
-
进行合并
git merge origin/master
-
拉取远程仓库指定分支到本地
-
首先要与origin master建立连接:
git remote add origin git@github.com:XXXX/nothing2.git
-
切换到其中某个子分支:
git checkout -b dev origin/dev
-
可能会报这种错误:
fatal: Cannot update paths and switch to branch 'dev' at the same time.
Did you intend to checkout 'origin/dev' which can not be resolved as commit?
-
原因是你本地并没有dev这个分支,这时你可以用
git branch -a
命令来查看本地是否具有dev分支 -
我们需要:
git fetch origin dev
命令来把远程分支拉到本地 -
然后使用:
git checkout -b dev origin/dev
在本地创建分支dev并切换到该分支 -
最后使用:
git pull origin dev
就可以把某个分支上的内容都拉取到本地了
SEO优化
SEO(Search Engine Optimization):汉译为搜索引擎优化。是一种方式:利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。目的是让其在行业内占据领先地位,获得品牌收益。很大程度上是网站经营者的一种商业行为,将自己或自己公司的排名前移。
优化策略
1.主题要明确,内容要丰富
在设计制作网站之前,要清晰设定网络的主题、用途和内容,网站主题须明确突出,内容丰富饱满,以符合用户体验为原则。对于一个网站来说,优化网站的主题与实际内容才是最为重要的。一个网站需要有鲜明的主题,丰富的与主题相关的内容,专注于某些领域的变化的,及时更新。
2.引出链接要人气化
搜索引擎判断网站的好坏的一个标准是外部链接的多少以及所链接的网站质量。创建有人气化的、有意义的引出链接,提高链接广泛度,既能提高在搜索引擎的排名,同时也可以起到互相宣传的作用。
3.关键词设定要突出
网站的关键词非常重要,它决定网站是否能被用户搜索到,因此在关键词的选择上要特意注意。关键词的选择必须突出,遵循一定的原则,如:关键词要与网站主题相关,不要一味的追求热门词汇;避免使用含义很广的一般性词汇;根据产品的种类及特性,尽可能选取具体的词;选取人们在使用搜索引擎时常用到与网站所需推广的产品及服务相关的词。5至10个关键词数量是比较适中的,密度可为2%—8%。要重视在标题(Page Title)、段落标题(Heading)这两个网页中最重要最显眼的位置体现关键词,还须在网页内容、图片的alt属性、META标签等网页描述上均可不同的程度设置突出关键词。
4.网站架构层次要清晰
网站结构上尽量避免采用框架结构,导航条尽量不使用FLASH按钮。首先要重视网站首页的设计,因为网站的首页被搜索引擎检测到的概率要比其他网页大得多。通常要将网站的首页文件放在网站的根目录下,因为根目录下的检索速度最快。其次要注意网站的层次(即子目录)不宜太多,一级目录不超过两个层次,详细目录也不要超过四个层次。最后,网站的导航尽量使用纯文字进行导航,因为文本要比图片表达的信息更多。
5.页面容量要合理化
网页分为静态网页与动态网页两种,动态网页即具有交互功能的网页,也就是通过数据库搜索返回数据,这样搜索引擎在搜索时所费的时间较长,而且一旦数据库中的内容更新,搜索引擎抓取的数据也不再准确,所以搜索引擎很少收录动态网页,排名结果也不好。而静态网页不具备交互功能,即单纯的信息介绍,搜索引擎搜索时所费时间短,而且准确,所以愿意收录,排名结果比较好。所以网站要尽量使用静态网页,减少使用动态网页。网页容量越小显示速度越快,对搜索引擎蜘蛛程序的友好度越高,因而在制作网页的时候要尽量精简HTML代码,通常网页容量不超过15kB。网页中的Java.script和CSS尽可能和网页分离。应该鼓励遵循W3C的规范使用,更规范的XHTML和XML作为显示格式。
6.网站导航要清晰化
搜素引擎是通过专有的蜘蛛程序来查找出每一个网页上的HTML代码,当网页上有链接时就逐个搜索,直到没有指向任何页面的链接。蜘蛛程序需要访问完所有的页面需要花费很长的时间,所以网站的导航需要便于蜘蛛程序进行索引收录。可根据自己的网站结构,制作网站地图simemap.html,在网页地图中列出网站所有子栏目的链接,并将网站中所有的文件放在网站的根目录下。网站地图可增加搜索引擎友好度,可让蜘蛛程序快速访问整个站点上的所有网页和栏目。
7.网站发布要更新
为了更好的实现与搜索引擎对话,将经过优化的企业网站主动提交到各搜索引擎,让其免费收录,争取较好的自然排名。一个网站如果能够进行有规律的更新,那么搜索引擎更容易收录。因而合理的更新网站也是搜索引擎优化的一个重要方法。 [1]
SEO实战
TDK优化
TDK为title
,description
,keywords
三个的统称。当然title
是最有用的,是非常值得优化的;而keywords
因为以前被seo人员过度使用,所以现在对这个进行优化对搜索引擎是没用的,这里就不说了;description
的描述会直接显示在搜索的介绍中,所以对用户的判断是否点击还是非常有效的
<title>SEO优化实战 - 腾讯Web前端 IMWeb 团队社区 | blog | 团队博客</title>
<meta name="description" content="Web前端 腾讯IMWeb 团队社区">
<meta name="keywords" content="前端交流,前端社区,前端,iconfont,javascript,html,css,webfront,nodejs, node, express, connect, socket.io, lego.imweb.io">
页面内容优化
使用html5结构
如果条件允许(如移动端,兼容ie9+,如果ie8+就针对ie8引入html5.js
吧),是时候开始考虑使用html5语义化标签。如header
,footer
,section
,aside
,nav
,article
等,这里我们截图看一个整体布局
更多html5语义化标签请参考:All HTML5 Tags
唯一的H1标题
每个页面都应该有个唯一的h1标题,但不是每个页面的h1标题都是站点名称。(但html5中h1标题是可以多次出现的,每个具有结构大纲的标签都可以拥有自己独立的h1标题,如header
,footer
,section
,aside
,article
)
首页的h1标题为站点名称,内页的h1标题为各个内页的标题,如分类页用分类的名字,详细页用详细页标题作为h1标题
<!-- 首页 -->
<h1 class="page-tt">腾讯课堂</h1>
<!-- 分类页 -->
<h1 class="page-tt">前端开发在线培训视频教程</h1>
<!-- 详细页 -->
<h1 class="page-tt">html5+CSS3</h1>
img设置alt
属性
img
必须设置alt
属性,如果宽度和高度固定请同时设置固定的值
<img src="" alt="seo优化实战" width="200" height="100" />
nofollow
对不需要跟踪爬行的链接,设置nofollow
。可用在博客评论、论坛帖子、社会化网站、留言板等地方,也可用于广告链接,还可用于隐私政策,用户条款,登录等。如下代码表示该链接不需要跟踪爬行,可以阻止蜘蛛爬行及传递权重。
<a href="http://example.com" rel="nofollow">no follow 链接</a>
正文
内容方面考虑:
- 自然写作
- 高质量原创内容
- 吸引阅读的写作手法
- 突出卖点
- 增强信任感
- 引导进一步行为
用户体验方面考虑:
- 排版合理、清晰、美观、字体、背景易于阅读
- 实质内容处在页面最重要位置,用户一眼就能看到
- 实质内容与广告能够清晰区分
- 第一屏就有实质内容,而不是需要下拉页面才能看到
- 广告数量不宜过多,位置不应该妨碍用户阅读
- 如果图片、视频有利于用户理解页面内容,尽量制作图片、视频等
- 避免过多弹窗
URL优化
URL设计原则:
- 越短越好
- 避免太多参数
- 目录层次尽量少
- 文件及目录名具描述性
- URL中包括关键词(中文除外)
- 字母全部小写
- 连词符使用
-
而不是_
- 目录形式而非文件形式
URL静态化
以现在搜索引擎的爬行能力是可以不用做静态化的,但是从收录难易度,用户体验及社会化分享,静态简短的URL都是更有利的。
具体讨论可参考:URL静态化还是不静态化?
URL规范化
1、统一连接
http://www.domainname.com
http://domainname.com
http://www.domainname.com/index.html
http://domainname.com/index.html
以上四个其实都是首页,虽然不会给访客造成什么麻烦,但对于搜索引擎来说就是四条网址,并且内容相同,很可能会被误认为是作弊手段,而且当搜索引擎要规范化网址时,需要从这些选择当中挑一个最好的代表,但是挑的这个不一定是你想要的。所以最好自己就规范好。
2、301跳转
第一种是URL发生改变,一定要把旧的地址301指向新的,不然之前做的一些收录权重什么的全白搭了。
第二种是一些cms系统,极有可能会造成多个路径对应同一篇文章。如drupal默认的路径是以node/nid
,但是如果启用了path token,就可以自己自定义路径。这样一来就有两条路径对应同一篇文章。所以可以启用301,最终转向一个路径。
3、canonical
这个标签表示页面的唯一性(这个标签以前百度不支持,现在支持),用在平时参数传递的时候,如:
//:ke.qq.com/download/app.html
//:ke.qq.com/download/app.html?from=123
//:ke.qq.com/download/app.html?from=456
以上三个表示三个页面,但其实后两个只是想表明从哪来的而已,所以为了确保这三个为同一个页面,我们在head
上加上canonical
标签。
<link rel="cononical" href="//:ke.qq.com/download/download/app.html" />
robots
robots.txt
搜索引擎蜘蛛访问网站时会第一个访问robots.txt文件,robots.txt用于指导搜索引擎蜘蛛禁止抓取网站某些内容或只允许抓取那些内容,放在站点根目录。
以腾讯课堂的robots.txt为例:
- User-agent 表示以下规则适用哪个蜘蛛,
*
表示所有 #
表示注释- Disallow 表示禁止抓取的文件或目录,必须每个一行,分开写
- Allow 表示允许抓取的文件或目录,必须每个一行,分开写
- Sitemap 表示站点XML地图,注意S大写
下面表示禁止所有搜索引擎蜘蛛抓取任何内容
User-agent: *
Disallow: /
下面表示允许所有搜索引擎蜘蛛抓取任何内容
User-agent: *
Disallow:
注意:被robots禁止抓取的URL还是可能被索引并出现在搜索结果中的。只要有导入链接指向这个URL,搜索引擎就知道这个URL的存在,虽然不会抓取页面内容,但是索引库还是有这个URL的信息。以淘宝为例:
禁止百度搜索引擎抓取
百度搜索有显示
更多关于robots.txt
请参考:如何使用robots.txt及其详解
meta robots
如果要想URL完全不出现在搜索结果中,则需设置meta robots
<meta name="robots" content="onindex,nofollow">
上面代码表示:禁止所有搜索引擎索引本页,禁止跟踪本页上的链接。
当然还有其他类型的content,不过各个浏览器支持情况不同,所以这里忽略。
sitemap
站点地图格式分为HTML和XML两种。
HTML版本的是普通的HTML页面sitemap.html
,用户可以直接访问,可以列出站点的所有主要链接,建议不超过100条。
XML版本的站点地图是google在2005年提出的,由XML标签组成,编码为utf-8
,罗列页面所有的URL。其格式如下:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>//ke.qq.com</loc>
<lastmod>2015-12-28</lastmod>
<changefreq>always</changefreq>
<priority>1.0</priority>
</url>
...
</urlset>
其中urlset
,url
,loc
三个为必须标签,lastmod
,changefreq
,priority
为可选标签。
lastmod
表示页面最后一次更新时间。
changefreq
表示文件更新频率,有以下几种取值:always, hourly, daily, weekly, monthly, yearly, never。其中always表示一直变动,每次访问页面内容都不同;而never表示从来不变。
priority
表示URL相对重要程度,取值范围为0.0-1.0
,1.0
表示最重要,一般用在网站首页,对应的0.0
就是最不重要的,默认重要程度为0.5
。(注意这里的重要度是我们标记的,并不代表搜索引擎真的就完全按照我们设置的重要度来排列)
sitemap.xml
不能超过10M,而且每个sitemap文件中url的条数不要超过5万条,当你的sitemap文件很大的时候,可以分解为多个文件。如下分为两条,一条为基础,一条为产品详细页。
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>//ke.qq.com/sitemap-basic.xml</loc><lastmod>2015-12-28T02:10Z</lastmod></sitemap>
<sitemap><loc>//ke.qq.com/sitemap-product.xml</loc><lastmod>2015-12-28T02:10Z</lastmod></sitemap>
</sitemapindex>
SEO工具
- 百度搜索风云榜
- 百度指数
- 百度站长平台
- meta seo inspector,检查标签的,谷歌插件
- seo in china,百度收录的各种数据,谷歌插件
- check my links,检查链接,谷歌插件
- seo quake,统计各种数据,谷歌插件
最后,本文参考百度搜索引擎优化指南2.0 和zac著作《SEO实战密码》(对SEO感兴趣的同学,可以买本看看)。
参考:SEO优化实战
let、const、class和var的区别
顶层对象(浏览器环境指 window、Node.js 环境指 Global)的属性和全局变量属性的赋值是相等价的,
- 使用 var 和 function 声明的是顶层对象的属性,而 let 就属于 ES6 规范了,但是 ES6 规范中 let、const、class 这些声明的全局变量,不再属于顶层对象的属性。
- let声明的变量是块级作用域(所在花括号里),var是函数作用域和全局作用域
- 从代码的写法上,let不能声明同名的变量,var可以。
-
var声明变量存在变量提升,let和const不存在变量提升
let a = '22' ;
(function (){
const a = '11'
})()// 这里成了一个私有作用域,所以可以
//如果这里出现const a = '11';// 则会报错!const不允许重名变量
console.log(a,'a') //22
js三大事件(鼠标事件、键盘事件、html事件)
鼠标事件
- click:单击
- dblclick:双击
- mousedown:鼠标按下
- mouseup:鼠标抬起
- mouseover:鼠标悬浮
- mouseout:鼠标离开
- mousemove:鼠标移动
- mouseenter:鼠标进入
- mouseleave:鼠标离开
键盘事件
- keydown:按键按下
- keyup:按键抬起
- keypress:按键按下抬起
HTML事件
- load:文档加载完成
- select:被选中的时候
- change:内容被改变
- focus:得到光标
- resize:窗口尺寸变化
- scroll:滚动条移动
JS引擎的执行机制
首先,请牢记2点:
(1) JS是单线程语言
(2) JS的Event Loop是JS的执行机制。深入了解JS的执行,就等于深入了解JS里的event loop
例1,观察它的执行顺序
console.log(1)
setTimeout(function(){
console.log(2)
},0)
console.log(3)
运行结果是: 1 3 2
也就是说,setTimeout里的函数并没有立即执行,而是延迟了一段时间,满足一定条件后,才去执行的,这类代码,我们叫异步代码。
所以,这里我们首先知道了JS里的一种分类方式,就是将任务分为: 同步任务和异步任务
按照这种分类方式:JS的执行机制是
- 首先判断JS是同步还是异步,同步就进入主线程,异步就进入event table
- 异步任务在event table中注册函数,当满足触发条件后,被推入event queue
- 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中
以上三步循环执行,这就是event loop
所以上面的例子,你是否可以描述它的执行顺序了呢?
console.log(1) 是同步任务,放入主线程里
setTimeout() 是异步任务,被放入event table, 0秒之后被推入event queue里
console.log(3 是同步任务,放到主线程里
当 1、 3在控制条被打印后,主线程去event queue(事件队列)里查看是否有可执行的函数,执行setTimeout里的函数
3.JS中的event loop(2)
所以,上面关于event loop就是我对JS执行机制的理解,直到我遇到了下面这段代码
例2:
setTimeout(function(){
console.log('定时器开始啦')
});
new Promise(function(resolve){
console.log('马上执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
尝试按照,上文我们刚学到的JS执行机制去分析
setTimeout 是异步任务,被放到event table
new Promise 是同步任务,被放到主线程里,直接执行打印 console.log('马上执行for循环啦')
.then里的函数是 异步任务,被放到event table
console.log('代码执行结束')是同步代码,被放到主线程里,直接执行
所以,结果是 【马上执行for循环啦 --- 代码执行结束 --- 定时器开始啦 --- 执行then函数啦】吗?
亲自执行后,结果居然不是这样,而是【马上执行for循环啦 --- 代码执行结束 --- 执行then函数啦 --- 定时器开始啦】
那么,难道是异步任务的执行顺序,不是前后顺序,而是另有规定? 事实上,按照异步和同步的划分方式,并不准确。
而准确的划分方式是:
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick
按照这种分类方式:JS的执行机制是
- 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
- 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
重复以上2步骤,结合event loop(1) event loop(2) ,就是更为准确的JS执行机制了。
尝试按照刚学的执行机制,去分析例2:
首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里
遇到 new Promise直接执行,打印"马上执行for循环啦"
遇到then方法,是微任务,将其放到微任务的【队列里】
打印 "代码执行结束"
本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"执行then函数啦"
到此,本轮的event loop 全部完成。
下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个 setTimeout里的函数,执行打印"定时器开始啦"
所以最后的执行顺序是【马上执行for循环啦 --- 代码执行结束 --- 执行then函数啦 --- 定时器开始啦】
4. 谈谈setTimeout
这段setTimeout代码什么意思? 我们一般说: 3秒后,会执行setTimeout里的那个函数
setTimeout(function(){
console.log('执行了')
},3000)
但是这种说并不严谨,准确的解释是: 3秒后,setTimeout里的函数被会推入event queue,而event queue(事件队列)里的任务,只有在主线程空闲时才会执行。
所以只有满足 (1)3秒后 (2)主线程空闲,同时满足时,才会3秒后执行该函数
如果主线程执行内容很多,执行时间超过3秒,比如执行了10秒,那么这个函数只能10秒后执行了
webpack
什么是Webpack
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言或者高级语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
为什要使用WebPack
今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法
a:模块化,让我们可以把复杂的程序细化为小的文件;
b:类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能能装换为JavaScript文件使浏览器可以识别;
c:scss,less等CSS预处理器
.........
这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就为WebPack类的工具的出现提供了需求
Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
DOCTYPE
DOCTYPE全称Document Type Declaration(文档类型声明,缩写DTD)
DTD的声明影响浏览器对于CSS代码及Javascript脚本的解析。
<!DOCTYPE>声明叫做文件类型定义(DTD),声明的作用为了告诉浏览器该文件的类型。让浏览器解析器知道应该用哪个规范来解析文档。<!DOCTYPE>声明必须在 HTML 文档的第一行,这并不是一个 HTML 标签。
严格模式与混杂模式如何区分?它们有何意义?
严格模式:又称标准模式,是指浏览器按照 W3C 标准解析代码。
混杂模式:又称怪异模式或兼容模式,是指浏览器用自己的方式解析代码。
如何区分:浏览器解析时到底使用严格模式还是混杂模式,与网页中的 DTD 直接相关。
1、如果文档包含严格的 DOCTYPE ,那么它一般以严格模式呈现。(严格 DTD ——严格模式)
2、包含过渡 DTD 和 URI 的 DOCTYPE ,也以严格模式呈现,但有过渡 DTD 而没有 URI (统一资源标识符,就是声明最后的地址)会导致页面以混杂模式呈现。(有 URI 的过渡 DTD ——严格模式;没有 URI 的过渡 DTD ——混杂模式)
3、DOCTYPE 不存在或形式不正确会导致文档以混杂模式呈现。(DTD不存在或者格式不正确——混杂模式)
4、HTML5 没有 DTD ,因此也就没有严格模式与混杂模式的区别,HTML5 有相对宽松的语法,实现时,已经尽可能大的实现了向后兼容。( HTML5 没有严格和混杂之分)
意义:严格模式与混杂模式存在的意义与其来源密切相关,如果说只存在严格模式,那么许多旧网站必然受到影响,如果只存在混杂模式,那么会回到当时浏览器大战时的混乱,每个浏览器都有自己的解析模式。
用JS判断浏览器当前的模式:
- document.compatMode=='CSS1Compat'?'标准模式':'混杂模式';
- IE、Firefox、Opera、Sarari和Chrome都实现了这个属性;
IE8的特殊情况:
IE8又为document对象引入了一个名为documentMode新属性,这是因为IE8有3种不同的呈现模式,这个属性的值如果是5,则表示混杂模式(即IE5模式);如果是7,则表示IE7仿真模式;如果是8,则表示IE8标准模式。
参考:Doctype作用?严格模式与混杂模式如何区分?它们有何差异?
get和post的区别
- 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。
- get只能传输少量数据(url长度限制),而post可传输的数据容量会更大
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
但是,最底层的区别是:GET产生一个TCP数据包;POST产生两个TCP数据包。
GET和POST是什么?HTTP协议中的两种发送请求的方法。
HTTP是什么?HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。
HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据)
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。
因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑!跳入需谨慎。为什么?
1. GET与POST都有自己的语义,不能随便混用。
2. 据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
3. 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
h5新特性
语义化标签
用最恰当的HTML元素标记的内容。
优点:
- 提升可访问性
- SEO
- 结构清晰,利于维护
通用容器:
- div 块级通用容器
- span 短语内容无语义容器
<title></title>
:简短、描述性、唯一(提升搜索引擎排名)。
搜索引擎会将title作为判断页面主要内容的指标,有效的title应该包含几个与页面内容密切相关的关键字,建议将title核心内容放在前60个字符中。
<hn></hn>
:h1~h6分级标题,用于创建页面信息的层级关系。
对于搜索引擎而言,如果标题与搜索词匹配,这些标题就会被赋予很高的权重,尤其是h1
<header></header>
:页眉通常包括网站标志、主导航、全站链接以及搜索框。
也适合对页面内部一组介绍性或导航性内容进行标记。
<nav></nav>
:标记导航,仅对文档中重要的链接群使用。
Html5规范不推荐对辅助性页脚链接使用nav,除非页脚再次显示顶级全局导航、或者包含招聘信息等重要链接。
<main></main>
:页面主要内容,一个页面只能使用一次。如果是web应用,则包围其主要功能。
<article></article>
:表示文档、页面、应用或一个独立的容器。
article可以嵌套article,只要里面的article与外面的是部分与整体的关系。
<section></section>
:具有相似主图的一组内容,比如网站的主页可以分成介绍、新闻条目、联系信息等条块。
如果只是为了添加样式,请用div
<aside></aside>
:指定附注栏,包括引述、侧栏、指向文章的一组链接、广告、友情链接、相关产品列表等。
如果放在main内,应该与所在内容密切相关。
<footer></footer>
:页脚,只有当父级是body时,才是整个页面的页脚。
<small></small>
:指定细则,输入免责声明、注解、署名、版权。
只适用于短语,不要用来不标记“使用条款”,“隐私政策”等长的法律声明。
不单纯的样式标签(有意义的,对搜索引擎抓取有强调意义 strong > em > cite)
<strong></strong>
:表示内容重要性。
<em></em>
:标记内容着重点(大量用于提升段落文本语义)(斜体)
<cite></cite>
:指明引用或者参考,如图书的标题,歌曲、电影、等的名称,演唱会、音乐会、规范、报纸、或法律文件等。(斜体)
<mark></mark>
:突出显示文本(黄色背景颜色),提醒读者
<figure></figure>
:创建图(默认有40px左右margin)
<figcaption></figcaption>
:figure的标题,必须是figure内嵌的第一个或者最后一个元素。
<blockquoto></blockquoto>
:引述文本,默认新的一行显示。
<time></time>
:标记事件。datetime属性遵循特定格式,如果忽略此属性,文本内容必须是合法的日期或者时间格式。(不再相关的时间用s标签)
<abbr></abbr>
:解释缩写词。使用title属性可提供全称,只在第一次出现时使用就可以了
<dfn></dfn>
:定义术语元素,与定义必须紧挨着,可以在描述列表dl元素中使用。
<address></address>
:作者、相关人士或组织的联系信息(电子邮件地址、指向联系信息页的链接)表示一个具体的地址,字体为斜体,会自动换行
<del></del>
:移除的内容。 <ins></ins>
:添加的内容。
少有的既可以包围块级,又可以包围短语内容的元素。
<code></code>
:标记代码。包含示例代码或者文件名 (< < > >)
<pre></pre>
:预格式化文本。保留文本固有的换行和空格。
<meter></meter>
:表示分数的值或者已知范围的测量结果。如投票结果。
例如:<meter value="0.2" title=”Miles“>20%completed</meter>
<progress></progress>
:完成进度。可通过js动态更新value。
标签新属性
细说data-* dataset(IE11,火狐谷歌)
在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放。通过dataset来获取这些数据。这里的data-前缀就被称为data属性,其可以通过脚本进行定义,也可以应用CSS属性选择器进行样式设置。数量不受限制,在控制和渲染数据的时候提供了非常强大的控制。
一个实例教你如何使用data dataset
例如我们要在一个文字按钮上存放相对应的id
下面是元素应用data属性的一个例子:
<div id="food" data-drink="coffee" data-food="sushi" data-meal="lunch">¥20.12</div>
// 要想获取某个属性的值,可以像下面这样使用dataset对象:
var food = document.getElementById('food');
var typeOfDrink = food.dataset.drink;
classList(火狐谷歌最新,IE10以上)
- obj.classList.add() 添加class类
- obj.classList.remove() 移出class类
- obj.classList.contains() 判断是否包含指定class类
- obj.classList.toggle() 切换class类
- obj.classList.length 获取class类的个数
HTML5新表单
新的input类型
email 类型用于应该包含 e-mail 地址的输入域。在提交表单时,会自动验证 email 域的值。
E-mail: <input type="email" name="user_email" />
url
url 类型用于应该包含 URL 地址的输入域。在提交表单时,会自动验证 url 域的值。
Homepage: <input type="url" name="user_url" />
number
number 类型用于应该包含数值的输入域。您还能够设定对所接受的数字的限定:
Points: <input type="number" name="points" min="1" max="10" />
range
range 类型用于应该包含一定范围内数字值的输入域。range 类型显示为滑动条。您还能够设定对所接受的数字的限定:
<input type="range" name="points" min="1" max="10" />
search
search 类型用于搜索域,比如站点搜索或 Google 搜索。search 域显示为常规的文本域。
新的form属性
autocomplete
autocomplete 属性规定 form 或 input 域应该拥有自动完成功能。
注释:autocomplete 适用于 <form> 标签,以及以下类型的 <input> 标签:text, search, url, telephone, email, password, datepickers, range 以及 color。
<form action="demo_form.asp" method="get" autocomplete="on">
E-mail: <input type="email" name="email" autocomplete="off" />
</form>
novalidate
novalidate 属性规定在提交表单时不应该验证 form 或 input 域。
注释:novalidate 属性适用于 <form> 以及以下类型的 <input> 标签:text, search, url, telephone, email, password, date pickers, range 以及 color.
<form action="demo_form.asp" method="get" novalidate="true">
E-mail: <input type="email" name="user_email" />
<input type="submit" />
</form>
新的input属性
autocomplete
autocomplete 属性规定 form 或 input 域应该拥有自动完成功能。
注释:autocomplete 适用于 <form> 标签,以及以下类型的 <input> 标签:text, search, url, telephone, email, password, datepickers, range 以及 color。
<form action="demo_form.asp" method="get" autocomplete="on">
E-mail: <input type="email" name="email" autocomplete="off" />
</form>
autofocus
autofocus 属性规定在页面加载时,域自动地获得焦点。
注释:autofocus 属性适用于所有 <input> 标签的类型。
User name: <input type="text" name="user_name" autofocus="autofocus" />
form
form 属性规定输入域所属的一个或多个表单。
注释:form 属性适用于所有 <input> 标签的类型。
form 属性必须引用所属表单的 id:
<form action="demo_form.asp" method="get" id="user_form">
First name:<input type="text" name="fname" />
<input type="submit" />
</form>
Last name: <input type="text" name="lname" form="user_form" />
form overrides (formaction, formenctype, formmethod, formnovalidate, formtarget)
表单重写属性(form override attributes)允许您重写 form 元素的某些属性设定。
- 表单重写属性有:
1. formaction - 重写表单的 action 属性
2. formenctype - 重写表单的 enctype 属性
3. formmethod - 重写表单的 method 属性
4. formnovalidate - 重写表单的 novalidate 属性
5. formtarget - 重写表单的 target 属性
注释:表单重写属性适用于以下类型的 <input> 标签:submit 和 image。
<form action="demo_form.asp" method="get" id="user_form">
E-mail: <input type="email" name="userid" /><br />
<input type="submit" value="Submit" />
<br />
<input type="submit" formaction="demo_admin.asp" value="Submit as admin" />
<br />
<input type="submit" formnovalidate="true" value="Submit without validation" />
<br />
</form>
height 和 width 属性
height 和 width 属性规定用于 image 类型的 input 标签的图像高度和宽度。
注释:height 和 width 属性只适用于 image 类型的 <input> 标签。
<input type="image" src="img_submit.gif" width="99" height="99" />
list 属性
list 属性规定输入域的 datalist。datalist 是输入域的选项列表。
注释:list 属性适用于以下类型的 <input> 标签:text, search, url, telephone, email, date pickers, number, range 以及 color。
Webpage: <input type="url" list="url_list" name="link" />
<datalist id="url_list">
<option label="W3Schools" value="http://www.w3school.com.cn" />
<option label="Google" value="http://www.google.com" />
<option label="Microsoft" value="http://www.microsoft.com" />
</datalist>
min、max 和 step 属性
min、max 和 step 属性用于为包含数字或日期的 input 类型规定限定(约束)。
max 属性规定输入域所允许的最大值。
min 属性规定输入域所允许的最小值。
step 属性为输入域规定合法的数字间隔(如果 step="3",则合法的数是 -3,0,3,6 等)。
注释:min、max 和 step 属性适用于以下类型的 <input> 标签:date pickers、number 以及 range。
下面的例子显示一个数字域,该域接受介于 0 到 10 之间的值,且步进为 3(即合法的值为 0、3、6 和 9):
Points: <input type="number" name="points" min="0" max="10" step="3" />
multiple 属性
multiple 属性规定输入域中可选择多个值。
注释:multiple 属性适用于以下类型的 <input> 标签:email 和 file。
Select images: <input type="file" name="img" multiple="multiple" />
novalidate 属性
novalidate 属性规定在提交表单时不应该验证 form 或 input 域。
注释:novalidate 属性适用于 <form> 以及以下类型的 <input> 标签:text, search, url, telephone, email, password, date pickers, range 以及 color.
<form action="demo_form.asp" method="get" novalidate="true">
E-mail: <input type="email" name="user_email" />
<input type="submit" />
</form>
pattern 属性
pattern 属性规定用于验证 input 域的模式(pattern)。
注释:pattern 属性适用于以下类型的 <input> 标签:text, search, url, telephone, email 以及 password。
下面的例子显示了一个只能包含三个字母的文本域(不含数字及特殊字符):
Country code: <input type="text" name="country_code"
pattern="[A-z]{3}" title="Three letter country code" />
placeholder 属性
placeholder 属性提供一种提示(hint),描述输入域所期待的值。
注释:placeholder 属性适用于以下类型的 <input> 标签:text, search, url, telephone, email 以及 password。
提示(hint)会在输入域为空时显示出现,会在输入域获得焦点时消失:
<input type="search" name="user_search" placeholder="Search W3School" />
required 属性
required 属性规定必须在提交之前填写输入域(不能为空)。
注释:required 属性适用于以下类型的 <input> 标签:text, search, url, telephone, email, password, date pickers, number, checkbox, radio 以及 file。
Name: <input type="text" name="usr_name" required="required" />
音频(audio)和视频(video)
支持的格式和写法
音频元素支持的3种格式:Ogg MP3 Wav
<audio controls>
<source src="horse.ogg" type="audio/ogg">
<source src="horse.mp3" type="audio/mpeg">
您的浏览器不支持 audio 元素。
</audio>
视频元素支持三种视频格式:MP4、WebM、Ogg。
<video width="320" height="240" controls>
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
您的浏览器不支持 video 标签。
</video>
标签属性
- 音视频:autoplay、controls、loop、muted、preload、src
-
视频:autoplay、controls、loop、muted、width、height、poster、preload、src
方法
- load():重新加载音频/视频元素
- play():开始播放音频/视频
- pause():暂停当前播放的音频/视频
事件
-
durationchange:当音频/视频的时长已更改时
-
ended:当目前的播放列表已结束时
-
pause:当音频/视频已暂停时
-
play:当音频/视频已开始或不再暂停时
-
ratechange:当音频/视频的播放速度已更改时
-
timeupdate:当目前的播放位置已更改时
- volumechange:当音量已更改时
事件属性
-
只读属性
- duration:返回当前的总时长
- currentSrc:返回当前URL
- ended:返回是否已结束
- paused:返回是否已暂停
-
获取并可修改的属性:
- autoplay:设置或返回是否自动播放
- controls:设置或返回是否显示控件(比如播放/暂停等)
- loop:设置或返回是否是循环播放
- muted:设置或返回是否静音
- currentTime:设置或返回当前播放位置(以秒计)
- volume:设置或返回音量(规定音频/视频的当前音量。必须是介于 0.0 与 1.0 之间的数字。)
1.0 是最高音量(默认);0.5 是一半音量 (50%); 0.0 是静音; - playbackRate:设置或返回播放速度
css3新特性
过渡
transition: CSS属性,花费时间,效果曲线(默认ease),延迟时间(默认0)
动画
animation:动画名称,一个周期花费时间,运动曲线(默认ease),动画延迟(默认0),播放次数(默认1),是否反向播放动画(默认normal),是否暂停动画(默认running)
@keyframes logo1 {
0%{
transform:rotate(180deg);
opacity: 0;
}
100%{
transform:rotate(0deg);
opacity: 1;
}
}
.logo-box .logo1{
animation: logo1 1s ease-in 2s;
animation-fill-mode:backwards;
}
形状转换
transform:适用于2D或3D转换的元素
transform-origin:转换元素的位置(围绕那个点进行转换)。默认(x,y,z):(50%,50%,0)
transform:rotate(30deg);
transform:translate(30px,30px);
transform:scale(.8);
transform: skew(10deg,10deg);
选择器
阴影
box-shadow: 水平阴影的位置 垂直阴影的位置 模糊距离 阴影的大小 阴影的颜色 阴影开始方向(默认是从里往外,设置inset就是从外往里);
边框
边框图片
border-image: 图片url 图像边界向内偏移 图像边界的宽度(默认为边框的宽度) 用于指定在边框外部绘制偏移的量(默认0) 铺满方式--重复(repeat)、拉伸(stretch)或铺满(round)(默认:拉伸(stretch));
.demo {
border: 15px solid transparent;
padding: 15px;
border-image: url(border.png);
border-image-slice: 30;
border-image-repeat: round;
border-image-outset: 0;
}
边框圆角
border-radius: n1,n2,n3,n4;
border-radius: n1,n2,n3,n4/n1,n2,n3,n4;
/*n1-n4四个值的顺序是:左上角,右上角,右下角,左下角。*/
background-size
文字
换行
语法:word-break: normal|break-all|keep-all;
语法:word-wrap: normal|break-word;
超出省略号这个,主要讲text-overflow
这个属性,我直接讲实例的原因是text-overflow
的三个写法,clip|ellipsis|string
。clip
这个方式处理不美观,不优雅。string
只在火狐兼容。
超出省略号
div
{
width:200px;
border:1px solid #000000;
overflow:hidden;
white-space:nowrap;
text-overflow:ellipsis;
}
多行超出省略号
超出省略号。这个对于大家来说,不难!但是以前如果是多行超出省略号,就只能用js模拟!现在css3提供了多行省略号的方法!遗憾就是这个暂时只支持webkit浏览器!
div
{
width:400px;
margin:0 auto;
overflow : hidden;
border:1px solid #ccc;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
颜色
rgba
一个是rgba(rgb为颜色值,a为透明度)
hsla
h:色相”,“s:饱和度”,“l:亮度”,“a:透明度”
渐变
弹性布局——flex
盒模型定义
box-sizing这个属性,网上说法是:属性允许您以特定的方式定义匹配某个区域的特定元素。
- box-sizing:border-box的时候,边框和padding包含在元素的宽高之内!
- box-sizing:content-box的时候,边框和padding不包含在元素的宽高之内!
媒体查询
rem布局——rem是根em(root em)的缩写。rem是和根元素关联的,不依赖当前元素。不管你在文档中的什么地方使用这个单位,1.2rem的计算值是相等的,等于1.2倍的根元素的字号大小。
<!--主要I是强制让文档的宽度与设备宽度保持1:1,最大宽度1.0,禁止屏幕缩放。-->
<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
@media screen and (max-width: 960px) {
body {
background-color: darkgoldenrod;
}
}
伪类与伪元素
css 引入伪类和伪元素概念是为了格式化文档树以外的信息。也就是说,伪类和伪元素是用来修饰不在文档树中的部分
对伪类和伪元素进行解释:
伪类用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的元素时,我们可以通过:hover 来描述这个元素的状态。虽然它和普通的 css 类相似,可以为已有的元素添加样式,但是它只有处于 dom 树无法描述的状态下才能为元素添加样式,所以将其称为伪类。
伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过:before 来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。
伪类与伪元素的区别
伪类:
伪类:li:first-child {
color: orange
}
伪元素:p:first-letter {
font-size: 5em;
}
伪类的操作对象是文档树中已有的元素,而伪元素则创建了一个文档数外的元素。因此,伪类与伪元素的区别在于:有没有创建一个文档树之外的元素。
伪元素是使用单冒号还是双冒号?
CSS3 规范中的要求使用双冒号 (::) 表示伪元素,以此来区分伪元素和伪类,比如::before 和::after 等伪元素使用双冒号 (::),:hover 和:active 等伪类使用单冒号 (:)。除了一些低于 IE8 版本的浏览器外,大部分浏览器都支持伪元素的双冒号 (::) 表示方法。
参考:伪类和伪元素
mongodb
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
为什么要用MongoDB
(1)MongoDB提出的是文档、集合(多条记录)的概念,使用BSON(类JSON)作为其数据模型结构,其结构是面向对象的而不是二维表,存储一个用户在MongoDB中是这样子的。
{
username:'123',
password:'123'
}
使用这样的数据模型,使得MongoDB能在生产环境中提供高读写的能力,吞吐量较于mysql等SQL数据库大大增强。
(2)易伸缩,自动故障转移。易伸缩指的是提供了分片能力,能对数据集进行分片,数据的存储压力分摊给多台服务器。自动故障转移是副本集的概念,MongoDB能检测主节点是否存活,当失活时能自动提升从节点为主节点,达到故障转移。
(3)数据模型因为是面向对象的,所以可以表示丰富的、有层级的数据结构,比如博客系统中能把“评论”直接怼到“文章“的文档中,而不必像myqsl一样创建三张表来描述这样的关系。
支持嵌套域的查询
查询可以深入到嵌套的对象和数组中,如果下面的对象被插入到users集合:
{
"username" : "bob",
"address" : {
"street" : "123 Main Street",
"city" : "Springfield",
"state" : "NY"
}
}
我们可以这样查询嵌套在里层的域
db.users.find({“address.state”:"NY”})
数组元素则可以被这样查询:
> db.food.insert({"fruit" : ["peach", "plum", "pear"]})
> db.food.find({"fruit" : "pear"})
几个shell实操
因为本篇文章不是API手册,所有这里对shell的使用也是基础的介绍什么功能可以用什么语句,主要是为了展示使用MongoDB shell的方便性,如果需要知道具体的MongoDB shell语法可以查阅官方文档。
1、切换数据库
use dba
创建数据库并不是必须的操作,数据库与集合只有在第一次插入文档时才会被创建,与对数据的动态处理方式是一致的。简化并加速开发过程,而且有利于动态分配命名空间。如果担心数据库或集合被意外创建,可以开启严格模式
2、插入语法
1db.users.insert({username:"smith"})
2db.users.save({username:"smith"})
3、查找语法
1 db.users.find()
2 db.users.count()
4、更新语法
1 db.users.update({username:"smith"},{$set:{country:"Canada"}})
2 //把用户名为smith的用户的国家改成Canada
3
4 db.users.update({username:"smith"},{$unset:{country:1}})
5 //把用户名为smith的用户的国家字段给移除
6
7 db.users.update({username:"jones"},{$set:{favorites:{movies:["casablance","rocky"]}}})
8 //这里主要体现多值修改,在favorties字段中添加多个值
9
10 db.users.update({"favorites.movies":"casablance"},{$addToSet:{favorites.movies:"the maltese"}},false,true)
11 //多项更新
5、删除语法
1db.foo.remove() //删除所有数据
2db.foo.remove({favorties.cities:"cheyene"}) //根据条件进行删除
3db.drop() //删除整个集合
6、索引相关语法
1db.numbers.ensureIndex({num:1})
2//创建一个升序索引
3db.numbers.getIndexes()
4//获取全部索引
7、基本管理语法
1 show dbs
2 //查询所有数据库
3 show collections
4 //显示所有表
5 db.stats()
6 //显示数据库状态信息
7 db.numbers.stats()
8 //显示集合表状态信息
9 db,shutdownServer()
10 //停止数据库
11 db.help()
12 //获取数据库操作命令
13 db.foo.help()
14 //获取表操作命令
15 tab 键 //能自动帮我们补全命令
Mongobd的重要模块–GridFS
GridFS是Mongo的一个子模块,使用GridFS可以基于MongoDB来持久存储文件。并且支持分布式应用(文件分布存储和读取)。作为MongoDB中二进制数据存储在数据库中的解决方案,通常用来处理大文件,对于MongoDB的BSON格式的数据(文档)存储有尺寸限制,最大为16M。但是在实际系统开发中,上传的图片或者文件可能尺寸会很大,此时我们可以借用GridFS来辅助管理这些文件。
GridFS存储原理
GridFS使用两个集合(collection)存储文件。一个集合是chunks, 用于存储文件内容的二进制数据;一个集合是files,用于存储文件的元数据。
GridFS会将两个集合放在一个普通的buket中,并且这两个集合使用buket的名字作为前缀。MongoDB的GridFs默认使用fs命名的buket存放两个文件集合。因此存储文件的两个集合分别会命名为集合fs.files ,集合fs.chunks。
当把一个文件存储到GridFS时,如果文件大于chunksize (每个chunk块大小为256KB),会先将文件按照chunk的大小分割成多个chunk块,最终将chunk块的信息存储在fs.chunks集合的多个文档中。然后将文件信息存储在fs.files集合的唯一一份文档中。其中fs.chunks集合中多个文档中的file_id字段对应fs.files集中文档”_id”字段。
读文件时,先根据查询条件在files集合中找到对应的文档,同时得到“_id”字段,再根据“_id”在chunks集合中查询所有“files_id”等于“_id”的文档。最后根据“n”字段顺序读取chunk的“data”字段数据,还原文件。
深入浅出mongoose
mongoose是nodeJS提供连接 mongodb的一个库,类似于jquery和js的关系,对mongodb一些原生方法进行了封装以及优化。简单的说,Mongoose就是对node环境中MongoDB数据库操作的封装,一个对象模型工具,将数据库中的数据转换为JavaScript对象以供我们在应用中使用。
小程序
小程序提供了一个简单、高效的应用开发框架和丰富的组件及API,帮助开发者在微信中开发具有原生 APP 体验的服务。
整个小程序框架系统分为两部分:逻辑层(App Service)和 视图层(View)。小程序提供了自己的视图层描述语言 WXML
和 WXSS
,以及基于 JavaScript
的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,让开发者能够专注于数据与逻辑。
响应的数据绑定
框架的核心是一个响应的数据绑定系统,可以让数据与视图非常简单地保持同步。当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。(声明式渲染{{}})
页面管理
框架 管理了整个小程序的页面路由,可以做到页面间的无缝切换,并给以页面完整的生命周期。开发者需要做的只是将页面的数据、方法、生命周期函数注册到 框架 中,其他的一切复杂的操作都交由 框架 处理。
基础组件
框架 提供了一套基础的组件,这些组件自带微信风格的样式以及特殊的逻辑,开发者可以通过组合基础组件,创建出强大的微信小程序 。
丰富的 API
框架 提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。
云开发
开发者可以使用云开发开发微信小程序、小游戏,无需搭建服务器,即可使用云端能力。
云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。
云开发提供了几大基础能力支持:
能力 | 作用 | 说明 |
---|---|---|
云函数 | 无需自建服务器 | 在云端运行的代码,微信私有协议天然鉴权,开发者只需编写自身业务逻辑代码 |
数据库 | 无需自建数据库 | 一个既可在小程序前端操作,也能在云函数中读写的 JSON 数据库 |
存储 | 无需自建存储和 CDN | 在小程序前端直接上传/下载云端文件,在云开发控制台可视化管理 |
云调用 | 原生微信服务集成 | 基于云函数免鉴权使用小程序开放接口的能力,包括服务端调用、获取开放数据等能力 |
js 深拷贝 vs 浅拷贝
堆和栈的区别
其实深拷贝和浅拷贝的主要区别就是其在内存中的存储类型不同。
堆和栈都是内存中划分出来用来存储的区域。
栈(stack)为自动分配的内存空间,它由系统自动释放;而堆(heap)则是动态分配的内存,大小不定也不会自动释放。
对象
var obj = {a: 1, b: 2, c: { a: 3 },d: [4, 5]}
var obj1 = obj
var obj3 = {...obj}
var obj4 = Object.assign({},obj)
var obj2 = JSON.parse(JSON.stringify(obj))//深拷贝常用方法
obj.a = 999
obj.c.a = -999
obj.d[0] = 123
console.log(obj1) //{a: 999, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj3) //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj4) //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj2) //{a: 1, b: 2, c: { a: 3 },d: [4, 5]}
数组
var arr = [1, 2, 3, [4, 5], {a: 6, b: 7}]
var arr2 = arr
var arr3 = [...arr]
var arr4 = Object.assign([],arr)
var arr1 = JSON.parse(JSON.stringify(arr))//深拷贝常用方法
console.log(arr === arr1) //false
console.log(arr === arr2) //true
console.log(arr === arr3) //false
console.log(arr === arr4) //false
arr[0]= 999
arr[3][0]= -999
arr[4].a = 123
console.log(arr1) //[1, 2, 3, [4, 5], {a: 6, b: 7}]
console.log(arr2) //[999, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr3) //[1, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr4) //[1, 2, 3, [-999, 5], {a: 123, b: 7}]
深拷贝:
思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可。我们直接来看一下 Zepto 中深拷贝的代码:
// 内部方法:用户合并一个或多个对象到第一个对象
// 参数:
// target 目标对象 对象都合并到target里
// source 合并对象
// deep 是否执行深度合并
function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
// source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
// source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
// 执行递归
extend(target[key], source[key], deep)
}
// 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
else if (source[key] !== undefined) target[key] = source[key]
}
// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1);
//第一个参数为boolean值时,表示是否深度合并
if (typeof target == 'boolean') {
deep = target;
//target取第二个参数
target = args.shift()
}
// 遍历后面的参数,都合并到target上
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}
在 Zepto 中的 $.extend
方法判断的第一个参数传入的是一个布尔值,判断是否进行深拷贝。
在 $.extend
方法内部,只有一个形参 target,这个设计你真的很巧妙。
因为形参只有一个,所以 target 就是传入的第一个参数的值,并在函数内部设置一个变量 args 来接收去除第一个参数的其余参数,如果该值是一个布尔类型的值的话,说明要启用深拷贝,就将 deep 设置为 true,并将 target 赋值为 args 的第一个值(也就是真正的 target)。如果该值不是一个布尔类型的话,那么传入的第一个值仍为 target 不需要进行处理,只需要遍历使用 extend 方法就可以。
浏览器的兼容性
所谓的浏览器兼容性问题,是指因为不同的浏览器对同一段代码有不同的解析,造成页面显示效果不统一的情况。
- 渐进增强(progressive enhancement): 针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验
- 优雅降级`(graceful degradation): 一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
Web标准以及W3C
Web标准是由万维网联盟(W3C)制订的,WEB标准的产生,网页内容能被更多的用户所访问到,文件下载和页面显示速度更快,所有页面都能提供适合打印的版本,网页开发人员开发更快捷,代码更易于维护,提高了搜索引擎的精确性,提高了网站的易用性。
我们需要注意的代码标准有:
抛弃声明:以后我们将抛弃font标签,新的页面中不应该再出现如<font color=”red”></font>,已经存在的老的页面也应该在修改时尽量替代,替代方法:<span
class=”red_tex”></span>。
一个标准XHTML头信息格式如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="gb2312">
<head>
<meta charset="utf-8" />
<title>W3Cschool - 学技术查资料,从w3cschool开始!</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="keywords" content="w3cschool,w3cschool在线教程,技术文档,编程入门教程,W3Cschool,W3C,HTML,HTML5,CSS,Javascript,jQuery,Bootstrap,PHP,Java,Sql" />
<meta name="description" content="w3cschool是一个专业的编程入门学习及技术文档查询网站,提供包括HTML,CSS,Javascript,jQuery,C,PHP,Java,Python,Sql,Mysql等编程语言和开源技术的在线教程及使用手册,是类国外W3Cschool的W3C学习社区及菜鸟编程平台。" />
</head>
1、什么是DOCTYPE
DOCTYPE是document type(文档类型)的简写,用来说明你用的XHTML或者HTML是什么版本。其中的DTD(例如xhtml1-transitional.dtd)叫文档类型定义,里面包含了文档的规则,浏览器就根据你定义的DTD来解释你页面的标识,并展现出来。要建立符合标准的网页,DOCTYPE声明是必不可少的关键组成部分;除非你的XHTML确定了一个正确的DOCTYPE,否则你的标识和CSS都不会生效。
XHTML 1.0 提供了三种DTD声明可供选择:
i) 过渡的(Transitional):要求非常宽松的DTD,它允许你继续使用HTML4.01的标识(但是要符合xhtml的写法)。
完整代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
ii) 严格的(Strict):要求严格的DTD,你不能使用任何表现层的标识和属性,例如<br>。
完整代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
iii) 框架的(Frameset):专门针对框架页面设计使用的DTD,如果你的页面中包含有框架,需要采用这种DTD。
完整代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
注:DOCTYPE声明必须放在每一个XHTML文档最顶部,在所有代码和标识之上。
html5 : 没有DTD
<!DOCTYPE html>
2、名字空间 namespace
<html xmlns="http://www.w3.org/1999/xhtml" lang="gb2312">
通常我们HTML4.0的代码只是<html>,这里的"xmlns"是什么呢?
这个“xmlns”是XHTML namespace的缩写,叫做“名字空间”声明。XHTML是HTML向XML过渡的标识语言,它需要符合XML文档规则,因此也需要定义名字空间。又因为XHTML1.0不能自定义标识,所以它的名字空间都相同,就是"http://www.w3.org/1999/xhtml"。目前阶段我们只要照抄代码就可以了。
3、定义语言编码
<meta http-equiv=“Content-Type” content=“text/html; charset=gb2312” />
为了被浏览器正确解释和通过W3C代码校验,所有的XHTML文档都必须声明它们所使用的编码语言,我们一般使用gb2312(简体中文),制作多国语言页面也有可能用Unicode、ISO-8859-1等,根据你的需要定义。
注:如果忘记了定义语言编码,可能就会出现,你在DW(dreamweaver)做完一个页面,第二次打开时所有的中文变成了乱码。
<meta charset="utf-8">
4、Javascript定义
Js必须要用<script language="javascript" type="text/javascript">来开头定义,而不是原来的<script language=javascript>或干脆直接<script>,并且需要加个注释符<!-- -->,以保证不在不支持js的浏览器上直接显示出代码来。
例如:
<script language="javascript" type="text/javascript">
//<![CDATA[
function show_layout(selObj){
var n = selObj.options[selObj.selectedIndex].value;
document.getElementById('stylesheet').href = n;
}
//]]>
</script>
注:具体参考js规范。
5、CSS定义
CSS必须要用<style type=“text/css”>开头来定义,而不是原来的直接<style>,也不建议直接写在内容代码里如:<div style=”padding-left:20px;”></div>,并需要加个注释符<!-- -->
例如:
<style type="text/css" media="screen">
<!--
body {margin:0px;padding:0px;font-size:12px;text-align:center}
-->
</style>
为保证各浏览器的兼容性,在写CSS时请都写上数量单位,例如:错误:.space_10{padding-left:10} 正确:.space_10 {padding-left:10px}
6、不要在注释内容中使用“--”
“--”只能发生在XHTML注释的开头和结束,也就是说,在内容中它们不再有效。
例如下面的代码是无效的:<!--这里是注释-----------这里是注释-->
正确的应用等号或者空格替换内部的虚线。<!--这里是注释============这里是注释-->
7、所有标签的元素和属性的名字都必须使用小写
与HTML不一样,XHTML对大小写是敏感的,<title>和<TITLE>是不同的标签。XHTML要求所有的标签和属性的名字都必须使用小写。例如:<BODY>必须写成<body>。大小写夹杂也是不被认可的,通常dreamweaver自动生成的属性名字"onMouseOver"也必须修改成"onmouseover"。
8、所有的属性必须用引号""括起来
在HTML中,你可以不需要给属性值加引号,但是在XHTML中,它们必须被加引号。
例如:<height=80>必须修改为:<height="80">。
特殊情况,你需要在属性值里使用双引号,你可以用",单引号可以使用',例如:<alt="say'hello'">
9、把所有<和&特殊符号用编码表示
任何小于号(<),不是标签的一部分,都必须被编码为 <
任何大于号(>),不是标签的一部分,都必须被编码为 >
任何与号(&),不是实体的一部分的,都必须被编码为 &
错误:
http://club.china.alibaba.com/forum/thread/search_forum.html?action=SearchForum&doSearchForum=true&main=1&catcount=10&keywords=mp3
正确:
http://club.china.alibaba.com/forum/thread/search_forum.html?action=SearchForum&doSearchForum=true&main=1&catcount=10&keywords=mp3
10、给所有属性赋一个值
XHTML规定所有属性都必须有一个值,没有值的就重复本身。例如:
<td nowrap><input type="checkbox" name="shirt" value="medium" checked>必须修改为:
<td nowrap="nowrap"><input type="checkbox" name="shirt" value="medium" checked="checked" />
11、所有的标记都必须要有一个相应的结束标记
以前在HTML中,你可以打开许多标签,例如<p>和<li>而不一定写对应的</p>和</li>来关闭它们。但在XHTML中这是不合法的。XHTML要求有严谨的结构,所有标签必须关闭。如果是单独不成对的标签,在标签最后加一个"/"来关闭它。
例如:<br />
<img height="80" alt="网页" title=”网页” src="logo.gif" width="200" />
特殊结束标记
错误:
Document.write("<td width=\"300\"><a href=\"1.html\">ok</a></td>");
正确:
Document.write("<td width=\"300\"><a href=\"1.html\">ok<\/a><\/td>");
在js中,原已结束的标签需要再转义再结束。
12、所有的标记都必须合理嵌套
同样因为XHTML要求有严谨的结构,因此所有的嵌套都必须按顺序,以前我们这样写的代码:
<p><b></p></b>必须修改为:<p><b></b></p>
就是说,一层一层的嵌套必须是严格对称。
错误:
<table><tr><form><td></td></form></tr></table>
正确:
<form><table><tr><td></td></tr></table></form>
13、图片添加有意义的alt属性
例如:<img src="logo.gif" width="100" height="100" align="middle" boder="0" alt="w3cschool" />
尽可能的让作为内容的图片都带有属于自己的alt属性。
同理:添加文字链接的title属性。
<a href="#" target="_blank" title="新闻新闻新闻新闻">新闻新闻…</a>,在一些限定字数的内容展示尤为重要,帮助显示不完成的内容显示完整,而不用考虑页面会因此而撑大。
14、在form表单中增加lable,以增加用户友好度
例如:
<form action="http://somesite.com/prog/adduser" method="post">
<label for="firstname">first name: </label>
<input type="text" id="firstname" />
<label for="lastname">last name: </label>
<input type="text" id="lastname" />
</form>
主流浏览器的内核
浏览器内核主要指的是浏览器的渲染引擎,渲染引擎决定了浏览器如何加载和显示网页的内容以及信息。我们主要用于测试的浏览器都有:IE、Chrome、Firefox、Safari、Opera、360浏览器。
- IE:trident内核(IE内核)
- Firefox:geoko内核,Mozilla自己开发的一套开放源代码、以C++编写的渲染引擎。
- Safari:webkit内核,开源的浏览器引擎,源自于Linux平台上的一个引擎,经过Apple公司的修改可以支持Mac与Windows平台。
- Chrome:Blink内核,Google和Opera Software共同研发。
- Opera:以前是presto内核,现在改为Blink内核。
- 360浏览器: 兼容模式(trident内核)、极速模式(Blink内核)。
标准模式(Standards Mode)和怪异模式 (Quirks Mode)
在Netscape Navigator和Microsoft Internet Explorer为数不多的浏览器盛行时,他们对网页有不同的实现方式,那个时候的网页都是针对这两个浏览器写的。随着各种浏览器的兴起,加上Web标准的制定,现在的浏览器不能继续使用以前的页面了,所以浏览器引入了标准模式和怪异模式来解决这一问题。
标准模式就是浏览器按照Web标准来渲染页面;为了解决浏览器还是能使用以前写的页面,所以怪异模式就产生了。怪异模式在不同的浏览器显示都是不一样的,因为他们都是按照自己的方式来渲染页面。
我们知道了标准模式和怪异模式,可是浏览器是怎么选择模式来渲染页面的呢?我们经常在页面的开头看到<!DOCTYPE>声明,这是告诉浏览器选择哪个版本的HTML,对于渲染模式的选择,浏览器是根据DTD的声明。如果网页中有DTD标准文档的声明,那浏览器会按照标准模式来渲染网页;如果网页没有DTD声明或者HTML4以下的DTD声明,那浏览器就按照自己的方式渲染页面,页面进入怪异模式。
CSS盒模型
如果想要了解详细解说,请移至:http://www.cnblogs.com/ylliap/p/6119740.html
盒模型指定元素如何显示,理解它,对我们的布局有很大的帮助。盒模型由内容(content)、内边距(padding)、边框(border)、外边距(margin)组成。
盒模型有两种:IE盒模型(图1)、标准的W3C盒模型(图2)。从图1和图2就可以看出,IE盒模型的width包括了border、padding、content,而W3C盒模型的width仅限于content。
在CSS3的属性中,box-sizing可以设置盒模型类型,默认值为content-box,content-box表示W3C盒模型,border-box表示IE盒模型。
图1. IE盒模型 图2. W3C盒模型
重置浏览器样式
不同的浏览器对标签的默认样式值不同,所以我们需要有一套样式表来重置浏览器样式,避免我们写的网页在各个浏览器中造成的显示差异。
可以使用Normalize.css只是一个很小的css文件,但它在默认的HTML元素样式上提供了跨浏览器的高度一致性。相比于传统的css reset,Normalize.css是一种现代的,为HTML5准备的优质替代方案。
Normalize.css只是一个很小的css文件,但它在默认的HTML元素样式上提供了跨浏览器的高度一致性。相比于传统的css reset,Normalize.css是一种现代的,为HTML5准备的优质替代方案。
表现与数据分离
也可以说是界面与数据分离,要体现在代码上,操作数据的代码和操作界面的代码,要分开写。
优势:当页面需求发生改变,只需要改写界面的代码,并且修改的代码不能影响到操作数据访问的代码。
如MVVM框架,就是视图与数据分离,具体为:数据模型以及业务逻辑为model,保存具体数据以及操作数据行为(增删改查更新等)的方法。操作DOM视图的方法分为view
参考:【web前端面试题整理07】我不理解表现与数据分离。。。、
HTML语义化
HTML语义化就是页面去掉样式或者加载失败的时候能够让页面呈现出清晰的结构。HTML5新增了好多标签,例如:header、footer、nav、menu、section、article等等,我们单单从字面上理解,就知道标签的含义。在写页面的时候,我们可以直接引用这些标签,不需要再用没有任何含义的div标签了,对于机器可以识别,对于开发人员,很容易明白,这就是HTML语义化。
HTML语义化的好处有:有利于SEO优化,利于被搜索引擎收录,更便于搜索引擎的爬虫程序来识别;便于项目的开发及维护,使HTML代码更具有可读性,便于其他设备解析。
CSS选择器优先级
掌握选择器优先级,再也不用!important来到处打补丁。
CSS的基本选择器:
- id选择器(用DOM的id申明)
- 类选择器(用一个样式类名申明)和伪类
- 属性选择器(用DOM的属性申明)
- 标签选择器(用HTML标签申明)和伪元素
还有一种添加样式可以在标签上直接添加,属于行内样式。
在这里,我们只做简单说明,以上面几种选择器来排序:行内元素 < id选择器 < 类选择器/属性选择器 < 标签选择器。
通配选择符(universal selector)(*
)关系选择符(combinators)(+
, >
, ~
, '
', ||
)和 否定伪类(negation pseudo-class)(:not()
)对优先级没有影响。(但是,在 :not()
内部声明的选择器会影响优先级)。
清除浮动
如果容器中有浮动的元素,容器的高度不能自动延长适应内容的高度,使得内容溢出到容器外而影响页面布局,为了避免这种情况的发生,我们需要用CSS来清除元素的浮动。
一般常用的方法有三种:
A. 在浮动元素后面添加带有clear属性的空元素
<div>
<div style="float: left;">left</div>
<div style="float: right;">right</div>
<div style="clear: both;"></div>
</div>
B. 给容器添加属性overflow: hidden或者overflow: auto,在IE6中还需触发haslayout,所以还需添加zoom: 1。
<div style="overflow: auto;*zoom: 1;">
<div style="float: left;">left</div>
<div style="float: right;">right</div>
</div>
C. 使用:after伪元素
<style>
.clearfix {*zoom: 1;}
.clearfix:after {content: ".";display: block;height: 0;visibility: hidden;clear: both;}
</style>
<div class="clearfix">
<div style="float: left;">left</div>
<div style="float: right;">right</div>
</div>
参与:各大浏览器兼容性问题总结
响应式布局和自适应布局详解
响应式布局等于流动网格布局,而自适应布局等于使用固定分割点来进行布局。
自适应布局给了你更多设计的空间,因为你只用考虑几种不同的状态。而在响应式布局中你却得考虑上百种不同的状态。虽然绝大部分状态差异较小,但仍然也算做差异。它使得把握设计最终效果变得更难,同样让响应式布局更加的难以测试和预测。
响应式布局概念
Responsive design,意在实现不同屏幕分辨率的终端上浏览网页的不同展示方式。通过响应式设计能使网站在手机和平板电脑上有更好的浏览阅读体验。
响应式设计的步骤
1. 设置 Meta 标签
多数移动浏览器将HTML页面放大为宽的视图(viewport)以符合屏幕分辨率。你可以使用视图的meta标签来进行重置。下面的视图标签告诉浏览器,使用设备的宽度作为视图宽度并禁止初始的缩放。在<head>
标签里加入这个meta标签。
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
[1](user-scalable = no 属性能够解决 iPad 切换横屏之后触摸才能回到具体尺寸的问题。 )
3. 通过媒介查询来设置样式 Media Queries
原文链接:http://caibaojian.com/356.html
Media Queries 是响应式设计的核心。·
它根据条件告诉浏览器如何为指定视图宽度渲染页面。假如一个终端的分辨率小于 980px,那么可以这样写:
//code from http://caibaojian.com/356.html
@media screen and (max-width: 980px) {
#head { … }
#content { … }
#footer { … }
}
这里的样式就会覆盖上面已经定义好的样式。
原文链接:http://caibaojian.com/356.html
4. 设置多种试图宽度
假如我们要设定兼容 iPad 和 iphone 的视图,那么可以这样设置:·
//code from http://caibaojian.com/356.html
/** iPad **/
@media only screen and (min-width: 768px) and (max-width: 1024px) {}
/** iPhone **/
@media only screen and (min-width: 320px) and (max-width: 767px) {}
一些注意的
例如这样:
#head { width: 100% }
#content { width: 50%; }
- 简单的解决方法可以使用百分比,但这样不友好,会放大或者缩小图片。那么可以尝试给图片指定的最大宽度为百分比。假如图片超过了,就缩小。假如图片小了,就原尺寸输出。
img { width: auto; max-width: 100%; }
- 用
::before
和::after
伪元素 +content 属性来动态显示一些内容或者做其它很酷的事情,在 css3 中,任何元素都可以使用 content 属性了,这个方法就是结合 css3 的 attr 属性和 HTML 自定义属性的功能: HTML 结构:
<img src="image.jpg"
data-src-600px="image-600px.jpg"
data-src-800px="image-800px.jpg"
alt="">
CSS 控制:
@media (min-device-width:600px) {
img[data-src-600px] {
content: attr(data-src-600px, url);
}
}
@media (min-device-width:800px) {
img[data-src-800px] {
content: attr(data-src-800px, url);
}
}
例如 pre
,iframe
,video
等,都需要和img
一样控制好宽度。对于table
,建议不要增加 padding 属性,低分辨率下使用内容居中:
table th, table td { padding: 0 0; text-align: center; }
以上内容和代码来自:掌心,感谢,欢迎查看我之前做过的响应式设计:查看演示打造布局结构
我们可以监测页面布局随着不同的浏览环境而产生的变化,如果它们变的过窄过短或是过宽过长,则通过一个子级样式表来继承主样式表的设定,并专门针对某些布局结构进行样式覆写。我们来看下代码示例:
/* Default styles that will carry to the child style sheet */
html,body{
background...
font...
color...
}
h1,h2,h3{}
p, blockquote, pre, code, ol, ul{}
/* Structural elements */
#wrapper{
width: 80%;
margin: 0 auto;
background: #fff;
padding: 20px;
}
#content{
width: 54%;
float: left;
margin-right: 3%;
}
#sidebar-left{
width: 20%;
float: left;
margin-right: 3%;
}
#sidebar-right{
width: 20%;
float: left;
}
下面的代码可以放在子级样式表Mobile.css中,专门针对移动设备进行样式覆写:
#wrapper{
width: 90%;
}
#content{
width: 100%;
}
#sidebar-left{
width: 100%;
clear: both;
/* Additional styling for our new layout */
border-top: 1px solid #ccc;
margin-top: 20px;
}
#sidebar-right{
width: 100%;
clear: both;
/* Additional styling for our new layout */
border-top: 1px solid #ccc;
margin-top: 20px;
}
大致的视觉效果如下图所示:
横屏与竖屏的区别
图中上半部分是大屏幕设备所显示的完整页面,下面的则是该页面在小屏幕设备的呈现方式。页面HTML代码如下:
Ethan的文章中的“Meet the media query”部分有更多的范例及解释。更有效率的做法是,将多个media queries整合在一个样式表文件中
/* Smartphones (portrait and landscape) ----------- */
@media only screen
and (min-device-width : 320px)
and (max-device-width : 480px) {
/* Styles */
}
/* Smartphones (landscape) ----------- */
@media only screen
and (min-width : 321px) {
/* Styles */
}
/* Smartphones (portrait) ----------- */
@media only screen
and (max-width : 320px) {
/* Styles */
}
译文:流动网格
rem是如何实现自适应布局的?
rem是什么?
rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单位。看到rem大家一定会想起em单位,em(font size of the element)是指相对于父元素的字体大小的单位。它们之间其实很相似,只不过一个计算的规则是依赖根元素一个是依赖父元素计算。·
为什么web app要使用rem?
这里我特别强调web app,web page就不能使用rem吗,其实也当然可以,不过出于兼容性的考虑在web app下使用更加能突显这个单位的价值和能力,接下来我们来看看目前一些企业的web app是怎么做屏幕适配的。·
1、实现强大的屏幕适配布局:
最近iphone6一下出了两款尺寸的手机,导致的移动端的屏幕种类更加的混乱,记得一两年前做web app有一种做法是以320宽度为标准去做适配,超过320的大小还是以320的规格去展示,这种实现方式以淘宝web app为代表作,但是近期手机淘宝首页进行了改版,采用了rem这个单位,首页以内依旧是和以前一样各种混乱,有定死宽度的页面,也有那种流式布局的页面。
我们现在在切页面布局的使用常用的单位是px,这是一个绝对单位,web app的屏幕适配有很多中做法,例如:流式布局、限死宽度,还有就是通过响应式来做,但是这些方案都不是最佳的解决方法。
例如流式布局的解决方案有不少弊端,它虽然可以让各种屏幕都适配,但是显示的效果极其的不好,因为只有几个尺寸的手机能够完美的显示出视觉设计师和交互最想要的效果,但是目前行业里用流式布局切web app的公司还是挺多的
上面的网站都是采用的流式布局的技术实现的,他们在页面布局的时候都是通过百分比来定义宽度,但是高度大都是用px来固定住,所以在大屏幕的手机下显示效果会变成有些页面元素宽度被拉的很长,但是高度还是和原来一样,实际显示非常的不协调,这就是流式布局的最致命的缺点,往往只有几个尺寸的手机下看到的效果是令人满意的,其实很多视觉设计师应该无法接受这种效果,因为他们的设计图在大屏幕手机下看到的效果相当于是被横向拉长来一样。
流式布局并不是最理想的实现方式,通过大量的百分比布局,会经常出现许多兼容性的问题,还有就是对设计有很多的限制,因为他们在设计之初就需要考虑流式布局对元素造成的影响,只能设计横向拉伸的元素布局,设计的时候存在很多局限性。·
2.固定宽度做法
还有一种是固定页面宽度的做法,早期有些网站把页面设置成320的宽度,超出部分留白,这样做视觉,前端都挺开心,视觉在也不用被流式布局限制自己的设计灵感了,前端也不用在搞坑爹的流式布局。但是这种解决方案也是存在一些问题,例如在大屏幕手机下两边是留白的,还有一个就是大屏幕手机下看起来页面会特别小,操作的按钮也很小,手机淘宝首页起初是这么做的,但近期改版了,采用了rem。
3.响应式做法
响应式这种方式在国内很少有大型企业的复杂性的网站在移动端用这种方法去做,主要原因是工作大,维护性难,所以一般都是中小型的门户或者博客类站点会采用响应式的方法从web page到web app直接一步到位,因为这样反而可以节约成本,不用再专门为自己的网站做一个web app的版本。
4.设置viewport进行缩放
天猫的web app的首页就是采用这种方式去做的,以320宽度为基准,进行缩放,最大缩放为320*1.3 = 416,基本缩放到416都就可以兼容iphone6 plus的屏幕了,这个方法简单粗暴,又高效。说实话我觉得他和用接下去我们要讲的rem都非常高效,不过有部分同学使用过程中反应缩放会导致有些页面元素会糊的情况。
<meta name="viewport" content="width=320,maximum-scale=1.3,user-scalable=no">
rem能等比例适配所有屏幕
上面讲了一大堆目前大部分公司主流的一些web app的适配解决方案,接下来讲下rem是如何工作的。
上面说过rem是通过根元素进行适配的,网页中的根元素指的是html我们通过设置html的字体大小就可以控制rem的大小。举个例子:
//code from http://caibaojian.com/web-app-rem.html
html{
font-size:20px;
}
.btn {
width: 6rem;
height: 3rem;
line-height: 3rem;
font-size: 1.2rem;
display: inline-block;
background: #06c;
color: #fff;
border-radius: .5rem;
text-decoration: none;
text-align: center;
}
Demo 上面代码结果按钮大小如下图:
小button
我把html设置成10px是为了方便我们计算,为什么6rem等于60px。如果这个时候我们的.btn的样式不变,我们再改变html的font-size的值,看看按钮发生上面变化:
html{
font-size:40px;
}
Demo
按钮大小结果如下:
2倍小button
上面的width,height变成了上面结果的两倍,我们只改变了html的font-size,但.btn样式的width,height的rem设置的属性不变的情况下就改变了按钮在web中的大小。
其实从上面两个案例中我们就可以计算出1px多少rem:
第一个例子:
120px = 6rem * 20px(根元素设置大值)
第二个例子:
240px = 6rem * 40px(根元素设置大值)
推算出:
10px = 1rem 在根元素(font-size = 10px的时候);
20px = 1rem 在根元素(font-size = 20px的时候);
40px = 1rem 在根元素(font-size = 40px的时候);
在上面两个例子中我们发现第一个案例按钮是等比例放大到第二个按钮,html font-size的改变就会导致按钮的大小发生改变,我们并不需要改变先前给按钮设置的宽度和高度,其实这就是我们最想看到的,为什么这么说?接下来我们再来看一个例子:
由上面两个的demo中我们知道改变html的font-size可以等比改变所有用了rem单位的元素,所以大家可以通过chrome浏览器的调试工具去切换第三个的demo在不同设备下的展示效果,或者通过缩放浏览器的宽度来查看效果,我们可以看到不管在任何分辨率下,页面的排版都是按照等比例进行切换,并且布局没有乱。我只是通过一段js根据浏览器当前的分辨率改变font-size的值,就简单的实现了上面的效果,页面的所有元素都不需要进行任何改变。
到这里肯定有很多人会问我是怎么计算出不同分辨率下font-size的值?
首先假设我上面的页面设计稿给我时候是按照640的标准尺寸给我的前提下,(当然这个尺寸肯定不一定是640,可以是320,或者480,又或是375)来看一组表格。
上面的表格蓝色一列是Demo3中页面的尺寸,页面是以640的宽度去切的,怎么计算不同宽度下font-site的值,大家看表格上面的数值变化应该能明白。举个例子:384/640 = 0.6,384是640的0.6倍,所以384页面宽度下的font-size也等于它的0.6倍,这时384的font-size就等于12px。在不同设备的宽度计算方式以此类推。·
Demo3中我是通过JS去动态计算根元素的font-size,这样的好处是所有设备分辨率都能兼容适配,淘宝首页目前就是用的JS计算。但其实不用JS我们也可以做适配,一般我们在做web app都会先统计自己网站有哪些主流的屏幕设备,然后去针对那些设备去做media query设置也可以实现适配,例如下面这样:
//code from http://caibaojian.com/web-app-rem.html
html {
font-size : 20px;
}
@media only screen and (min-width: 401px){
html {
font-size: 25px !important;
}
}
@media only screen and (min-width: 428px){
html {
font-size: 26.75px !important;
}
}
@media only screen and (min-width: 481px){
html {
font-size: 30px !important;
}
}
@media only screen and (min-width: 569px){
html {
font-size: 35px !important;
}
}
@media only screen and (min-width: 641px){
html {
font-size: 40px !important;
}
}
上面的做的设置当然是不能所有设备全适配,但是用JS是可以实现全适配。具体用哪个就要根据自己的实际工作场景去定了。
下面推荐两个国内用了rem技术的移动站,大家可以上去参考看看他们的做法,手机淘宝目前只有首页用了rem,淘宝native app的首页是内嵌的web app首页。
淘宝首页:m.taobao.com
D X:m.dx.com
原文链接:http://caibaojian.com/web-app-rem.html
REM自适应JS
具体使用方法请参考这篇文章:Rem精简版实现自适应-优化flexible.js·
//code from http://caibaojian.com/web-app-rem.html
//designWidth:设计稿的实际宽度值,需要根据实际设置
//maxWidth:制作稿的最大宽度值,需要根据实际设置
//这段js的最后面有两个参数记得要设置,一个为设计稿实际宽度,一个为制作稿最大宽度,例如设计稿为750,最大宽度为750,则为(750,750)
;(function(designWidth, maxWidth) {
var doc = document,
win = window,
docEl = doc.documentElement,
remStyle = document.createElement("style"),
tid;
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
maxWidth = maxWidth || 540;
width>maxWidth && (width=maxWidth);
var rem = width * 100 / designWidth;
remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
}
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(remStyle);
} else {
var wrap = doc.createElement("div");
wrap.appendChild(remStyle);
doc.write(wrap.innerHTML);
wrap = null;
}
//要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次;
refreshRem();
win.addEventListener("resize", function() {
clearTimeout(tid); //防止执行两次
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener("pageshow", function(e) {
if (e.persisted) { // 浏览器后退的时候重新计算
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === "complete") {
doc.body.style.fontSize = "16px";
} else {
doc.addEventListener("DOMContentLoaded", function(e) {
doc.body.style.fontSize = "16px";
}, false);
}
})(750, 750);
部分文章参考:web app变革之rem
响应式与自适应的区别
- 1.自适应布局通过检测视口分辨率,来判断当前访问的设备是:pc端、平板、手机,从而请求服务层,返回不同的页面;响应式布局通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容。
- 2.自适应布局需要开发多套界面,而响应式布局只需要开发一套界面就可以了。
- 3.自适应对页面做的屏幕适配是在一定范围:比如pc端一般要大于1024像素,手机端要小于768像素。而响应式布局是一套页面全部适应。
- 4.自适应布局如果屏幕太小会发生内容过于拥挤。而响应式布局正是为了解决这个问题而衍生出的概念,它可以自动识别屏幕宽度并做出相应调整的网页设计。
总之,响应式布局还是要比自适应布局要好一点,但是自适应布局更加贴切实际,因为你只需要考虑几种状态就可以了而不是像响应式布局需要考虑非常多状态。所以的说无论哪种设计都有它们各自的特点,我们要根据项目的需求来选择适合的布局方式。
JavaScript 数据类型知识整理
先给出两道题目:
javascript中有哪些数据类型
JavaScript 中共有七种内置数据类型,包括基本类型和对象类型。
基本类型
基本类型分为以下六种:
-
string(字符串)
-
boolean(布尔值)
-
number(数字)
-
symbol(符号)
-
null(空值)
-
undefined(未定义)
注意:
-
string 、number 、boolean 和 null undefined 这五种类型统称为原始类型(Primitive),表示不能再细分下去的基本类型
-
symbol是ES6中新增的数据类型,symbol 表示独一无二的值,通过 Symbol 函数调用生成,由于生成的 symbol 值为原始类型,所以 Symbol 函数不能使用 new 调用;
-
null 和 undefined 通常被认为是特殊值,这两种类型的值唯一,就是其本身。
对象类型
对象类型也叫引用类型,array和function是对象的子类型。对象在逻辑上是属性的无序集合,是存放各种值的容器。对象值存储的是引用地址,所以和基本类型值不可变的特性不同,对象值是可变的。
说说你对javascript是弱类型语言的理解?
JavaScript 是弱类型语言,而且JavaScript 声明变量的时候并没有预先确定的类型, 变量的类型就是其值的类型,也就是说变量当前的类型由其值所决定,夸张点说上一秒种的String,下一秒可能就是个Number类型了,这个过程可能就进行了某些操作发生了强制类型转换。虽然弱类型的这种不需要预先确定类型的特性给我们带来了便利,同时也会给我们带来困扰。为了能充分利用该特性就必须掌握类型转换的原理
javascript中强制类型转换是一个非常易出现bug的点,知道强制转换时候的规则吗?
-
ToPrimitive(转换为原始值)
ToPrimitive对原始类型不发生转换处理,只针对引用类型(object)的,其目的是将引用类型(object)转换为非对象类型,也就是原始类型。
ToPrimitive 运算符接受一个值,和一个可选的 期望类型作参数。ToPrimitive 运算符将值转换为非对象类型,如果对象有能力被转换为不止一种原语类型,可以使用可选的 期望类型 来暗示那个类型。
转换后的结果原始类型是由期望类型决定的,期望类型其实就是我们传递的type。直接看下面比较清楚。 ToPrimitive方法大概长这么个样子具体如下。
/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)
type不同值的说明
-
type为string:
-
先调用obj的toString方法,如果为原始值,则return,否则第2步
-
调用obj的valueOf方法,如果为原始值,则return,否则第3步
-
抛出TypeError 异常
-
type为number:
-
调用obj的valueOf方法,如果为原始值,则返回,否则下第2步
-
调用obj的toString方法,如果为原始值,则return,否则第3步
-
抛出TypeError 异常
-
type参数为空
-
该对象为Date,则type被设置为String
-
否则,type被设置为Number,即会首先检查该值是否有valueOf()方法,如果有并且返回基本类型值,就使用该值进行强制类型转换;如果没有就使用toString()的返回值(如果存在)来进行强制类型转换
Date数据类型特殊说明:
对于Date数据类型,我们更多期望获得的是其转为时间后的字符串,而非毫秒值(时间戳),如果为number,则会取到对应的毫秒值,显然字符串使用更多。 其他类型对象按照取值的类型操作即可。
ToPrimitive总结
ToPrimitive转成何种原始类型,取决于type,type参数可选,若指定,则按照指定类型转换,若不指定,默认根据实用情况分两种情况,Date为string,其余对象为number。那么什么时候会指定type类型呢,那就要看下面两种转换方式了。
toString
Object.prototype.toString()
toString() 方法返回一个表示该对象的字符串。参考:Object.prototype.toString()
每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。对普通对象来说,除非自行定义,否则toString()返回内部属性[[Class]]的值,如"[object Object]"。
数组的默认toString()方法经过了重新定义,将所有单元字符串化以后再用","连接起来:
var a = [1,2,3]
a.toString() //"1,2,3"
如果是原始类型,抽象操作ToString则负责处理非字符串到字符串的强制类型转换。
基本类型值的字符串化规则为:
null转换为"null",
undefined转换为"undefined",
true转换为""true"。
数字的字符串化遵循通用规则,那些极小和极大的数字使用指数形式:
var a = 1.07*1000*1000*1000*1000*1000*1000*1000
a.toString() //"1.07e21"
valueOf
Object.prototype.valueOf()方法返回指定对象的原始值。
JavaScript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是我们很少需要自己调用此函数,valueOf 方法一般都会被 JavaScript 自动调用。
// Array:返回数组对象本身
var array = ["ABC", true, 12, -5];
console.log(array.valueOf() === array); // true
// Date:当前时间距1970年1月1日午夜的毫秒数
var date = new Date(2013, 7, 18, 23, 11, 59, 230);
console.log(date.valueOf()); // 1376838719230
// Number:返回数字值
var num = 15.26540;
console.log(num.valueOf()); // 15.2654
// 布尔:返回布尔值true或false
var bool = true;
console.log(bool.valueOf() === bool); // true
// new一个Boolean对象
var newBool = new Boolean(true);
// valueOf()返回的是true,两者的值相等
console.log(newBool.valueOf() == newBool); // true
// 但是不全等,两者类型不相等,前者是boolean类型,后者是object类型
console.log(newBool.valueOf() === newBool); // false
// Function:返回函数本身
function foo(){}
console.log( foo.valueOf() === foo ); // true
var foo2 = new Function("x", "y", "return x + y;");
console.log( foo2.valueOf() );
/*
ƒ anonymous(x,y
) {
return x + y;
}
*/
// Object:返回对象本身
var obj = {name: "张三", age: 18};
console.log( obj.valueOf() === obj ); // true
// String:返回字符串值
var str = "http://www.xyz.com";
console.log( str.valueOf() === str ); // true
// new一个字符串对象
var str2 = new String("http://www.xyz.com");
// 两者的值相等,但不全等,因为类型不同,前者为string类型,后者为object类型
console.log( str2.valueOf() === str2 ); // false
console.log(typeof str2.valueOf(),'str2.valueOf()'); // string
参考: Object.prototype.valueOf()
强制转换
强制转换主要指使用Number
、String
和Boolean
三个函数,手动将各种类型的值,分布转换成数字、字符串或者布尔值。
Number()
使用Number
函数,可以将任意类型的值转化成数值。
下面分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象。
(1)原始类型值
原始类型值的转换规则如下。
// 数值:转换后还是原来的值
Number(324) // 324
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN
// 空字符串转为0
Number('') // 0
// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0
// undefined:转成 NaN
Number(undefined) // NaN
// null:转成0
Number(null) // 0
Number
函数将字符串转为数值,要比parseInt
函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN
。
parseInt('42 cats') // 42
Number('42 cats') // NaN
上面代码中,parseInt
逐个解析字符,而Number
函数整体转换字符串的类型。
另外,parseInt
和Number
函数都会自动过滤一个字符串前导和后缀的空格。
parseInt('\t\v\r12.34\n') // 12
Number('\t\v\r12.34\n') // 12.34
(2)对象
简单的规则是,Number
方法的参数是对象时,将返回NaN
,除非是包含单个数值的数组。
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
之所以会这样,是因为Number
背后的转换规则比较复杂。
第一步,调用对象自身的valueOf
方法。如果返回原始类型的值,则直接对该值使用Number
函数,不再进行后续步骤。
第二步,如果valueOf
方法返回的还是对象,则改为调用对象自身的toString
方法。如果toString
方法返回原始类型的值,则对该值使用Number
函数,不再进行后续步骤。
第三步,如果toString
方法返回的是对象,就报错。
请看下面的例子。
var obj = {x: 1};
Number(obj) // NaN
// 等同于
if (typeof obj.valueOf() === 'object') {
Number(obj.toString());
} else {
Number(obj.valueOf());
}
上面代码中,Number
函数将obj
对象转为数值。背后发生了一连串的操作,首先调用obj.valueOf
方法, 结果返回对象本身;于是,继续调用obj.toString
方法,这时返回字符串[object Object]
,对这个字符串使用Number
函数,得到NaN
。
默认情况下,对象的valueOf
方法返回对象本身,所以一般总是会调用toString
方法,而toString
方法返回对象的类型字符串(比如[object Object]
)。所以,会有下面的结果。
Number({}) // NaN
如果toString
方法返回的不是原始类型的值,结果就会报错。
var obj = {
valueOf: function () {
return {};
},
toString: function () {
return {};
}
};
Number(obj)
// TypeError: Cannot convert object to primitive value
上面代码的valueOf
和toString
方法,返回的都是对象,所以转成数值时会报错。
从上例还可以看到,valueOf
和toString
方法,都是可以自定义的。
Number({
valueOf: function () {
return 2;
}
})
// 2
Number({
toString: function () {
return 3;
}
})
// 3
Number({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// 2
上面代码对三个对象使用Number
函数。第一个对象返回valueOf
方法的值,第二个对象返回toString
方法的值,第三个对象表示valueOf
方法先于toString
方法执行。
String()
String
函数可以将任意类型的值转化成字符串,转换规则如下。
(1)原始类型值
- 数值:转为相应的字符串。
- 字符串:转换后还是原来的值。
- 布尔值:
true
转为字符串"true"
,false
转为字符串"false"
。 - undefined:转为字符串
"undefined"
。 - null:转为字符串
"null"
。
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
(2)对象
String
方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
var b = [1, 2, [3, 4, [5, 6]]]
console.log(b.toString(), 'b.toString') //"1,2,3,4,5,6"
console.log([b.toString()], '[b.toString]') //["1,2,3,4,5,6"]
//再也不怕多重数组拆解成一个数组啦哈哈
String
方法背后的转换规则,与Number
方法基本相同,只是互换了valueOf
方法和toString
方法的执行顺序。
-
先调用对象自身的
toString
方法。如果返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 -
如果
toString
方法返回的是对象,再调用原对象的valueOf
方法。如果valueOf
方法返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 -
如果
valueOf
方法返回的是对象,就报错。
下面是一个例子。
String({a: 1})
// "[object Object]"
// 等同于
String({a: 1}.toString())
// "[object Object]"
上面代码先调用对象的toString
方法,发现返回的是字符串[object Object]
,就不再调用valueOf
方法了。
如果toString
法和valueOf
方法,返回的都是对象,就会报错。
var obj = {
valueOf: function () {
return {};
},
toString: function () {
return {};
}
};
String(obj)
// TypeError: Cannot convert object to primitive value
下面是通过自定义toString
方法,改变返回值的例子。
String({
toString: function () {
return 3;
}
})
// "3"
String({
valueOf: function () {
return 2;
}
})
// "[object Object]"
String({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// "3"
上面代码对三个对象使用String
函数。第一个对象返回toString
方法的值(数值3),第二个对象返回的还是toString
方法的值([object Object]
),第三个对象表示toString
方法先于valueOf
方法执行。
Boolean()
Boolean
函数可以将任意类型的值转为布尔值。
它的转换规则相对简单:除了以下五个值的转换结果为false
,其他的值全部为true
。
undefined
null
-0
或+0
NaN
''
(空字符串)
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
注意,所有对象(包括空对象)的转换结果都是true
,甚至连false
对应的布尔对象new Boolean(false)
也是true
(详见《原始类型值的包装对象》一章)。
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
所有对象的布尔值都是true
,这是因为 JavaScript 语言设计的时候,出于性能的考虑,如果对象需要计算才能得到布尔值,对于obj1 && obj2
这样的场景,可能会需要较多的计算。为了保证性能,就统一规定,对象的布尔值为true
。
js转换规则不同场景应用
什么时候自动转换为string类型
-
在没有对象的前提下
字符串的自动转换,主要发生在字符串的加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。
'2' + 1 // '21'
'2' + true // "2true"
'2' + false // "2false"
'2' + undefined // "2undefined"
'2' + null // "2null"
-
当有对象且与对象+时候
//toString的对象
var obj2 = {
toString:function(){
return 'a'
}
}
console.log('2'+obj2)
//输出结果2a
//常规对象
var obj1 = {
a:1,
b:2
}
console.log('2'+obj1);
//输出结果 2[object Object]
//几种特殊对象
'2' + {} // "2[object Object]"
'2' + [] // "2"
'2' + function (){} // "2function (){}"
'2' + ['koala',1] // 2koala,1
对下面'2'+obj2详细举例说明如下:
-
左边为string,ToPrimitive原始值转换后不发生变化
-
右边转化时同样按照ToPrimitive进行原始值转换,由于指定的type是number,进行ToPrimitive转化调用obj2.valueof(),得到的不是原始值,进行第三步
-
调用toString() return 'a'
-
符号两边存在string,而且是+号运算符则都采用String规则转换为string类型进行拼接
-
输出结果2a
对下面'2'+obj1详细举例说明如下:
-
左边为string,ToPrimitive转换为原始值后不发生变化
-
右边转化时同样按照ToPrimitive进行原始值转换,由于指定的type是number,进行ToPrimitive转化调用obj2.valueof(),得到{ a: 1, b: 2}
-
调用toString() return [object Object]
-
符号两边存在string,而且是+号运算符则都采用String规则转换为string类型进行拼接
-
输出结果2[object Object]
代码中几种特殊对象的转换规则基本相同,就不一一说明,大家可以想一下流程。
注意:不管是对象还不是对象,都有一个转换为原始值的过程,也就是ToPrimitive转换,只不过原始类型转换后不发生变化,对象类型才会发生具体转换。
什么时候自动转换为Number类型
-
有加法运算符,但是无String类型的时候,都会优先转换为Number类型
例子:
true + 0 // 1 true + true // 2 true + false //1
-
除了加法运算符,其他运算符都会把运算自动转成数值。例子:
'5' - '2' // 3 '5' * '2' // 10 true - 1 // 0 false - 1 // -1 '1' - 1 // 0 '5' * [] // 0 false / '5' // 0 'abc' - 1 // NaN null + 1 // 1 undefined + 1 // NaN //一元运算符(注意点) +'abc' // NaN -'abc' // NaN +true // 1 -false // 0
注意:null转为数值时为0,而undefined转为数值时为NaN。
判断等号也放在Number里面特殊说明
== 抽象相等比较与+运算符不同,不再是String优先,而是Nuber优先。 下面列举x == y的例子
-
如果x,y均为number,直接比较 没什么可解释的了
1 == 2 //false
-
如果存在对象,ToPrimitive() type为number进行转换,再进行后面比较
var obj1 = {
valueOf:function(){
return '1'
}
}
1 == obj1 //true
//obj1转为原始值,调用obj1.valueOf()
//返回原始值'1'
//'1'toNumber得到 1 然后比较 1 == 1
[] == ![] //true
//[]作为对象ToPrimitive得到 ''
//![] 首先[]是一个空数组,与{}空对象一样返回true,所以![]便返回false,toNumber后转换得到0
//'' == 0
//转换为 0==0 //true
-
存在boolean,按照ToNumber将boolean转换为1或者0,再进行后面比较
//boolean 先转成number,按照上面的规则得到1
//3 == 1 false
//0 == 0 true
3 == true // false
'0' == false //true
4.如果x为string,y为number,x转成number进行比较
//'0' toNumber()得到 0
//0 == 0 true
'0' == 0 //true
什么时候进行布尔转换
-
布尔比较时
-
if(obj) , while(obj) 等判断时或者 三元运算符只能够包含布尔值
条件部分的每个值都相当于false,使用否定运算符后,就变成了true
if ( !undefined
&& !null
&& !0
&& !NaN
&& !''
) {
console.log('true');
} // true
//下面两种情况也会转成布尔类型
expression ? true : false
!! expression
抽象相等
ES5规范11.9.3节的“抽象相等比较算法”定义了==运算符的行为。该算法简单而又全面,涵盖了所有可能出现的类型组合,以及它们进行强制类型转换的方式。
比较运算x==y, 其中x和 y是值,产生true或者false。这样的比较按如下方式进行:
1. 若Type(x)与Type(y)相同, 则
a. 若Type(x)为Undefined, 返回true。 undefined == undefined // true
b. 若Type(x)为Null, 返回true。// null == null // true
c. 若Type(x)为Number, 则
i. 若x为NaN, 返回false。即 NaN == NaN //false
ii. 若y为NaN, 返回false。 NaN == undefined // false
iii. 若x与y为相等数值, 返回true。
iv. 若x 为 +0 且 y为−0, 返回true。
v. 若x 为 −0 且 y为+0, 返回true。
vi. 返回false。
d. 若Type(x)为String, 则当x和y为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。 否则, 返回false。
e. 若Type(x)为Boolean, 当x和y为同为true或者同为false时返回true。 否则, 返回false。
f. 当x和y为引用同一对象时返回true。否则,返回false。
2. 若x为null且y为undefined, 返回true。 undefined == null // true
3. 若x为undefined且y为null, 返回true。
4. 若Type(x) 为 Number 且 Type(y)为String, 返回comparison x == ToNumber(y)的结果。
5. 若Type(x) 为 String 且 Type(y)为Number,返回比较ToNumber(x) == y的结果。
6. 若Type(x)为Boolean, 返回比较ToNumber(x) == y的结果。
7. 若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果。
8. 若Type(x)为String或Number,且Type(y)为Object,返回比较x == ToPrimitive(y)的结果。
9. 若Type(x)为Object且Type(y)为String或Number, 返回比较ToPrimitive(x) == y的结果。
10. 返回false。
也就是说,当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。
其他类型和布尔类型之间的相等比较
==最容易出错的一个地方是true和false与其他类型之间的相等比较。
var a = '42'
var b = true
a == b //false
复制代码
结果是false,这让人很容易掉坑里。如果严格按照“抽象相等比较算法”,这个结果也就是意料之中的。
根据第7条规则,若Type(y)为Boolean, 返回比较x == ToNumber(y)的结果,即返回'42' == 1,结果为false。
很奇怪吧?所以无论什么情况下都不要使用== true和== false。
还有一个坑常常被提到:
0 == '\n' //true
复制代码
""、"\n"(或者" "等其他空格组合)等空字符串被ToNumber强制类型转换为0。
再来看看那些“短”的地方:
"0" == false // true
false == 0 // true
false == "" // true
false == [] // true
"" == 0 // true
"" == [] // true
0 == [] // true
其中有4种情况涉及== false,之前我们说过应该避免,所以还剩下后面3种。
这些特殊情况会导致各种问题,使用中要多加小心。我们要对==两边的值认真推敲,以下两个原则可以让我们有效地避免出错。
- 如果两边的值中有true或者false,千万不要使用==
- 如果两边的值中有[]、""、或者0,尽量不要使用==
隐式强制转换在部分情况下确实很危险,为了安全起见就要使用===
js中的数据类型判断
面试官问:如何判断数据类型?怎么判断一个值到底是数组类型还是对象?
三种方式,分别为 typeof、instanceof 和 Object.prototype.toString()
typeof
通过 typeof操作符来判断一个值属于哪种基本类型。
typeof 'seymoe' // 'string'
typeof true // 'boolean'
typeof 10 // 'number'
typeof Symbol() // 'symbol'
typeof null // 'object' 无法判定是否为 null
typeof undefined // 'undefined'
typeof {} // 'object'
typeof [] // 'object'
typeof(() => {}) // 'function'
上面代码的输出结果可以看出,
-
null 的判定有误差,得到的结果 如果使用 typeof,null得到的结果是object
-
操作符对对象类型及其子类型,例如函数(可调用对象)、数组(有序索引对象)等进行判定,则除了函数都会得到 object 的结果。
综上可以看出typeOf对于判断类型还有一些不足,在对象的子类型和null情况下。
instanceof
通过 instanceof 操作符也可以对对象类型进行判定,其原理就是测试构造函数的 prototype 是否出现在被检测对象的原型链上。
[] instanceof Array // true
({}) instanceof Object // true
(()=>{}) instanceof Function // true
复制代码注意:instanceof 也不是万能的。 举个例子:
let arr = []
let obj = {}
arr instanceof Array // true
arr instanceof Object // true
obj instanceof Object // true
在这个例子中,arr 数组相当于 new Array() 出的一个实例,所以 arr.proto === Array.prototype,又因为 Array 属于 Object 子类型,即 Array.prototype.proto === Object.prototype,所以 Object 构造函数在 arr 的原型链上。所以 instanceof 仍然无法优雅的判断一个值到底属于数组还是普通对象。
还有一点需要说明下,有些开发者会说 Object.prototype.proto === null,岂不是说 arr instanceof null 也应该为 true,这个语句其实会报错提示右侧参数应该为对象,这也印证 typeof null 的结果为 object 真的只是javascript中的一个 bug 。
Object.prototype.toString() 可以说是判定 JavaScript 中数据类型的终极解决方法了,具体用法请看以下代码:
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(() => {}) // '[object Function]'
Object.prototype.toString.call('seymoe') // '[object String]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'
我们可以发现该方法在传入任何类型的值都能返回对应准确的对象类型。用法虽简单明了,但其中有几个点需要理解清楚:
-
该方法本质就是依托Object.prototype.toString() 方法得到对象内部属性 [[Class]]
-
传入原始类型却能够判定出结果是因为对值进行了包装
-
null 和 undefined 能够输出结果是内部实现有做处理
NaN相关总结
NaN的概念
NaN 是一个全局对象的属性,NaN 是一个全局对象的属性,NaN是一种特殊的Number类型。
什么时候返回NaN (开篇第二道题也得到解决)
-
无穷大除以无穷大
-
给任意负数做开方运算
-
算数运算符与不是数字或无法转换为数字的操作数一起使用
-
字符串解析成数字
一些例子:
Infinity / Infinity; // 无穷大除以无穷大
Math.sqrt(-1); // 给任意负数做开方运算
'a' - 1; // 算数运算符与不是数字或无法转换为数字的操作数一起使用
'a' * 1;
'a' / 1;
parseInt('a'); // 字符串解析成数字
parseFloat('a');
Number('a'); //NaN
'abc' - 1 // NaN
undefined + 1 // NaN
//一元运算符(注意点)
+'abc' // NaN
-'abc' // NaN
对 null 和 undefined 进行比较
当使用 null
或 undefined
与其他值进行比较时,其返回结果常常出乎你的意料。
当使用严格相等 ===
比较二者时
它们不相等,因为它们属于不同的类型。
当使用非严格相等 ==
比较二者时
JavaScript 存在一个特殊的规则,会判定它们相等。他们俩就像“一对恋人”,仅仅等于对方而不等于其他任何的值(只在非严格相等下成立)。
当使用数学式或其他比较方法 < > <= >=
时:
null/undefined
会被转化为数字:null
被转化为 0
,undefined
被转化为 NaN
。
下面让我们看看,这些规则会带来什么有趣的现象。同时更重要的是,我们需要从中学会如何远离这些特性带来的“陷阱”。
奇怪的结果:null vs 0
通过比较 null
和 0 可得:
是的,上面的结果完全打破了你对数学的认识。在最后一行代码显示“null
大于等于 0”的情况下,前两行代码中一定会有一个是正确的,然而事实表明它们的结果都是 false。
为什么会出现这种反常结果,这是因为相等性检测 ==
和普通比较符 > < >= <=
的代码逻辑是相互独立的。进行值的比较时,null
会被转化为数字,因此它被转化为了 0
。这就是为什么(3)中 null >= 0
返回值是 true,(1)中 null > 0
返回值是 false。
另一方面,undefined
和 null
在相等性检测 ==
中不会进行任何的类型转换,它们有自己独立的比较规则,所以除了它们之间互等以及与本身相等外,不会等于任何其他的值。这就解释了为什么(2)中 null == 0
会返回 false。
特立独行的 undefined
undefined
不应该被与其他值进行比较:
为何它看起来如此厌恶 0?返回值都是 false!
原因如下:
(1)
和(2)
都返回false
是因为undefined
在比较中被转换为了NaN
,而NaN
是一个特殊的数值型值,它与任何值进行比较都会返回false
。(3)
返回false
是因为这是一个相等性检测,而undefined
只与null
和自己相等,不会与其他值相等。
规避错误
我们为何要研究上述示例?我们需要时刻记得这些古怪的规则吗?不,其实不需要。虽然随着代码写得越来越多,我们对这些规则也都会烂熟于胸,但是我们需要更为可靠的方法来避免潜在的问题:
除了严格相等 ===
外,其他凡是有 undefined/null
参与的比较,我们都需要额外小心。
除非你非常清楚自己在做什么,否则永远不要使用 >= > < <=
去比较一个可能为 null/undefined
的变量。对于取值可能是 null/undefined
的变量,请按需要分别检查它的取值情况。
误区
toString和String的区别
-
toString
-
toString()可以将数据都转为字符串,但是null和undefined不可以转换。
console.log(null.toString()) //报错 TypeError: Cannot read property 'toString' of null console.log(undefined.toString()) //报错 TypeError: Cannot read property 'toString' of undefined
-
toString()括号中可以写数字,代表进制
二进制:.toString(2);
八进制:.toString(8);
十进制:.toString(10);
十六进制:.toString(16);
-
String
-
String()可以将null和undefined转换为字符串,但是没法转进制字符串
console.log(String(null)); // null console.log(String(undefined)); // undefined
插个小片段
console.log(NaN == NaN, 'NaN == NaN') // false
console.log(NaN == undefined, 'NaN == undefined') // false
console.log(undefined == undefined, 'undefined == undefined') // true
console.log(null == null, 'null == null') // true
console.log(undefined == null, 'undefined == null') // true 非严格模式下
console.log(0 == null, '0 == null') // false 非严格模式下
console.log([] == [], '[]') // false
console.log(!![], '!![]') // true
console.log([], '[]') // length: 0__proto__: Array(0)
console.log(typeof [], 'typeof []') // object
console.log(false == [], 'false == []') // true
console.log(-["1"], '- ["1"]') // -1
console.log(-["1", "2"], '- ["1", "2"]') // NaN
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000
console.log(a.toString(), 'a.toString()') //"1.07e21"
var b = [1, 2, [3, 4, [5, 6]]]
console.log(b.toString(), 'b.toString') //"1,2,3,4,5,6"
console.log([b.toString()], '[b.toString]') //["1,2,3,4,5,6"]
// new一个字符串对象
var str2 = new String("http://www.xyz.com");
// 两者的值相等,但不全等,因为类型不同,前者为string类型,后者为object类型
console.log(str2.valueOf() === str2); // false
console.log(typeof str2.valueOf(),'str2.valueOf()'); // string
参考:数据类型转换、经常被面试官考的 JavaScript 数据类型知识你真的懂吗?、JavaScript中的强制类型转换、值的比较