值得注意的是,和⼤多数浏览器不同,Chrome 浏览器的每个标签⻚都分别对应⼀个呈现引擎实例。每个标签⻚都是⼀个独⽴的进程。
module.exports={
//入口文件的配置项
entry:{},
//出口文件的配置项
output:{},
//模块:例如解读CSS,图片如何转换,压缩
module:{},
//插件,用于生产模版和各项功能
plugins:[],
//配置webpack开发服务功能
devServer:{}
}
简单描述了一下这几个属性是干什么的。
描述一下npm run dev / npm run build执行的是哪些文件
通过配置proxyTable来达到开发环境跨域的问题,然后又可以扩展和他聊聊跨域的产生,如何跨域
最后可以在聊聊webpack的优化,例如babel-loader的优化,gzip压缩等等
响应式页面中经常用到根据屏幕密度设置不同的图片。这时就用到了 img 标签的srcset属性。srcset属性用于设置不同屏幕密度下,img 会自动加载不同的图片。用法如下:
<img src="image-128.png" srcset="image-256.png 2x" />
使用上面的代码,就能实现在屏幕密度为1x的情况下加载image-128.png, 屏幕密度为2x时加载image-256.png。
按照上面的实现,不同的屏幕密度都要设置图片地址,目前的屏幕密度有1x,2x,3x,4x四种,如果每一个图片都设置4张图片,加载就会很慢。所以就有了新的srcset标准。代码如下:
<img src="image-128.png"
srcset="image-128.png 128w, image-256.png 256w, image-512.png 512w"
sizes="(max-width: 360px) 340px, 128px" />
其中srcset指定图片的地址和对应的图片质量。sizes用来设置图片的尺寸零界点。对于 srcset 中的 w 单位,可以理解成图片质量。如果可视区域小于这个质量的值,就可以使用。浏览器会自动选择一个最小的可用图片。
sizes语法如下:
sizes="[media query] [length], [media query] [length] ... "
sizes就是指默认显示128px, 如果视区宽度大于360px, 则显示340px。
在了解setState之前,我们先来简单了解下 React 一个包装结构: Transaction:
事务 (Transaction)
是 React 中的一个调用结构,用于包装一个方法,结构为: initialize - perform(method) - close。通过事务,可以统一管理一个方法的开始与结束;处于事务流中,表示进程正在执行一些操作
异步与同步: setState并不是单纯的异步或同步,这其实与调用时的环境相关:
注意事项:
总结
setState 并非真异步,只是看上去像异步。在源码中,通过
isBatchingUpdates
来判断
setState
是先存进 state
队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。isBatchingUpdates
会为 true
呢?在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。addEventListener
、setTimeout
、setInterval
等事件中,就只能同步更新。一般认为,
做异步设计是为了性能优化、减少渲染次数
,React 团队还补充了两点。
setState
只有在 React 自身的合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步的setState
的异步并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的异步。当然可以通过 setState 的第二个参数中的 callback 拿到更新后的结果setState
的批量更新优化也是建立在异步(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在异步中如果对同一个值进行多次 setState,setState 的批量更新策略会对其进行覆盖,去最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新这是一道经常会出现的 React setState 笔试题:下面的代码输出什么呢?
class Test extends React.Component {
state = {
count: 0
};
componentDidMount() {
this.setState({count: this.state.count + 1});
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
setTimeout(() => {
this.setState({count: this.state.count + 1});
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
}, 0);
}
render() {
return null;
}
};
我们可以进行如下的分析:
console.log
,都在 React 的生命周期事件中,所以是异步的处理方式,则输出都为 0
;setTimeout
中的 console.log
处于原生事件中,所以会同步的处理再输出结果,但需要注意,虽然 count
在前面经过了两次的 this.state.count + 1
,但是每次获取的 this.state.count
都是初始化时的值,也就是 0
;count
是 1
,那么后续在 setTimeout
中的输出则是 2
和 3
。所以完整答案是 0,0,2,3
同步场景
异步场景中的案例使我们建立了这样一个认知:setState 是异步的,但下面这个案例又会颠覆你的认知。如果我们将 setState 放在 setTimeout 事件中,那情况就完全不同了。
class Test extends Component {
state = {
count: 0
}
componentDidMount(){
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
console.log("setTimeout: " + this.state.count);
}, 0);
}
render(){
...
}
}
那这时输出的应该是什么呢?如果你认为是 0,0,那么又错了。
正确的结果是 0,2
。因为 setState
并不是真正的异步函数,它实际上是通过队列延迟执行操作实现的,通过 isBatchingUpdates 来判断 setState 是先存进 state 队列还是直接更新。值为 true 则执行异步操作,false 则直接同步更新
接下来这个案例的答案是什么呢
class Test extends Component {
state = {
count: 0
}
componentDidMount(){
this.setState({
count: this.state.count + 1
}, () => {
console.log(this.state.count)
})
this.setState({
count: this.state.count + 1
}, () => {
console.log(this.state.count)
})
}
render(){
...
}
}
如果你觉得答案是 1,2
,那肯定就错了。这种迷惑性极强的考题在面试中非常常见,因为它反直觉。
如果重新仔细思考,你会发现当前拿到的 this.state.count
的值并没有变化,都是 0
,所以输出结果应该是 1,1
。
当然,也可以在 setState
函数中获取修改后的 state
值进行修改。
class Test extends Component {
state = {
count: 0
}
componentDidMount(){
this.setState(
preState=> ({
count:preState.count + 1
}),()=>{
console.log(this.state.count)
})
this.setState(
preState=>({
count:preState.count + 1
}),()=>{
console.log(this.state.count)
})
}
render(){
...
}
}
这些通通是异步的回调,如果你以为输出结果是 1,2,那就又错了,实际上是 2,2
。
为什么会这样呢?当调用 setState
函数时,就会把当前的操作放入队列中
。React 根据队列内容,合并 state 数据,完成后再逐一执行回调,根据结果更新虚拟 DOM,触发渲染。所以回调时,state 已经合并计算完成了
,输出的结果就是 2,2
了。
应该有面试官问过你:
这些问题其实都可以被看作是同一个问题,那就是面试官在问你:你对JS闭包了解多少?
来总结一下我听到过的答案,尽量完全复原候选人面试的时候说的原话。
答案1:
就是一个function
里面return
了一个子函数,子函数访问了外面那个函数的变量。
答案2:
for循环里面可以用闭包来解决问题。
for(var i = 0; i < 10; i++){
setTimeout(()=>console.log(i),0)
}
// 控制台输出10遍10.
for(var i = 0; i < 10; i++){
(function(a){
setTimeout(()=>console.log(a),0)
})(i)
}
// 控制台输出0-9
答案3:
当前作用域产产生了对父作用域的引用。
答案4:
不知道。是跟浏览器的垃圾回收机制有关吗?
开杠了。请问,小伙伴的答案和以上的内容有多少相似程度?
其实,拿着这些问题好好想想,你就会发现这些问题都只是为了最终那一个问题。
闭包的底层实现原理
1. JS执行上下文
我们都知道,我们手写的js代码是要经过浏览器V8进行预编译后才能真正的被执行。例如变量提升、函数提升。举个栗子。
// 栗子:
var d = 'abc';
function a(){
console.log("函数a");
};
console.log(a); // ƒ a(){ console.log("函数a"); }
a(); // '函数a'
var a = "变量a";
console.log(a); // '变量a'
a(); // a is not a function
var c = 123;
// 输出结果及顺序:
// ƒ a(){ console.log("函数a"); }
// '函数a'
// '变量a'
// a is not a function
// 栗子预编后相当于:
function a(){
console.log("函数a");
};
var d;
console.log(a); // ƒ a(){ console.log("函数a"); }
a(); // '函数a'
a = "变量a"; // 此时变量a赋值,函数声明被覆盖
console.log(a); // "变量a"
a(); // a is not a function
那么问题来了。 请问是谁来执行预编译操作的?那这个谁又是在哪里进行预编译的?
是的,你的疑惑没有错。js代码运行需要一个运行环境,那这个环境就是执行上下文。 是的,js运行前的预编译也是在这个环境中进行。
js执行上下文分三种:
全局执行上下文
: 代码开始执行时首先进入的环境。函数执行上下文
:函数调用时,会开始执行函数中的代码。eval执行上下文
:不建议使用,可忽略。那么,执行上下文的周期,分为两个阶段:
创建阶段
VO
),建立作用域链、作用域链、作用域链(重要的事说三遍)this
指向,并绑定this
执行阶段
。这个阶段进行变量赋值,函数引用及执行代码。你现在猜猜看,预编译是发生在什么时候?
噢,我忘记说了,其实与编译还有另一个称呼:执行期上下文
。
预编译发生在函数执行之前。预编译四部曲为:
AO
对象undefined
AO
对象中拿。所以,预编译真正的结果是:
var AO = {
a = function a(){console.log("函数a");};
d = 'abc'
}
我们重新来。
变量对象是 js
代码在进入执行上下文时,js
引擎在内存中建立的一个对象,用来存放当前执行环境中的变量。
变量对象的创建,是在执行上下文创建阶段,依次经过以下三个过程:
arguments
对象。 对于函数执行环境,首先查询是否有传入的实参,如果有,则会将参数名是实参值组成的键值对放入arguments
对象中。否则,将参数名和 undefined
组成的键值对放入 arguments
对象中。
//举个栗子
function bar(a, b, c) {
console.log(arguments); // [1, 2]
console.log(arguments[2]); // undefined
}
bar(1,2)
console.log(a); // function a() {console.log('Is a ?') }
function a() {
console.log('Is a');
}
function a() {
console.log('Is a ?')
}
/**ps: 在执行第一行代码之前,函数声明已经创建完成.后面的对之前的声明进行了覆盖。**/
undefined
。当遇到同名的函数声明,为了避免函数被赋值为 undefined
,会忽略此声明console.log(a); // function a() {console.log('Is a ?') }
console.log(b); // undefined
function a() {
console.log('Is a ');
}
function a() {
console.log('Is a ?');
}
var b = 'Is b';
var a = 10086;
/**这段代码执行一下,你会发现 a 打印结果仍旧是一个函数,而 b 则是 undefined。**/
根据以上三个步骤,对于变量提升也就知道是怎么回事了。
执行上下文的第二个阶段,称为执行阶段,在此时,会进行变量赋值,函数引用并执行其他代码,此时,变量对象变为活动对象。
我们还是举上面的例子:
console.log(a); // function a() {console.log('fjdsfs') }
console.log(b); // undefined
function a() {
console.log('Is a');
}
function a() {
console.log('Is a?');
}
var b = 'Is b';
console.log(b); // 'Is b'
var a = 10086;
console.log(a); // 10086
var b = 'Is b?';
console.log(b); // 'Is b?'
在上面的代码中,代码真正开始执行是从第一行 console.log() 开始的,自这之前,执行上下文是这样的:
// 创建过程
EC= {
VO: {}; // 创建变量对象
scopeChain: {}; // 作用域链
}
VO = {
argument: {...}; // 当前为全局上下文,所以这个属性值是空的
a: <a reference> // 函数 a 的引用地址 b: undefiend // 见上文创建变量对象的第三步}
Lexical scope
)这里想说明,我们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript
的一个复杂之处在于它如何查找变量,如果在函数执行上下文中找不到变量,它将在调用上下文中寻找它,如果在它的调用上下文中没有找到,就一直往上一级,直到它在全局执行上下文中查找为止。(如果最后找不到,它就是 undefined
)。
再来举个栗子:
1: let top = 0; //
2: function createWarp() {
3: function add(a, b) {
4: let ret = a + b
5: return ret
6: }
7: return add
8: }
9: let sum = createWarp()
10: let result = sum(top, 8)
11: console.log('result:',result)
分析过程如下:
top
并赋值为0.createWarp
的变量,并为其分配了一个函数定义。其中第3-7行描述了其函数定义,并将函数定义存储到那个变量(createWarp
)中。sum
的新变量,暂时,值为 undefined
。()
,表明需要执行或调用一个函数。那么查找全局执行上下文的内存并查找名为 createWarp
的变量。 明显,已经在步骤2中创建完毕。接着,调用它。createWarp
执行上下文。我们可以在 createWarp
的执行上下文中创建自有变量。js
引擎createWarp
的上下文添加到调用堆栈(call stack
)。因为这个函数没有参数,直接跳到它的主体部分.createWarp
执行上下文中创建一个变量 add
。add
只存在于 createWarp
执行上下文中, 其函数定义存储在名为 add
的自有变量中。add
的内容。js引擎查找一个名为 add
的变量并找到它. 第4行和第5行括号之间的内容构成该函数定义。createWarp
调用完毕,createWarp
执行上下文将被销毁。add 变量也跟着被销毁。 但 add
函数定义仍然存在,因为它返回并赋值给了 sum
变量。 (ps: 这才是闭包产生的变量存于内存当中的真相
)小结一下
现在,如果再让你回答什么是闭包,你能答出多少?
其实,大家说的都对。不管是函数返回一个函数,还是产生了外部作用域的引用,都是有道理的。
所以,什么是闭包?
DOM 节点的获取的API及使用:
getElementById // 按照 id 查询
getElementsByTagName // 按照标签名查询
getElementsByClassName // 按照类名查询
querySelectorAll // 按照 css 选择器查询
// 按照 id 查询
var imooc = document.getElementById('imooc') // 查询到 id 为 imooc 的元素
// 按照标签名查询
var pList = document.getElementsByTagName('p') // 查询到标签为 p 的集合
console.log(divList.length)
console.log(divList[0])
// 按照类名查询
var moocList = document.getElementsByClassName('mooc') // 查询到类名为 mooc 的集合
// 按照 css 选择器查询
var pList = document.querySelectorAll('.mooc') // 查询到类名为 mooc 的集合
创建一个新节点,并把它添加到指定节点的后面。 已知的 HTML 结构如下:
<html>
<head>
<title>DEMO</title>
</head>
<body>
<div id="container">
<h1 id="title">我是标题</h1>
</div>
</body>
</html>
要求添加一个有内容的 span 节点到 id 为 title 的节点后面,做法就是:
// 首先获取父节点
var container = document.getElementById('container')
// 创建新节点
var targetSpan = document.createElement('span')
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'
// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan)
删除指定的 DOM 节点, 已知的 HTML 结构如下:
<html>
<head>
<title>DEMO</title>
</head>
<body>
<div id="container"> <h1 id="title">我是标题</h1>
</div> </body>
</html>
需要删除 id 为 title 的元素,做法是:
// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)
或者通过子节点数组来完成删除:
// 获取目标元素的父元素var container = document.getElementById('container')// 获取目标元素var targetNode = container.childNodes[1]// 删除目标元素container.removeChild(targetNode)
修改 DOM 元素这个动作可以分很多维度,比如说移动 DOM 元素的位置,修改 DOM 元素的属性等。
将指定的两个 DOM 元素交换位置, 已知的 HTML 结构如下:
<html>
<head>
<title>DEMO</title>
</head>
<body>
<div id="container"> <h1 id="title">我是标题</h1>
<p id="content">我是内容</p>
</div> </body>
</html>
现在需要调换 title 和 content 的位置,可以考虑 insertBefore 或者 appendChild:
// 获取父元素
var container = document.getElementById('container')
// 获取两个需要被交换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')
// 交换两个元素,把 content 置于 title 前面
container.insertBefore(content, title)
JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是ES6 中新增的数据类型:
这些数据可以分为原始数据类型和引用数据类型:
两种类型的区别在于存储位置的不同:
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
在操作系统中,内存被分为栈区和堆区:
【1】队头堵塞:
队头阻塞是由 HTTP 基本的“请求 - 应答”模型所导致的。HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求是没有优先级的,只有入队的先后顺序,排在最前面的请求会被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本,造成了队头堵塞的现象。
在 Vue3.0 中通过 Proxy
来替换原本的 Object.defineProperty
来实现数据响应式。
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
let p = new Proxy(target, handler)
target
代表需要添加代理的对象,handler
用来自定义对象中的操作,比如可以用来自定义 set
或者 get
函数。
下面来通过 Proxy
来实现一个数据响应式:
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(`监听到属性${property}改变为${v}`)
},
(target, property) => {
console.log(`'${property}' = ${target[property]}`)
}
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2
在上述代码中,通过自定义 set
和 get
函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get
中收集依赖,在 set
派发更新,之所以 Vue3.0 要使用 Proxy
替换原本的 API 原因在于 Proxy
无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy
可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。
Promise.resolve = function(value) {
// 1.如果 value 参数是一个 Promise 对象,则原封不动返回该对象
if(value instanceof Promise) return value;
// 2.如果 value 参数是一个具有 then 方法的对象,则将这个对象转为 Promise 对象,并立即执行它的then方法
if(typeof value === "object" && 'then' in value) {
return new Promise((resolve, reject) => {
value.then(resolve, reject);
});
}
// 3.否则返回一个新的 Promise 对象,状态为 fulfilled
return new Promise(resolve => resolve(value));
}
网页加载流程
<head>
元素内容会先被解析,此时浏览器还没开始渲染页面
<head>
元素里有用于描述页面元数据的<meta>
元素,还有一些<link>
元素涉及外部资源(如图片、CSS 样式
等),此时浏览器会去获取这些外部资源。除此之外,我们还能看到<head>
元素中还包含着不少的<script>
元素,这些<script>
元素通过src
属性指向外部资源HTML
页面<body>
元素内容开始被解析,浏览器开始渲染页面在这个过程中,我们看到
<head>
中放置的<script>
元素会阻塞页面的渲染过程:把 JavaScript 放在<head>
里,意味着必须把所有 JavaScript 代码都下载、解析和解释完成后,才能开始渲染页面
。 如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应,浏览器就会呈现“假死”状态,用户体验会变得很糟糕 因此,对于对性能要求较高、需要快速将内容呈现给用户的网页,常常会将 JavaScript 脚本放在<body>
的最后面。这样可以避免资源阻塞,页面得以迅速展示
。我们还可以使用defer/async/preload
等属性来标记<script>
标签,来控制 JavaScript 的加载顺序
延迟加载的方式有哪些
js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
几种方式是:
defer
属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer
属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样async
属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async
属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行DOM
标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script
标签来引入 js 脚本怎么判断页面是否加载完成
Load
事件触发代表页面中的 DOM
,CSS
,JS
,图片已经全部加载完毕。DOMContentLoaded
事件触发代表初始的 HTML
被完全加载和解析,不需要等待 CSS
,JS
,图片加载实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,CSS3 中可以使用 transition 和 animation 来实现,HTML5 中的 canvas 也可以实现。除此之外,HTML5 提供一个专门用于请求动画的API,那就是 requestAnimationFrame,顾名思义就是请求动画帧。
MDN对该方法的描述:
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
语法: window.requestAnimationFrame(callback);
其中,callback是下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,它表示requestAnimationFrame() 开始去执行回调函数的时刻。该方法属于宏任务,所以会在执行完微任务之后再去执行。
取消动画: 使用cancelAnimationFrame()来取消执行动画,该方法接收一个参数——requestAnimationFrame默认返回的id,只需要传入这个id就可以取消动画了。
优势:
setTimeout执行动画的缺点:它通过设定间隔时间来不断改变图像位置,达到动画效果。但是容易出现卡顿、抖动的现象;原因是:
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
(1)Promise的实例有三个状态:
当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。
(2)Promise的实例有两个过程:
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
Promise的特点:
pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来——“承诺”;pending
变为fulfilled
,从pending
变为rejected
。这时就称为resolved
(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的。Promise的缺点:
总结: Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。
状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。
注意: 在构造 Promise
的时候,构造函数内部的代码是立即执行的
当一个内部函数被调用,就会形成闭包,闭包就是能够读取其他函数内部变量的函数。
闭包作用:
局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。
p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}
a:hover {color: #FF00FF}
p:first-child {color: red}
总结: 伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变。
媒体查询由⼀个可选的媒体类型和零个或多个使⽤媒体功能的限制了样式表范围的表达式组成,例如宽度、⾼度和颜⾊。媒体查询,添加⾃CSS3,允许内容的呈现针对⼀个特定范围的输出设备⽽进⾏裁剪,⽽不必改变内容本身,适合web⽹⻚应对不同型号的设备⽽做出对应的响应适配。
媒体查询包含⼀个可选的媒体类型和满⾜CSS3规范的条件下,包含零个或多个表达式,这些表达式描述了媒体特征,最终会被解析为true或false。如果媒体查询中指定的媒体类型匹配展示⽂档所使⽤的设备类型,并且所有的表达式的值都是true,那么该媒体查询的结果为true。那么媒体查询内的样式将会⽣效。
<!-- link元素中的CSS媒体查询 -->
<link rel="stylesheet" media="(max-width: 800px)" href="example.css" />
<!-- 样式表中的CSS媒体查询 -->
<style>
@media (max-width: 600px) { .facet_sidebar { display: none; } }
</style>
简单来说,使用 @media 查询,可以针对不同的媒体类型定义不同的样式。@media 可以针对不同的屏幕尺寸设置不同的样式,特别是需要设置设计响应式的页面,@media 是非常有用的。当重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。
基本概念:
HTTP,全称为 HyperText Transfer Protocol,即为超文本传输协议。是互联网应用最为广泛的一种网络协议
所有的 www 文件都必须遵守这个标准。
http特性:
HTTP 是无连接无状态的
HTTP 一般构建于 TCP/IP 协议之上,默认端口号是 80
HTTP 可以分为两个部分,即请求和响应。
http请求:
HTTP 定义了在与服务器交互的不同方式,最常用的方法有 4 种
分别是 GET,POST,PUT, DELETE。URL 全称为资源描述符,可以这么认为:一个 URL 地址
对应着一个网络上的资源,而 HTTP 中的 GET,POST,PUT,DELETE
就对应着对这个资源的查询,修改,增添,删除4个操作。
HTTP 请求由 3 个部分构成,分别是:状态行,请求头(Request Header),请求正文。
HTTP 响应由 3 个部分构成,分别是:状态行,响应头(Response Header),响应正文。
HTTP 响应中包含一个状态码,用来表示服务器对客户端响应的结果。
状态码一般由3位构成:
1xx : 表示请求已经接受了,继续处理。
2xx : 表示请求已经处理掉了。
3xx : 重定向。
4xx : 一般表示客户端有错误,请求无法实现。
5xx : 一般为服务器端的错误。
比如常见的状态码:
200 OK 客户端请求成功。
301 Moved Permanently 请求永久重定向。
302 Moved Temporarily 请求临时重定向。
304 Not Modified 文件未修改,可以直接使用缓存的文件。
400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized 请求未经授权,无法访问。
403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因。
404 Not Found 请求的资源不存在,比如输入了错误的URL。
500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。
503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。
大概还有一些关于http请求和响应头信息的介绍。
Service workers
本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API
浏览器对 ServiceWorker 做了很多限制
ServiceWorker
中无法直接访问 DOM
,但可以通过 postMessage
接口发送的消息来与其控制的页面进行通信ServiceWorker
只能在本地环境下或 HTTPS
网站中使用ServiceWorker
有作用域的限制,一个 ServiceWorker
脚本只能作用于当前路径及其子路径;目前该技术通常用来做缓存文件,提高首屏速度
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("sw.js")
.then(function(registration) {
console.log("service worker 注册成功");
})
.catch(function(err) {
console.log("servcie worker 注册失败");
});
}
// sw.js
// 监听 `install` 事件,回调中缓存所需文件
self.addEventListener("install", e => {
e.waitUntil(
caches.open("my-cache").then(function(cache) {
return cache.addAll(["./index.html", "./index.js"]);
})
);
});
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response) {
return response;
}
console.log("fetch source");
})
);
});
打开页面,可以在开发者工具中的
Application
看到Service Worker
已经启动了
在 Cache 中也可以发现我们所需的文件已被缓存
当我们重新刷新页面可以发现我们缓存的数据是从
Service
Worker
中读取的
优点:
1.体验好,不刷新,减少 请求 数据ajax异步获取 页面流程;
2.前后端分离
3.减轻服务端压力
4.共用一套后端程序代码,适配多端
缺点:
1.首屏加载过慢;
2.SEO 不利于搜索引擎抓取
HTTPS的优点如下:
HTTPS的缺点如下:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。