本文主要总结学习过程中自己觉得重要或者不懂常见的小知识,持续更新
parseInt
parseInt方法用于将字符串转为整数。
parseInt的返回值只有两种可能,要么是一个十进制整数,要么是NaN。
如果parseInt的参数不是字符串,则会先转为字符串再转换。
第二个参数
parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制。
ex:
parseInt('1000') // 1000
// 等同于
parseInt('1000', 10) // 1000
parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
Number.prototype.toString()
Number对象部署了自己的toString方法,用来将一个数值转为字符串形式。
参数
toString方法可以接受一个参数,表示输出的进制。如果省略这个参数,默认将数值先转为十进制,再输出字符串;否则,就根据参数指定的进制,将一个数字转化成某个进制的字符串
ex:
(10).toString(2) // "1010"
(10).toString(8) // "12"
(10).toString(16) // "a"
setTimeout
学习promise时发现的问题
第三个参数
setTimeout(resolve, ms, 'done');
这里的第三个参数为附加参数,一旦定时器到期,它们会作为参数传递给 function 或执行字符串
setTimeout(resolve, ms, 'done')
和setTimeout(resolve('done'), ms)
区别
就是func()和func的区别,setTimeout的第一个参数应该是函数,如果用func()相当于其返回值为第一个参数,不再为函数,所以func()会立即执行,不会又延时的作用。
本例中 resolve 是一个函数,因此行为正常
setTimeout(resolve(‘World’), ms)中 resolve(‘World’) 不是函数,是什么决定于 resolve 的返回值类型,但无论如何,resolve 在 注册 timer 的时候 就已经执行了,自然也就没有延迟效果了
鼠标事件
鼠标进入节点
mouseenter
鼠标进入一个节点时触发,进入子节点不会触发这个事件mouseover
鼠标进入一个节点时触发,进入子节点会再一次触发这个事件
他们都是鼠标移入一个节点时触发,区别仅在于进入子节点会不会再次触发事件
鼠标离开节点
mouseleave
鼠标离开一个节点时触发,离开父节点不会触发这个事件mouseout
鼠标离开一个节点时触发,离开父节点也会触发这个事件
ps:一般mouseenter
与mouseleave
配合使用, mouseover
和mouseout
配合使用
其他:
- mousedown:按下鼠标键时触发。
- mouseup:释放按下的鼠标键时触发。
- contextmenu:按下鼠标右键时(上下文菜单出现前)触发,或者按下“上下文菜单键”时触发。
- wheel:滚动鼠标的滚轮时触发,该事件继承的是WheelEvent接口。
js 阻塞效应解决
defer属性
为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对<script>
元素加入defer属性。它的作用是延迟脚本的执行,等到 DOM 加载生成后,再执行脚本。
<script src="a.js" defer></script>
<script src="b.js" defer></script>
上面代码中,只有等到 DOM 加载完成后,才会执行a.js和b.js。
defer属性的运行流程如下。
- 浏览器开始解析 HTML 网页。
- 解析过程中,发现带有defer属性的
<script>
元素。 - 浏览器继续往下解析 HTML 网页,同时并行下载
<script>
元素加载的外部脚本。 - 浏览器完成解析 HTML 网页,此时再回过头执行已经下载完成的脚本。
ps:对于内置而不是加载外部脚本的script标签,以及动态生成的script标签,defer属性不起作用。另外,使用defer加载的外部脚本不应该使用document.write方法。
async 属性
<script src="a.js" async></script>
<script src="b.js" async></script>
async属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染。
- 浏览器开始解析 HTML 网页。
- 解析过程中,发现带有async属性的script标签。
- 浏览器继续往下解析 HTML 网页,同时并行下载
<script>
标签中的外部脚本。 - 脚本下载完成,浏览器暂停解析 HTML 网页,开始执行下载的脚本。
- 脚本执行完毕,浏览器恢复解析 HTML 网页。
注意:一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。另外,使用async属性的脚本文件里面的代码,不应该使用document.write方法。
一般来说,如果脚本之间没有依赖关系,就使用async属性,如果脚本之间有依赖关系(比如只有先引入jquery.js才能使用jquery语法),就使用defer属性。如果同时使用async和defer属性,后者不起作用,浏览器行为由async属性决定。
脚本的动态加载
<script>
元素还可以动态生成,生成后再插入页面,从而实现脚本的动态加载。
['a.js', 'b.js'].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
document.head.appendChild(script);
});
这种方法的好处是,动态生成的script标签不会阻塞页面渲染,也就不会造成浏览器假死。但是问题在于,这种方法无法保证脚本的执行顺序,哪个脚本文件先下载完成,就先执行哪个。
如果想避免这个问题,可以设置async属性为false。
['a.js', 'b.js'].forEach(function(src) {
var script = document.createElement('script');
script.src = src;
script.async = false;
document.head.appendChild(script);
});
加载所用协议
如果不指定协议,浏览器默认采用 HTTP 协议下载。
如果要采用 HTTPS 协议下载,必需写明
<script src="https://example.js"></script>
根据页面本身的协议来决定加载协议,这时可以采用下面的写法。
<script src="//example.js"></script>
不同浏览器渲染引擎及渲染机制
- Firefox:Gecko 引擎
- Safari:WebKit 引擎
- Chrome:Blink 引擎(基于 WebKit 的 fork Web 渲染引擎)
- IE: Trident 引擎
- Edge: EdgeHTML 引擎 (随着win10推出的)
- Opera: Presto->blink
渲染引擎处理网页,通常分成四个阶段。
- 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
- 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
- 布局:计算出渲染树的布局(layout)。
- 绘制:将渲染树绘制到屏幕。
以上四步并非严格按顺序执行,往往第一步还没完成,第二步和第三步就已经开始了。所以,会看到这种情况:网页的 HTML 代码还没下载完,但浏览器已经显示出内容了
重流和重绘及优化技巧
渲染树转换为网页布局,称为“布局流”(flow);布局显示到页面的这个过程,称为“绘制”(paint)。它们都具有阻塞效应,并且会耗费很多时间和计算资源。
页面生成以后,脚本操作和样式表操作,都会触发“重流”(reflow)和“重绘”(repaint)。用户的互动也会触发重流和重绘,比如设置了鼠标悬停(a:hover)效果、页面滚动、在输入框中输入文本、改变窗口大小等等。
重流和重绘并不一定一起发生,重流必然导致重绘,重绘不一定需要重流。比如改变元素颜色,只会导致重绘,而不会导致重流;改变元素的布局,则会导致重绘和重流。
优化技巧:
- 读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
- 缓存 DOM 信息。
- 不要一项一项地改变样式,而是使用 CSS class 一次性改变样式。
- 使用documentFragment操作 DOM
- 动画使用absolute定位或fixed定位,这样可以减少对其他元素的影响。
- 只在必要时才显示隐藏元素。
- 使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流。
- 使用虚拟 DOM(virtual DOM)库。
ex:
// 重绘代价高
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);
原型 原型链
原型
原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
ex:
function Cat(name, color) {
this.name = name;
this.color = color;
this.meow = function () {
console.log('喵喵');
};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow
// false
上面代码中,cat1和cat2是同一个构造函数的两个实例,它们都具有meow方法。由于meow方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。
这个问题的解决方法,就是 JavaScript 的原型对象(prototype)
JavaScript 规定,每个函数都有一个prototype属性,指向一个对象。
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
ex:
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
构造函数Animal的prototype属性,就是实例对象cat1和cat2的原型对象。原型对象上添加一个color属性,结果,实例对象都共享了该属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
ex:
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
总结:
- 当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。但是如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
- 原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象
原型链
JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的。
ps:Object.prototype的原型是null,null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。
总结:
- 读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
- 如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)
ps:一级级向上,在整个原型链上寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大
AJAX跨源通信
JSONP (只能发送get请求)
JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单易用,没有兼容性问题,老式浏览器全部支持,服务端改造非常小。
流程:
- 第一步,网页添加一个
<script>
元素,向服务器请求一个脚本,这不受同源政策限制(同样的还有img以及href等属性),可以跨域请求。 - 第二步,服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回
- 第三步,客户端会将服务器返回的字符串,作为代码解析,因为浏览器认为,这是
<script>
标签请求的脚本内容。这时,客户端只要定义了回调函数,就能在该函数体内,拿到服务器返回的 JSON 数据。
ex:
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于 JSONP 是必需的.同时作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用JSON.parse()的步骤。
WebSocket
WebSocket 是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的 WebSocket 请求的头信息
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以 WebSocket 才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
CORS
CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能
整个 CORS 通信过程,都是浏览器自动完成,对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。
CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
只要同时满足以下两大条件,就属于简单请求。反之则属于非简单请求。
(1)请求方法是以下三种方法之一。
- HEAD
- GET
- POST
(2)HTTP 的头信息不超出以下几种字段。
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求流程:
对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个Origin字段。
ex:
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面的头信息中,Origin字段用来说明,本次请求来自哪个域(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。这种错误无法通过状态码识别
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
上面的头信息之中,有三个与 CORS 请求相关的字段,都以Access-Control-开头。
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中
(3)Access-Control-Expose-Headers
该字段可选。CORS 请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个服务器返回的基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。
与JSONP比较
CORS 与 JSONP 的使用目的相同,但是比 JSONP 更强大。JSONP 只支持GET请求,CORS 支持所有类型的 HTTP 请求。JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。
非简单请求自行查阅
.native修饰符
官方对.native修饰符的解释为:
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符 .native 。例如:
<my-component v-on:click.native="doTheThing"></my-component>
简单理解为:.native修饰符是告诉编译器,前面的事件为原生的事件,不是自定义的事件,按照原生的方法解析便可。
css伪类伪元素
- CSS3 规范中,为了区分伪类和伪元素,大多数浏览器都支持这两种表示方式。单冒号(:)用于 CSS3 伪类,双冒号(::)用于 CSS3 伪元素
- 伪类本质上是为了弥补常规CSS选择器的不足,以便获取到更多信息;
- 伪元素本质上是创建了一个有内容的虚拟容器
- 可以同时使用多个伪类,而只能同时使用一个伪元素;
伪类
伪类存在的意义是为了通过选择器找到那些不存在与DOM树中的信息以及不能被常规CSS选择器获取到的信息。
常见的状态伪类主要包括:
- :link 应用于未被访问过的链接;
- :hover 应用于鼠标悬停到的元素;
- :active 应用于被激活的元素;
- :visited 应用于被访问过的链接,与:link互斥。
- :focus 应用于拥有键盘输入焦点的元素。
执行时记忆顺序:love 并且 hate(link -> visited -> hover -> active)
常见结构性伪类:是css3新增选择器
:first-child 选择某个元素的第一个子元素;
:last-child 选择某个元素的最后一个子元素;
:nth-child() 选择某个元素的一个或多个特定的子元素;
:nth-last-child() 选择某个元素的一个或多个特定的子元素,从这个元素的最后一个子元素开始算;
:nth-of-type() 选择指定的元素;
:nth-last-of-type() 选择指定的元素,从元素的最后一个开始计算;
:first-of-type 选择一个上级元素下的第一个同类子元素;
:last-of-type 选择一个上级元素的最后一个同类子元素;
:only-child 选择的元素是它的父元素的唯一一个子元素;
伪元素
常见的伪元素选择器:
- ::first-letter 选择元素文本的第一个字(母)。
- ::first-line 选择元素文本的第一行。
- ::before 在元素内容的最前面添加新内容。
- ::after 在元素内容的最后面添加新内容。
- ::selection匹配用户被用户选中或者处于高亮状态的部分
- ::placeholder匹配占位符的文本,只有元素设置了placeholder属性时,该伪元素才能生效
组件中的data为什么是一个函数而不是一个对象
首先官方是这么解释的
通俗的讲就是:
因为对象是一个引用数据类型,如果data是一个对象的情况下会造成所有组件共用一个data。而当data是一个函数的情况下,每次函数执行完毕后都会返回一个新的对象,这样的话每个组件都会维护一份独立的对象(data)
本文持续更新
博文参考
https://wangdoc.com/javascript/