我对JavaScript的一些认识
本文很多内容皆是来自阮一峰大神JavaScript标准参考教程,本人只是在根据此书脉络梳理自身对js知识掌握的同时对某些内容做总结记录......
1. 基本概念
JavaScript是一种轻量级的脚本语言,它本身不提供任何与I/O(输入input/输出output)相关的API,都要靠宿主环境(host)提供,去调用宿主环境提供的API。
所谓“脚本语言”,是指其本身不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序的“脚本”。
JavaScript 的发明目的,就是作为浏览器的内置脚本语言,为网页开发者提供操控浏览器的能力。它是目前唯一一种通用的浏览器脚本语言。
2. 语法
语句和表达式的区别在于,前者主要为了进行某种操作,一般情况下不需要返回值;后者则是为了得到返回值,一定会返回一个值。
语句以分号结尾,一个分号就表示一个语句结束。
分号前面可以没有任何内容,JavaScript引擎将其视为空语句。
如果使用var重复声明一个已经存在的变量,第二次声明是无效的。
但是,如果第二次声明的时候还进行了赋值,则会覆盖掉前面的值。
break语句和continue语句
break语句用于跳出代码块或循环。
continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
3. 原型原型链
面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)来实现对象的继承。JavaScript 语言的继承则是通过“原型对象”(prototype)。
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
“原型链”(prototype chain):对象到原型,再到原型的原型…… 这就形成了原型链。
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的。
Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。原型链到此为止。
读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
注意,一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle();
v instanceof Vehicle // true 对象v是构造函数Vehicle的实例,所以返回true
4. promise
Promise 的设计思想是,所有异步任务都返回一个 Promise 实例。Promise 实例有一个then方法,用来指定下一步的回调函数。
function f1(resolve, reject) {
// 异步代码...
}
var p1 = new Promise(f1);
p1.then(f2);
5. DOM对象
document.scrollingElement属性返回文档的滚动元素。也就是说,当文档整体滚动时,到底是哪个元素在滚动。
标准模式下,这个属性返回的文档的根元素document.documentElement(即<html>)。兼容(quirk)模式下,返回的是<body>元素,如果该元素不存在,返回null。
// 页面滚动到浏览器顶部
document.scrollingElement.scrollTop = 0;
document.fullscreenElement属性返回当前以全屏状态展示的 DOM 元素。如果不是全屏状态,该属性返回null。
我们可以通过document.fullscreenElement可以知道<video>元素有没有处在全屏状态,从而判断用户行为。
if (document.fullscreenElement.nodeName == 'VIDEO') {
console.log('全屏播放视频');
}
document.documentURI属性和document.URL属性都返回一个字符串,表示当前文档的网址。
不同之处是它们继承自不同的接口,documentURI继承自Document接口,可用于所有文档;URL继承自HTMLDocument接口,只能用于 HTML 文档。
document.domain属性返回当前文档的域名,不包含协议和接口。
Location对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。通过window.location和document.location属性,可以拿到这个对象。
为div1节点,插入一个值为newVal的my_attrib属性
document.getElementById('div1').setAttribute('my_attrib', 'newVal');
6. 事件
Element.matches方法返回一个布尔值,表示当前元素是否匹配给定的 CSS 选择器。
if (el.matches('.someClass')) { console.log('Match!'); }
Element.addEventListener():添加事件的回调函数
document.getElementById('box').addEventListener('click',function(){ console.log('输出') });
Element.removeEventListener():移除事件监听函数
注意,removeEventListener方法移除的监听函数,必须是addEventListener方法添加的那个监听函数,而且必须在同一个元素节点,否则无效。
function Cons(){ console.log('输出') } document.getElementById('box').addEventListener('click',Cons,false); document.getElementById('box').removeEventListener('click',Cons,false);
Element.dispatchEvent():触发事件
document.querySelector('#boxes').addEventListener('click',function(){//在boxes的点击事件中触发box的点击事件 var event = new Event('click'); document.getElementById('box').dispatchEvent(event); })
Element.scrollIntoView方法滚动当前元素,进入浏览器的可见区域;
该方法可以接受一个布尔值作为参数。如果为true,表示元素的顶部与当前区域的可见部分的顶部对齐(前提是当前区域可滚动);
如果为false,表示元素的底部与当前区域的可见部分的尾部对齐(前提是当前区域可滚动)。如果没有提供该参数,默认为true。
document.getElementById('six').addEventListener('click',function(){ document.getElementById('six').scrollIntoView(); // document.getElementById('six').scrollIntoView(false); })
7. 重流重绘
渲染树转换为网页布局,称为“布局流”(flow);布局显示到页面的这个过程,称为“绘制”(paint)。它们都具有阻塞效应,并且会耗费很多时间和计算资源。
页面生成以后,脚本操作和样式表操作,都会触发“重流”(reflow)和“重绘”(repaint)。用户的互动也会触发重流和重绘,比如设置了鼠标悬停(a:hover)效果、页面滚动、在输入框中输入文本、改变窗口大小等等。
重流和重绘并不一定一起发生,重流必然导致重绘,重绘不一定需要重流。比如改变元素颜色,只会导致重绘,而不会导致重流;改变元素的布局,则会导致重绘和重流。
大多数情况下,浏览器会智能判断,将重流和重绘只限制到相关的子树上面,最小化所耗费的代价,而不会全局重新生成网页。
作为开发者,应该尽量设法降低重绘的次数和成本。比如,尽量不要变动高层的 DOM 元素,而以底层 DOM 元素的变动代替;再比如,重绘table布局和flex布局,开销都会比较大。
重流重绘优化技巧:
读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
缓存 DOM 信息。
不要一项一项地改变样式,而是使用 CSS class 一次性改变样式。
使用documentFragment操作 DOM
动画使用absolute定位或fixed定位,这样可以减少对其他元素的影响。
只在必要时才显示隐藏元素。
使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流。
使用虚拟DOM(virtual DOM)库。
//下面是一个window.requestAnimationFrame()对比效果的例子 // 重绘代价高 function doubleHeight(element) { var currentHeight = element.clientHeight; element.style.height = (currentHeight * 2) + 'px'; } all_my_elements.forEach(doubleHeight); // 重绘代价低 function doubleHeight(element) { var currentHeight = element.clientHeight; window.requestAnimationFrame(function () { element.style.height = (currentHeight * 2) + 'px'; }); } all_my_elements.forEach(doubleHeight);
8. window对象
window.top属性指向最顶层窗口,主要用于在子窗口里面获取顶层的父窗口。
window.self:当前窗口,即自身。
window.parent属性指向父窗口。如果当前窗口没有父窗口,window.parent指向自身。
if (window.parent !== window.top) {
// 表明当前窗口嵌入不止一层
}
if (window.top === window.self) { // 当前窗口是顶层窗口 } else { // 当前窗口是子窗口 }
让父窗口的访问历史后退一次
window.parent.history.back();
window.open方法用于新建另一个浏览器窗口,类似于浏览器菜单的新建窗口选项。它会返回新窗口的引用,如果无法新建窗口,则返回null。
window.open(url, windowName, [windowFeatures])
open方法一共可以接受三个参数。
url:字符串,表示新窗口的网址。如果省略,默认网址就是about:blank。
windowName:字符串,表示新窗口的名字。如果该名字的窗口已经存在,则占用该窗口,不再新建窗口。如果省略,就默认使用_blank,表示新建一个没有名字的窗口。
windowFeatures:字符串,内容为逗号分隔的键值对(详见下文),表示新窗口的参数,比如有没有提示栏、工具条等等。如果省略,则默认打开一个完整 UI 的新窗口。如果新建的是一个已经存在的窗口,则该参数不起作用,浏览器沿用以前窗口的参数。
var popup = window.open( 'somepage.html', 'DefinitionsWindows', 'height=200,width=200,location=no,status=yes,resizable=yes,scrollbars=yes' );
//表示,打开的新窗口高度和宽度都为200像素,没有地址栏和滚动条,但有状态栏,允许用户调整大小。
第三个属性可以设置如下属性:
- left:新窗口距离屏幕最左边的距离(单位像素)。注意,新窗口必须是可见的,不能设置在屏幕以外的位置。
- top:新窗口距离屏幕最顶部的距离(单位像素)。
- height:新窗口内容区域的高度(单位像素),不得小于100。
- width:新窗口内容区域的宽度(单位像素),不得小于100。
- outerHeight:整个浏览器窗口的高度(单位像素),不得小于100。
- outerWidth:整个浏览器窗口的宽度(单位像素),不得小于100。
- menubar:是否显示菜单栏。
- toolbar:是否显示工具栏。
- location:是否显示地址栏。
- personalbar:是否显示用户自己安装的工具栏。
- status:是否显示状态栏。
- dependent:是否依赖父窗口。如果依赖,那么父窗口最小化,该窗口也最小化;父窗口关闭,该窗口也关闭。
- minimizable:是否有最小化按钮,前提是
dialog=yes。 - noopener:新窗口将与父窗口切断联系,即新窗口的
window.opener属性返回null,父窗口的window.open()方法也返回null。 - resizable:新窗口是否可以调节大小。
- scrollbars:是否允许新窗口出现滚动条。
- dialog:新窗口标题栏是否出现最大化、最小化、恢复原始大小的控件。
- titlebar:新窗口是否显示标题栏。
- alwaysRaised:是否显示在所有窗口的顶部。
- alwaysLowered:是否显示在父窗口的底下。
- close:新窗口是否显示关闭按钮。
window.close方法用于关闭当前窗口,一般只用来关闭window.open方法新建的窗口。该方法只对顶层窗口有效,iframe框架之中的窗口使用该方法无效。
popup.close()
浏览器脚本发生错误时,会触发window对象的error事件。我们可以通过window.onerror属性对该事件指定回调函数。
window.onerror = function (message, filename, lineno, colno, error) { console.log("出错了!--> %s", error.stack); };
9. screen对象
Screen.orientation:返回一个对象,表示屏幕的方向。
该对象的type属性是一个字符串,表示屏幕的具体方向,landscape-primary表示横放,landscape-secondary表示颠倒的横放,portrait-primary表示竖放,portrait-secondary。
我们可以根据屏幕的宽度,将用户导向不同网页的代码。
if ((screen.width <= 800) && (screen.height <= 600)) {
window.location.replace('small.html');
} else {
window.location.replace('wide.html');
}
10. navigator对象
navigator.userAgent属性返回浏览器的 User Agent 字符串,表示浏览器的厂商和版本信息。
通过userAgent可以大致识别所有移动设备的浏览器
var ua = navigator.userAgent.toLowerCase();
if (/mobi|android|touch|mini/i.test(ua)) {
// 移动设备的浏览器
} else {
// 非移动设备浏览器
}
Navigator.platform属性返回用户的操作系统信息,比如MacIntel、Win32、Linux x86_64等 。
navigator.onLine属性返回一个布尔值,表示用户当前在线还是离线(浏览器断线)。
有时,浏览器可以连接局域网,但是局域网不能连通外网。这时,有的浏览器的onLine属性会返回true,所以不能假定只要是true,用户就一定能访问互联网。不过,如果是false,可以断定用户一定离线。
用户变成在线会触发online事件,变成离线会触发offline事件,可以通过window.ononline和window.onoffline指定这两个事件的回调函数。
window.addEventListener('offline', function(e) { console.log('offline'); });
window.addEventListener('online', function(e) { console.log('online'); });
11. URL的解码和编码
网页的 URL 只能包含合法的字符,这可以分成两类。
- URL 元字符:分号(
;),逗号(’,’),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#) - 语义字符:
a-z,A-Z,0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号(\),圆括号(()`)
除了以上字符,其他字符出现在 URL 之中都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母。比如,UTF-8 的操作系统上,http://www.example.com/q=春节这个 URL 之中,汉字“春节”不是 URL 的合法字符,所以被浏览器自动转成http://www.example.com/q=%E6%98%A5%E8%8A%82。其中,“春”转成了%E6%98%A5,“节”转成了“%E8%8A%82”。这是因为“春”和”节“的 UTF-8 编码分别是E6 98 A5和E8 8A 82,将每个字节前面加上百分号,就构成了 URL 编码。
encodeURI()方法用于转码整个 URL。它的参数是一个字符串,代表整个 URL。它会将元字符和语义字符之外的字符,都进行转义。
encodeURIComponent()方法用于转码 URL 的组成部分,会转码除了语义字符之外的所有字符,即元字符也会被转码。所以,它不能用于转码整个 URL。它接受一个参数,就是 URL 的片段。
encodeURI('http://www.example.com/q=春节') // "http://www.example.com/q=%E6%98%A5%E8%8A%82"
encodeURIComponent('春节') // "%E6%98%A5%E8%8A%82" encodeURIComponent('http://www.example.com/q=春节') // "http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82"
//encodeURIComponent()会连 URL 元字符一起转义,所以如果转码整个 URL 就会出错。
decodeURI()方法用于整个 URL 的解码。它是encodeURI()方法的逆运算。它接受一个参数,就是转码后的 URL。
decodeURIComponent()用于URL 片段的解码。它是encodeURIComponent()方法的逆运算。它接受一个参数,就是转码后的 URL 片段。
12. storage存储对象
注意,Storage.setItem()两个参数都是字符串。如果不是字符串,会自动转成字符串,再存入浏览器。
写入不一定要用setItem(),直接赋值也是可以的。
// 下面三种写法等价 window.localStorage.foo = '123'; window.localStorage['foo'] = '123'; window.localStorage.setItem('foo', '123');
Storage 接口储存的数据发生变化时,会触发 storage 事件,可以指定这个事件的监听函数。
function onStorageChange(e) { console.log(e.key); } window.addEventListener('storage', onStorageChange);
注意,该事件有一个很特别的地方,就是它不在导致数据变化的当前页面触发,而是在同一个域名的其他窗口触发。
也就是说,如果浏览器只打开一个窗口,可能观察不到这个事件。比如同时打开多个窗口,当其中的一个窗口导致储存的数据发生改变时,只有在其他窗口才能观察到监听函数的执行。
可以通过这种机制,实现多个窗口之间的通信。
浙公网安备 33010602011771号