一、浏览器环境概述(BOM)
JavaScript 是浏览器的内置脚本语言,一旦网页内嵌了 JavaScript 脚本,浏览器加载网页,就会去执行脚本,从而达到操作浏览器的目的,实现网页的各种动态效果
二、script 元素工作原理
浏览器加载 JavaScript 脚本,主要通过<script>元素完成。
正常的网页加载流程是这样的:
- 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析
- 解析过程中,浏览器发现<script>元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎
- 如果<script>元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码
- JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页
加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。
原因是 JavaScript 代码可以修改 DOM,所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题。 如果外部脚本加载时间很长(一直无法完成下载),那么浏览器就会一直等待脚本下载完成,造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。
为了避免这种情况,较好的做法是将<script>标签都放在页面底部, 而不是头部。这样即使遇到脚本失去响应,网页主体的渲染也已经完成了,用户至少可以看到内容,而不是面对一张空白的页面。
2.1 defer 属性
为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对<script>元素加入 defer 属性。它的作用是延迟脚本的执行,等到 DOM 加载生成后,再执行脚本
有了 defer 属性,浏览器下载脚本文件的时候,不会阻塞页面渲染
2.2 async 属性
解决“阻塞效应”的另一个方法是对<script>元素加入 async 属性
async 属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染
async 属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。
一般来说,如果脚本之间没有依赖关系,就使用 async 属性,如果脚本之间有依赖关系,就使用 defer 属性。如果同时使用 async 和 defer 属性,后者不起作用,浏览器行为由 async 属性决定。
三、回流和重绘
渲染树转换为网页布局,称为“布局流”(flow);布局显示到页面的这个过程,称为“绘制”(paint)。它们都具有阻塞效应,并且会耗费很多时间和计算资源。 页面生成以后,脚本操作和样式表操作,都会触发“回流”(reflow) 和“重绘”(repaint)。
3.1 什么是回流和重绘?
回流:当节点树中的一部分因为元素的规模尺寸,布局,隐藏等改变而需要重新构建
重绘:当节点数中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的
3.2 什么时候会造成回流和重绘?
四、定时器
JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由 setTimeout() 和 setInterval() 这两个函数来完成。它们向任务队列添加定时任务
4.1 setTimeout()
setTimeout 函数用来指定某个函数或某段代码,在多少毫秒之后执行。 它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。
setTimeout 函数接受两个参数,第一个参数 func|code 是将要推迟执行的函数名或者一段代码,第二个参数 delay 是推迟执行的毫秒数。
解决方案
定时器可以进行取消
4.2 setInterval()
setInterval 函数的用法与 setTimeout 完全一致,区别仅仅在于 setInterval 指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行
通过setInterval方法实现网页动画
定时器可以进行取消
五、防抖(debounce)
防抖严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。
从滚动条监听的例子说起:
先说一个常见的功能,很多网站会提供这么一个按钮:用于返回顶部。
这个按钮只会在滚动到距离顶部一定位置之后才出现,那么我们现 在抽象出这个功能需求-- 监听浏览器滚动事件,返回当前滚条与顶部的距离这个需求很简单,直接写
在运行的时候会发现存在一个问题:这个函数的默认执行频率, 太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!
然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。 基于上述场景,首先提出第一种思路:在第一次触发事件时,不立 即执行函数,而是给出一个期限值比如200ms,然后
效果:如果短时间内大量触发同一事件,只会执行一次函数
实现:既然前面都提到了计时,那实现的关键就在于setTimeout这 个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现
到这里,已经把防抖实现了
六、节流(throttle)
节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死
继续思考,使用上面的防抖方案来处理问题的结果是如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢? 其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这 段时间后再重新激活(类似于技能冷却时间) 效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效
实现
这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态
如果一直拖着滚动条进行滚动,那么会以300ms的时间间隔,持续输出当前位置和顶部的距离讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:
七、window 对象
浏览器里面, window 对象(注意, w 为小写)指当前的浏览器窗口。 它也是当前页面的顶层对象,即最高一层的对象,所有其他对象都是它的下属
7.1 属性
①window.screenX,window.screenY
window.screenX 和 window.screenY 属性,返回浏览器窗口左上角相对于当前屏幕左上角的水平距离和垂直距离(单位像素)。这两个属性只读
②window.innerHeight,window.innerWidth
window.innerHeight 和 window.innerWidth 属性,返回网页在当前窗口中可见部分的高度和宽度,即“视口”(viewport)的大小(单位像素)。这两个属性只读
③window.outerHeight,window.outerWidth
window.outerHeight 和 window.outerWidth 属性返回浏览器窗口的高度和宽度, 包括浏览器菜单和边框(单位像素)。这两个属性只读
④window.scrollX,window.scrollY
window.scrollX 属性返回页面的水平滚动距离, window.scrollY 属性返回页面的垂直滚动距离,单位都为像素。这两个属性只读
⑤window.pageXOffset,window.pageYOffset
window.pageXOffset 属性和 window.pageYOffset 属性,是 window.scrollX 和 window.scrollY 别名
7.2 方法
①window.alert(),window.prompt(),window.confirm()
window.alert() 、 window.prompt() 、 window.confirm() 都是浏览器与用户互动的全局方法。它们会弹出不同的对话框,要求用户做出回应。注意,这 三个方法弹出的对话框,都是浏览器统一规定的式样,无法定制
②window.alert()
window.alert() 方法弹出的对话框,只有一个“确定”按钮,往往用来通知用户某些信息
③window.prompt()
window.prompt() 方法弹出的对话框,提示文字的下方,还有一个输入框,要求用户输入信息,并有“确定”和“取消”两个按钮。它往往用来获取用户输入的数据
④window.confirm()
window.confirm() 方法弹出的对话框,除了提示信息之外,只有“确定”和 “取消”两个按钮,往往用来征询用户是否同意
confirm 方法返回一个布尔值,如果用户点击“确定”,返回 true ;如果 用户点击“取消”,则返回 false
⑤window.open()
window.open 方法用于新建另一个浏览器窗口,类似于浏览器菜单的新建窗口选项。它会返回新窗口的引用,如果无法新建窗口,则返回 null
八、Navigator 对象
window.navigator 属性指向一个包含浏览器和系统信息的 Navigator 对象。脚本通过这个属性了解用户的环境信息
①Navigator.userAgent
navigator.userAgent 属性返回浏览器的 User Agent 字符串,表示用户设备信息,包含了浏览器的厂商、版本、操作系统等信息
过 userAgent 可以大致准确地识别手机浏览器,方法就是测试是否包含 mobi 字符串
②Navigator.plugins
Navigator.plugins 属性返回一个类似数组的对象,成员是 Plugin 实例对象,表示浏览器安装的插件,比如 Flash、ActiveX 等
③Navigator.platform
Navigator.platform 属性返回用户的操作系统信息,比如 MacIntel 、 Win32 、 Linux x86_64 等
④Navigator.language,Navigator.languages
Navigator.language 属性返回一个字符串,表示浏览器的首选语言。该属性只读
Navigator.languages 属性返回一个数组,表示用户可以接受的语言
九、Screen 对象
Screen 对象表示当前窗口所在的屏幕,提供显示设备的信息。 window.screen 属性指向这个对象
①Screen.height
浏览器窗口所在的屏幕的高度(单位像素)。除非调整显示器的分辨率,否则这个值可以看作常量,不会发生变化。显示器的分辨率与浏览器设置无关,缩放网页并不会改变分辨率
②Screen.width
浏览器窗口所在的屏幕的宽度(单位像素)
③Screen.availHeight
浏览器窗口可用的屏幕高度(单位像素)。因为部分空间可能不可用,比如系统的任务栏或者 Mac 系统屏幕底部的 Dock 区,这个属性等于 height 减去那些被系统组件的高度
④Screen.availWidth
浏览器窗口可用的屏幕宽度(单位像素)
⑤Screen.pixelDepth
整数,表示屏幕的色彩位数,比如 24 表示屏幕提供24位色彩
⑥Screen.orientation
返回一个对象,表示屏幕的方向。该对象的 type 属性是一个字符串,表示屏幕的具体方向, landscape-primary 表示横放, landscape-secondary 表示颠倒的横放, portrait-primary 表示竖放, portrait-secondary 表示颠倒的竖放。
十、History 对象
window.history 属性指向 History 对象,它表示当前窗口的浏览历史 History 对象保存了当前窗口访问过的所有页面网址
①History.back()
History.back() :移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果
②History.forward()
History.forward() :移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果
③History.go()
History.go() :接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址,比如 go(1) 相当于 forward() , go(-1) 相当于 back() 。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为 0 ,相当于刷新当前页面
十一、Cookie 对象
Cookie 是服务器保存在浏览器的一小段文本信息,每个 Cookie 的大小一般不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息
Cookie 的目的就是区分用户,以及放置状态信息,它的使用场景主要如下:
Cookie 不是一种理想的客户端存储机制。它的容量很小(4KB), 缺乏数据操作接口,而且会影响性能。客户端存储建议使用 Web storage API 。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面。
每个 Cookie 都有以下几方面的元数据:
- Cookie 的名字
- Cookie 的值(真正的数据写在这里面)
- 到期时间(超过这个时间会失效)
- 所属域名(默认为当前域名)
- 生效的路径(默认为当前网址)
不同浏览器对 Cookie 数量和大小的限制,是不一样的。一般来 说,单个域名设置的 Cookie 不应超过30个,每个 Cookie 的大小不 能超过 4KB。超过限制以后,Cookie 将被忽略,不会被设置
读取cookie
①Expires
Expires 属性指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 Cookie。它的值是 UTC 格式,可以使用 Date.prototype.toUTCString() 进行格式转换
如果不设置该属性,或者设为 null ,Cookie 只在当前会话 (session)有效,浏览器窗口一旦关闭,当前 Session 结束,该 Cookie 就会被删除。另外,浏览器根据本地时间,决定 Cookie 是否过期,由于本地时间是不精确的,所以没有办法保证 Cookie 一定会在服务器指定的时间过期。
②Max-Age
Max-Age 属性指定从现在开始 Cookie 存在的秒数,比如 60 * 60 * 24 * 365 (即一年)。过了这个时间以后,浏览器就不再保留这个 Cookie 如果同时指定了 Expires 和 Max-Age ,那么 Max-Age 的值将优先生效
③Domain
Domain 属性指定 Cookie 属于哪个域名,以后浏览器向服务器发送 HTTP 请求时,通过这个属性判断是否要附带某个 Cookie
④Path
Path 属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现, Path 属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如, Path 属性是 / ,那么请求 /docs 路径也会包含该 Cookie。当然,前提是 Domain 属性 必须符合条件
⑤Secure
Secure 属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器 会自动忽略服务器发来的 Secure 属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开
⑥HttpOnly
HttpOnly 属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是 document.cookie 属性、 XMLHttpRequest 对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie
⑦封装cookie操作
⑧apply、call和bind函数
无论是apply、call还是bind其实都是改变this的指向,我们先来看一个例子
下面我们来看一下通过他们来改变this指向
由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行
我们再来看一下apply和call的区别
第一个参数:this的指向,第二个参数为方法传递参数
常见应用场景
null代表指向window对象,这里是因为Array本身时候window对象的子元素