线程与进程
我们可以把计算机自下而上分成三层:硬件、操作系统和应用。有了操作系统的存在,上层运行的应用可以使用操作系统提供的能力使用硬件资源而不会直接访问硬件资源。
线程与进程 一个进程是应用正在运行的程序。而线程是进程中更小的一部分。当应用被启动,进程就被创建出来。程序可以创建线程来帮助其工作。操作系统会为进程分配私有的内存空间以供使用,当关闭程序时,这段私有的内存也会被释放。其实还有比线程更小的存在就是协程,而协成是运行在线程中更小的单位。async/await 就是基于协程实现的
浏览器架构
借助进程和线程,浏览器可以被设计成单进程、多线程架构,或者利用 IPC 实现多进程、多线程架构 进程工作内容
浏览器进程
浏览器进程负责管理 Chrome 应用本身,包括地址栏、书签、前进和后退按钮。同时也负责可不见的功能,比如网络请求、文件按访问等,也负责其他进程的调度。渲染进程
渲染进程负责站点的渲染,其中也包括 JavaScript 代码的运行,web worker 的管理等。插件进程
插件进程负责为浏览器提供各种额外的插件功能,例如 flash。GPU进程
进程负责提供成像的功能。
异步编程六种方式
回调函数
ajax(url, () => {
// 处理逻辑
})
回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return
事件监听
elment.addEvenListener("click",handler3,false);
这种方式下,异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生 这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
发布订阅 又称"观察者模式"(observer pattern)
首先,f2向信号中心jQuery订阅done信号。
jQuery.subscribe('done', f2);
然后,f1进行如下改写
function f1() {
setTimeout(function () {
// ...
jQuery.publish('done');
}, 1000);
}
上面代码中,jQuery.publish('done')的意思是,f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。 f2完成执行后,可以取消订阅(unsubscribe) Promise/A+
- Pending----Promise对象实例创建时候的初始状态
- Fulfilled----可以理解为成功的状态
- Rejected----可以理解为失败的状态 promse 的链式调用
- 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)
- 如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调
- 如果then中出现异常,会走下一个then的失败回调
- 在 then中使用了return,那么 return 的值会被Promise.resolve() 包装(见例1,2)
- then中可以不传递参数,如果不传递会透到下一个then中(见例3)
- catch 会捕获到没有捕获的异常
Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,可以把之前的回调地狱例子改 生成器Generators/ yield Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。
- 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
- Generator 函数除了状态机,还是一个遍历器对象生成函数。
- 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。
- yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。 async/await
使用async/await,你可以轻松地达成之前使用生成器和co函数所做到的工作,它有如下特点:
- async/await是基于Promise实现的,它不能用于普通的回调函数。
- async/await与Promise一样,是非阻塞的。
- async/await使得异步代码看起来像同步代码,这正是它的魔力所在。
一个函数如果加上 async ,那么该函数就会返回一个 Promise
let fs = require('fs')
function read(file) {
return new Promise(function(resolve, reject) {
fs.readFile(file, 'utf8', function(err, data) {
if (err) reject(err)
resolve(data)
})
})
}
async function readResult(params) {
try {
let p1 = await read(params, 'utf8')//await后面跟的是一个Promise实例
let p2 = await read(p1, 'utf8')
let p3 = await read(p2, 'utf8')
console.log('p1', p1)
console.log('p2', p2)
console.log('p3', p3)
return p3
} catch (error) {
console.log(error)
}
}
readResult('1.txt').then( // async函数返回的也是个promise
data => {
console.log(data)
},
err => console.log(err)
)
// p1 2.txt
// p2 3.txt
// p3 结束
// 结束
总结 1.JS 异步编程进化史:callback -> promise -> generator -> async + await
2.async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
3.async/await可以说是异步终极解决方案了。
(1) async/await函数相对于Promise,优势体现在:
- 处理 then 的调用链,能够更清晰准确的写出代码
- 并且也能优雅地解决回调地狱问题。
当然async/await函数也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。
(2) async/await函数对 Generator 函数的改进,体现在以下三点:
- 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
- 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作) 。
- 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
nodejs
NodeJs是 JavaScript 的运行平台 原来 JavaScript 只能运行在浏览器中,对操作系统的权限不够,不能存储文件,nodejs 相当于一个平台,可以让 JavaScript 直接和操作系统对话,并且完成了跨平台在 window ,macOS , Linux(服务器)部署
前端路由的两种模式: hash 模式和history 模式
为什么要使用路由 单页面应用利用了JavaScript动态变换网页内容,避免了页面重载;路由则提供了浏览器地址变化,网页内容也跟随变化,两者结合起来则为我们提供了体验良好的单页面web应用 前端路由实现方式 路由需要实现三个功能
- 当浏览器地址变化时,切换页面
- 点击浏览器【后退】,【前进】按钮,网页内容跟随变化
- 刷新浏览器,网页加载当前路由对应的内容 路由切换两种实现方式
- hash模式:监听浏览器地址hash值变化,执行相应的js切换网页;
- history模式:利用history API实现url地址改变,网页内容改变;
它们的区别最明显的就是hash会在浏览器地址后面增加#号,而history可以自定义地址
hash 模式 使用window.location.hash属性及窗口的onhashchange事件,可以实现监听浏览器地址hash值变化,执行相应的js切换网页。下面具体介绍几个使用过程中必须理解的要点:
1、hash指的是地址中#号以及后面的字符,也称为散列值。hash也称作锚点,本身是用来做页面跳转定位的。如http://localhost/index.html#abc,这里的#abc就是hash;
2、散列值是不会随请求发送到服务器端的,所以改变hash,不会重新加载页面;
3、监听 window 的 hashchange 事件,当散列值改变时,可以通过 location.hash 来获取和设置hash值;
4、location.hash值的变化会直接反应到浏览器地址栏;
//设置 url 的 hash,会在当前url后加上'#abc'
window.location.hash='abc';
let hash = window.location.hash //'#abc'
window.addEventListener('hashchange',function(){
//监听hash变化,点击浏览器的前进后退会触发
})
history 模式
概述
1、window.history 属性指向 History 对象,它表示当前窗口的浏览历史。当发生改变时,只会改变页面的路径,不会刷新页面。
2、History 对象保存了当前窗口访问过的所有页面网址。通过 history.length 可以得出当前窗口一共访问过几个网址。
3、由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。
4、浏览器工具栏的“前进”和“后退”按钮,其实就是对 History 对象进行操作。
属性
History.length
:当前窗口访问过的网址数量(包括当前网页)History.state
:History 堆栈最上层的状态值(详见下文)
方法
- History.back():移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。
- History.forward():移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。
- History.go():接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为0,相当于刷新当前页面
改变 history.state 的方法有 history.pushState()
该方法用于在历史中添加一条记录。pushState()
方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有变化
var data = { foo: 'bar' };
history.pushState(data, '', '2.html');
console.log(history.state) // {foo: "bar"}
history.replaceState
该方法用来修改 History 对象的当前记录,用法与 pushState() 方法一样。
history.pushState({page: 2}, '', '?page=2');
// URL 显示为 http://example.com/example.html?page=2
history.replaceState({page: 3}, '', '?page=3');
// URL 显示为 http://example.com/example.html?page=3
popstate
每当 history 对象出现变化时,就会触发 popstate 事件
window.addEventListener('popstate', function(e) {
//e.state 相当于 history.state
console.log('state: ' + JSON.stringify(e.state));
console.log(history.state);
});
flex: 0 1 auto 是什么意思
flex属性 是 flex-grow、flex-shrink、flex-basis三个属性的缩写。
flex-grow:定义项目的的放大比例;
- 默认为0,即 即使存在剩余空间,也不会放大;
- 所有项目的flex-grow为1:等分剩余空间(自动放大占位);
- flex-grow为n的项目,占据的空间(放大的比例)是flex-grow为1的n倍。
flex-shrink:定义项目的缩小比例;
- 默认为1,即 如果空间不足,该项目将缩小;
- 所有项目的flex-shrink为1:当空间不足时,缩小的比例相同;
- flex-shrink为0:空间不足时,该项目不会缩小;
- flex-shrink为n的项目,空间不足时缩小的比例是flex-shrink为1的n倍。
flex-basis: 定义在分配多余空间之前,项目占据的主轴空间(main size),浏览器根据此属性计算主轴是否有多余空间,
- 默认值为auto,即 项目原本大小;
- 设置后项目将占据固定空间。
所以flex属性的默认值为:0 1 auto (不放大会缩小)
flex为none:0 0 auto (不放大也不缩小)
flex为auto:1 1 auto (放大且缩小)
flex:1 即为flex-grow:1,经常用作自适应布局,将父容器的display:flex,侧边栏大小固定后,将内容区flex:1,内容区则会自动放大占满剩余空间
箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?
-
普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行)
-
箭头函数使用被称为 “胖箭头” 的操作
=>
定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无法被修改(new 也不行)。-
箭头函数常用于回调函数中,包括事件处理器或定时器
-
箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
-
没有原型、没有 this、没有 super,没有 arguments,没有 new.target
-
不能通过 new 关键字调用
- 一个函数内部有两个方法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上
- 当直接调用时,执行 [[Call]] 方法,直接执行函数体
- 箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。
-
function foo() {
return (a) => {
console.log(this.a);
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1);
bar.call(obj2);
// 2
JS相关
原生数组方法
栈方法
push方法
push方法可以接收一个或多个参数, 把它们追加到数组末尾, 并返回修改后数组的长度.
pop 方法
pop方法是将数组的最后一项移除, 将数组长度减1, 并返回移除的项.
队列方法
shift方法
shift方法将移除数组的第一项, 将数组长度减1, 并返回移除的项.
unshift方法
unshift也可以在接收一个或多个参数, 把它们依次添加到数组的前端, 并返回修改后数组的长度
重排序方法
reverse方法
是用来反转数组的.
sort方法
默认情况下, 它是对数组的每一项进行升序排列, 即最小的值在前面. 但sort方法会调用toString方法将每一项转成字符串进行比较(字符串通过Unicode位点进行排序), 那么这种比较方案在多数情况下并不是最佳方案. 例如:
var arr = [1, 3, 2, 5, 4]; arr.sort();
console.info(arr); // [1, 2, 3, 4, 5]
arr = [1, 5, 10, 20, 25, 30];
arr.sort();
console.info(arr); // [1, 10, 20, 25, 30, 5]
因此, sort方法可以接收一个比较函数作为参数, 由我们来决定排序的规则. 比较函数接收两个参数, 如果第一个参数小于第二个参数 (即第一个参数应在第二个参数之前) 则返回一个负数, 如果两个参数相等则返回0, 如果第一个参数大于第二个参数则返回一个正数
var arr = [1, 5, 10, 20, 25, 30];
arr.sort(function(value1, value2){
if(value1 < value2) {
return -1;
} else if(value1 > value2) {
return 1;
} else {
return 0;
}
});
console.info(arr); // [1, 5, 10, 20, 25, 30]
操作方法
concat方法
concat方法可以将多个数组合并成一个新的数组. concat可以接收的参数可以是数组, 也可以是非数组值.concat方法并不操作原数组, 而是新创建一个数组, 然后将调用它的对象中的每一项以及参数中的每一项或非数组参数依次放入新数组中, 并且返回这个新数组
slice方法
slice方法可以基于源数组中的部分元素, 对其进行浅拷贝, 返回包括从开始到结束(不包括结束位置)位置的元素的新数组.slice方法同样不操作调用它的数组本身, 而是将原数组的每个元素拷贝一份放到新创建的数组中. 而拷贝的过程, 也于concat方法相同.
splice 方法
splice方法可以用途删除或修改数组元素.接收三个参数
- 要删除的第一个元素的位置
- 要删除的项数
- 要插入的元素, 如果要插入多个元素可以添加更多的参数
var arr = ['a', 'b', 'c', 'd', 'e'];
var temp = arr.splice(2, 0, 'x', 'y', 'z');
console.info(arr); // ["a", "b", "x", "y", "z", "c", "d", "e"]
console.info(temp); // [], 并没有删除元素
位置方法
indexOf() lastIndexOf() 两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引,只indexOf() 方法从数组的开头(位置 0)开始向后查找, lastIndexOf() 方法则从数组的末尾开始向前查找,返回要查找项在数组中的位置,没找到会返回-1。采用全等操作符比较查找。
迭代方法
every() :对数组中的每一项运行给定函数,如果该函数对每一项都返回 true ,则返回 true 。
filter() :对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。
forEach() :对数组中的每一项运行给定函数。这个方法没有返回值。
map() :对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
some() :对数组中的每一项运行给定函数,如果该函数对任一项返回 true ,则返回 true 。
以上方法都不会修改数组中的包含的值。
每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。
9.归并方法
reduce() reduceRight() 这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中, reduce() 方法从数组的第一项开始,逐个遍历到最后。而 reduceRight() 则从数组的最后一项开始,向前遍历到第一项。
这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。传给 reduce() 和 reduceRight() 的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。
例子:
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
console.log(sum); //15
js数据类型、typeof、instanceof、类型转换
string、number、boolean、null、undefined、object(function、array)、symbol(ES10 BigInt)
typeof
主要用来判断数据类型 返回值有string、boolean、number、function、object、undefined。
instanceof
判断该对象是谁的实例。null
表示空对象undefined
表示已在作用域中声明但未赋值的变量
typeof 原理
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息👉
- 000:对象
- 010:浮点数
- 100:字符串
- 110:布尔
- 1:整数
but, 对于 undefined
和 null
来说,这两个值的信息存储是有点特殊的。
null
:所有机器码均为0
undefined
:用 −2^30 整数来表示
所以,typeof
在判断 null
的时候就出现问题了,由于 null
的所有机器码均为0,因此直接被当做了对象来看待,因此在用 typeof
来判断变量类型的时候,我们需要注意,最好是用 typeof
来判断基本数据类型(包括symbol
),避免对 null 的判断
还有一个不错的判断类型的方法,就是Object.prototype.toString,我们可以利用这个方法来对一个变量的类型来进行比较准确的判断
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('hi') // "[object String]"
Object.prototype.toString.call({a:'hi'}) // "[object
Object]" Object.prototype.toString.call([1,'a']) // "[object Array]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(() => {}) // "[object Function]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"
instanceof原理
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__ }
}
其实 instanceof
主要的实现原理就是只要右边变量的 prototype
在左边变量的原型链上即可。因此,instanceof
在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype
,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。
数组扁平化处理
flat
arr.flat(Infinity) // Infinity 的值为多少,就降维多少
const numbers = [1, 2, [3, 4, [5, 6]]];
// Considers default depth of 1
numbers.flat();
> [1, 2, 3, 4, [5, 6]]
// With depth of 2
numbers.flat(2);
> [1, 2, 3, 4, 5, 6]
// Executes two flat operations
numbers.flat().flat();
> [1, 2, 3, 4, 5, 6]
// Flattens recursively until the array contains no nested arrays
numbers.flat(Infinity)
> [1, 2, 3, 4, 5, 6]
reduce
遍历数组每一项,若值为数组则递归遍历,否则concat
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
reduce是数组的一种方法,它接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值 reduce包含两个参数:回调函数,传给total的初始值
// 求数组的各项值相加的和:
arr.reduce((total, item)=> { // total为之前的计算结果,item为数组的各项值
return total + item;
}, 0);
toString & split
调用数组的toString方法,将数组变为字符串然后再用split分割还原为数组
function flatten(arr) {
return arr.toString().split(',').map(function(item) {
return Number(item);
})
}
因为split分割后形成的数组的每一项值为字符串,所以需要用一个map方法遍历数组将其每一项转换为数值型
join & split
和上面的toString一样,join也可以将数组转换为字符串
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item);
})
}
递归
递归的遍历每一项,若为数组则继续遍历,否则concat
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
扩展运算符
es6的扩展运算符能将二维数组变为一维
[].concat(...[1, 2, 3, [4, 5]]); // [1, 2, 3, 4, 5]
根据这个结果我们可以做一个遍历,若arr中含有数组则使用一次扩展运算符,直至没有为止。
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
总结
虽然说写了5种方法,但是核心也只有一个:
遍历数组arr,若arr[i]为数组则递归遍历,直至arr[i]不为数组然后与之前的结果concat
闭包(高频)
闭包是指有权访问另一个函数作用域中的变量的函数 ——《JavaScript高级程序设计》
当函数可以记住并访问所在的词法作用域时,就产生了闭包,
即使函数是在当前词法作用域之外执行 ——《你不知道的JavaScript》
闭包 = 函数 + 函数能够访问的自由变量
ECMAScript中,闭包指的是:
-
从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
-
从实践角度:以下函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
- 在代码中引用了自由变量
-
闭包用途:
- 能够访问函数定义时所在的词法作用域(阻止其被回收)
- 私有化变量
- 模拟块级作用域
- 创建模块
-
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
原型、原型链(高频)
prototype: 每个函数都有一个 prototype 属性,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型
proto: 这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型
constructor: 每个原型都有一个 constructor 属性指向关联的构造函数
原型: 对象中固有的__proto__
属性,该属性指向对象的prototype
原型属性, 每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,由多个原型链组成原型链。原型链的尽头一般来说都是Object.prototype
所以这就是我们新建的对象为什么能够使用toString()
等方法的原因。
特点: JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
this指向、new关键字
this
对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this
等于window
,而当函数被作为某个对象调用时,this等于那个对象。 在实际开发中,this
的指向可以通过四种调用模式来判断。
- 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,
this
指向全局对象。 - 方法调用,如果一个函数作为一个对象的方法来调用时,
this
指向这个对象。 - 构造函数调用,
this
指向这个用new
新创建的对象。 - 第四种是
apply 、 call 和 bind
调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply
接收参数的是数组,call
接受参数列表,`` bind方法通过传入一个对象,返回一个
this绑定了传入对象的新函数。这个函数的
this指向除了使用
new `时会被改变,其他情况下都不会改变。
new
- 首先创建了一个新的空对象
- 设置原型,将对象的原型设置为函数的
prototype
对象。 - 让函数的
this
指向这个对象,执行构造函数的代码(为这个新对象添加属性) - 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
手写实现
function create() {
let obj = {};
let Con = [].shift.call(arguments);
obj.__proto__ = Con.prototype;
let result = Con.apply(obj, arguments);
return result instanceof Object ? result : obj;
}
-
对于对象来说,其实都是通过
new
产⽣的,⽆论是function Foo()
还是let a = {b : 1 }
。 -
对于创建⼀个对象来说,更推荐使⽤字⾯量的⽅式创建对象(⽆论性能上还是可读性)。因为你使⽤
new Object()
的⽅式创建对象需要通过作⽤域链⼀层层找到Object
,但是你使⽤字⾯量的⽅式就没这个问题
function Foo() {} // function 就是个语法糖 // 内部等同于 new Function() let a = { b: 1 } // 这个字⾯量内部也是使⽤了 new Object()
作用域、作用域链、变量提升
作用域链:
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
作用域链创建过程-函数激活: 以下函数为例
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
1、checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
2、执行checkscope 函数,创建checkscope执行上下文,checkscope执行上下文被压入执行栈
ECStack = [
checkscopeContext,
globalContext
];
3、checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],
}
4、第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
5、第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6、准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7、查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
变量创建
变量对象的创建过程就介绍完了,让我们简洁的总结我们上述所说:
- 全局上下文的变量对象初始化是全局对象
- 函数上下文的变量对象初始化只包括 Arguments 对象
- 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
- 在代码执行阶段,会再次修改变量对象的属性值
深拷贝 浅拷贝
赋值 深拷贝 浅拷贝区别
基础类型,=赋值
,值的复制
引用类型,=赋值
, 内存地址复制
拷贝:
- 准备一个新的对象/数组
- 把旧的值复制到新对象/数组
浅拷贝
-
什么是浅拷贝 把对象/数组第一项的值,复制到新的数组/对象中,第二层还是复制的指针,互相引用
-
浅拷贝使用场景 修改数组/对象,影响另一个数组/对象,砍断它们的联系
-
如何实现浅拷贝
1、Object.assign()
2、lodash 里面的_.clone
3、...展开运算符
4、Array.prototype.concat
5、Array.prototype.slice
深拷贝
- 什么是深拷贝 把对象/数组所有层的值,复制到新的对象/数组中
- 如何实现深拷贝 1、创建新对象/数组 2、判断是基础类型,直接赋值 3、判断对象类型,新建对象,递归调用函数,继续判断
深拷贝的实现方式
1、JSON.parse(JSON.stringify())
2、递归的操作
3、loadsh 里面的 cloneDeep
let oldObj = {
name: '小明',
age: 18,
grade: [100, 90],
family: {
fName: '王'
}
}
let newObj = {}
function deepClone (newO, old) {
for (let key in old) {
let val = old[key]
if (val instanceof Array) {
newO[key] = []
deepClone(newO[key], val)
} else if (val instanceof Object) {
newO[key] = {}
deepClone(newO[key], val)
} else {
newO[key] = val
}
}
}
deepClone(newObj, oldObj)
EventLoop
当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码全部执行完毕,
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
macro task 与 micro task
以上的事件循环过程是一个宏观的表述,实际上因为异步任务之间并不相同,因此他们的执行优先级也有区别。不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)。
以下事件属于宏任务:
setInterval()
setTimeout()
以下事件属于微任务
new Promise()
new MutaionObserver()
前面我们介绍过,在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈...如此反复,进入循环。
我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行
原生ajax
ajax是一种异步通信的方法,从服务端获取数据,达到局部刷新页面的效果。 过程:
- 创建
XMLHttpRequest
对象; - 调用
open
方法传入三个参数 请求方式(GET/POST)、url、同步异步(true/false)
; - 监听
onreadystatechange
事件,当readystate
等于4时返回responseText
; - 调用send方法传递参数。
事件冒泡、捕获(委托)
- 事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。
- 事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
event.stopPropagation()
或者 ie下的方法 event.cancelBubble = true;
//阻止事件冒泡
call aplly和 bind
共同点 都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行
call 写法
Function.call(obj,[param1[,param2[,…[,paramN]]]])
需要注意以下几点:
- 调用 call 的对象,必须是个函数 Function。
- call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
- 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。
function func (a,b,c) {}
func.call(obj, 1,2,3)
// func 接收到的参数实际上是 1,2,3
func.call(obj, [1,2,3])
// func 接收到的参数实际上是 [1,2,3],undefined,undefined
apply 写法
Function.apply(obj[,argArray])
需要注意的是:
- 它的调用者必须是函数 Function,并且只接收两个参数,第一个参数的规则与 call 一致。
- 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。
func.apply(obj, [1,2,3])
// func 接收到的参数实际上是 [1,2,3]
func.apply(obj, {
0: 1,
1: 2,
2: 3,
length: 3
})
// func 接收到的参数实际上是 1,2,3
补充类数组 就是具备与数组特征类似的对象可以通过角标进行调用,具有length属性,同时也可以通过 for 循环进行遍历,但是需要注意的是:类数组无法使用 forEach、splice、push 等数组原型链上的方法,毕竟它不是真正的数组
call apply 的用途
1、对象的继承
function superClass () {
this.a = 1;
this.print = function () {
console.log(this.a);
}
}
function subClass () {
superClass.call(this);
this.print();
}
subClass();
// 1
2、借用方法如果类数组想使用 Array 原型链上的方法,可以这样:
let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
apply 妙用
let min = Math.min.apply(null, array); //Math.max,用它来获取数组中最小的一项
let max = Math.max.apply(null, array); //用它来获取数组中最大的一项
实现两个数组合并 用 Array.prototype.push来实现
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
bind 写法
Function.bind(thisArg[, arg1[, arg2[, ...]]])
bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用
function add (a, b) {
return a + b;
}
function sub (a, b) {
return a - b;
}
add.bind(sub, 5, 3); // 这时,并不会返回 8
add.bind(sub, 5, 3)(); // 调用后,返回 8
Promise
基本过程:
- 初始化 Promise 状态(pending)
- 立即执行 Promise 中传入的 fn 函数,将Promise 内部 resolve、reject 函数作为参数传递给 fn ,按事件机制时机处理
- 执行 then(..) 注册回调处理数组(then 方法可被同一个 promise 调用多次)
- Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行。
真正的链式Promise是指在当前promise达到fulfilled状态后,即开始进行下一个promise.
链式调用
这个模型简单易懂,这里最关键的点就是在 then 中新创建的 Promise,它的状态变为 fulfilled 的节点是在上一个 Promise的回调执行完毕的时候。也就是说当一个 Promise 的状态被 fulfilled 之后,会执行其回调函数,而回调函数返回的结果会被当作 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会被改变(执行 resolve 或 reject),然后再去执行其回调,以此类推下去...链式调用的效应就出来了
异常捕获
异常通常是指在执行成功/失败回调时代码出错产生的错误,对于这类异常,我们使用 try-catch 来捕获错误,并将 Promise 设为 rejected 状态即可
finally方法
在实际应用的时候,我们很容易会碰到这样的场景,不管Promise最后的状态如何,都要执行一些最后的操作。我们把这些操作放到 finally 中,也就是说 finally 注册的函数是与 Promise 的状态无关的,不依赖 Promise 的执行结果。所以我们可以这样写 finally 的逻辑
### resolve 方法和 reject 方法
实际应用中,我们可以使用 Promise.resolve 和 Promise.reject 方法,用于将于将非 Promise 实例包装为 Promise 实例。如下例子:
Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等价于
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))
复制代码
这些情况下,Promise.resolve 的入参可能有以下几种情况:
- 无参数 [直接返回一个resolved状态的 Promise 对象]
- 普通数据对象 [直接返回一个resolved状态的 Promise 对象]
- 一个Promise实例 [直接返回当前实例]
- 一个thenable对象(thenable对象指的是具有then方法的对象) [转为 Promise 对象,并立即执行thenable对象的then方法。]
Promise.all
入参是一个 Promise 的实例数组,然后注册一个 then 方法,然后是数组中的 Promise 实例的状态都转为 fulfilled 之后则执行 then 方法。这里主要就是一个计数逻辑,每当一个 Promise 的状态变为 fulfilled 之后就保存该实例返回的数据,然后将计数减一,当计数器变为 0 时,代表数组中所有 Promise 实例都执行完毕Promise.race
有了 Promise.all 的理解,Promise.race 理解起来就更容易了。它的入参也是一个 Promise 实例数组,然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行。因为 Promise 的状态只能改变一次,那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法,注入到数组中的每一个 Promise 实例中的回调函数中即可
总结
Promise 源码不过几百行,我们可以从执行结果出发,分析每一步的执行过程,然后思考其作用即可。其中最关键的点就是要理解 then 函数是负责注册回调的,真正的执行是在 Promise 的状态被改变之后。而当 resolve 的入参是一个 Promise 时,要想链式调用起来,就必须调用其 then 方法(then.call),将上一个 Promise 的 resolve 方法注入其回调数组中
ES6
- 新增symbol类型 表示独一无二的值,用来定义独一无二的对象属性名;
- const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,也就是不存在变量提升。(const一般用于声明常量);
- 变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(...rest);
- 模板字符串(
${data}
); - 扩展运算符(数组、对象);;
- 箭头函数;
- Set和Map数据结构;
- Proxy/Reflect;
- Promise;
- async函数;
- Class;
- Module语法(import/export)。
预编译的四个阶段
1.创建AO对象
2.找形参和变量的声明,作为AO对象的属性名,值是 undefined
3.实参和形参相统一
4.找函数声明,如果函数声明和变量名一样,则覆盖变量声明(函数提升只提升函数声明,函数表达式不提升)
this 指向问题
定义:可以这样理解,在JavaScript中,this的指向是调用时决定的,而不是创建时决定的,这就会导致this的指向会让人迷惑,简单来说,this具有运行期绑定的特性。
- 全局上下文
- 函数上下文
- call() 、apply() this 指向绑定的对象上
- bind() this将永久地被绑定到了bind的第一个参数
- 箭头函数 所有的箭头函数都没有自己的this,都指向外层
箭头函数
- 箭头函数中的 this 是在定义函数的时候绑定,而不是在执行的时候绑定
- 箭头函数中,this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this 就是外层代码块的 this ,正因为他没有 this,所以就不能用作构造函数
- 箭头函数中的 this 是在定义函数的时候绑定
- 所谓的定义时候绑定,就是 this 是继承父执行上下文中的 this
js 的作用域
作用域说明:一般理解指一个变量的作用范围
全局作用域
- 全局作用域在函数打开时被创建,在页面关闭时被销毁
- 编译在 script 标签里的变量和函数,作用域为全局,在页面中的任何位置都可以被访问
- 在全局作用域中有全局对象 window。代表一个浏览器窗口,由浏览器创建,可以直接调用
- 全局作用域中声明的变量和函数会作为 window 对象的属性方法保存
函数作用域
- 调用函数是,函数作用域被创建,函数执行完毕,函数作用域被销毁
- 每调用一次函数都会创建一个新的函数作用域,他们之间是相互独立的
- 函数作用域可以访问到全局作用域的变量,函数外部无法访问到函数内部的变量
- 在函数作用域中访问变量是,会先在函数内部寻找,若没有找到,则会到函数的上一级作用域中寻找
执行期的上下文
- 当函数代码执行的前期,会创建一个执行上下文的内容对象AO(作用域)
- 这个内容对象是预编译时候创建出来的
- 在全局代码执行的前期会创建一个执行期的上下文对象GO
- 对于每个执行上下文,都有三个重要属性:
1、变量对象(Variable object,VO)
2、作用域链(Scope chain)
3、this
作用域链
定义: 是函数作用域AO, 和全局作用域VO的集合
闭包
function a() {
var aa = 123
function b() {
console.log(aa)
}
return b
}
var res = a()
res()
原理: b 函数被创建时可以拿到 a 函数的AO,VO, a 函数被销毁时,b函数指向AO, VO链路未被销毁
应用: 单例模式
全局可以访问一个点,并且可以缓存
什么是闭包
闭包是一种特殊的对象,它由两部分组成:执行上下文(代号 A),以及在该执行上下文中创建的函数 (代号 B),当 B 执行时,如果访问了 A 中变量对象的值,那么闭包就会产生,且在 Chrome 中使用这个执行上下文 A 的函数名代指闭包。
一般如何产生闭包
- 返回函数
- 函数当做参数传递
闭包的应用场景
- 柯里化 bind
- 模块
哪些操作会造成内存泄露
- 闭包
- 意外的全局变量 (未声明直接赋值的变量)
- 被遗忘的定时器
- 脱离 dom的引用 (获取 dom, 虽然代码执行完 dom 被清除了,但内存中一直保持对dom 的引用)
高阶函数
定义: 将函数作为参数或者返回值的函数
event-loop
- 语言特点: 单线程,解释性语言
- 时间循环机制由三部分组成:调用栈、微任务队列、消息队列
- 过程
1、event-loop开始的时候,会从全局一行一行的执行,遇到函数调用,会压入到调用栈中,被压入的函数被称之为帧,当函数返回后会从调用栈中弹出 2、js 中的异步操作,比如 fetch setTimeOut setInterval 压入到调用栈中的时候里面的消息会进入到消息队列中,消息队列中的内容会等到调用栈清空之后再执行 3、promise async await的异步操作的时候会加入到微任务中去,会在调用栈清空的时候立即执行,调用栈中加入的微任务会立即执行 4、也就是说微任务中的内容执行优先级大于消息队列中的内容
var p = new Promise(resolve = > {
console.log(4)
resolve(5)
})
function func1() {
console.log(1)
}
function func2() {
setTimeOut(() => {
console.log(2)
}, 0)
func1()
console.log(3)
p.then(resolve => {
console.log(resolve)
})
}
func2()
执行结果: 4, 1, 3, 5, 2
BFC
常见的定位方案
- 普通流
- 浮动
- 绝对定位
概念
它是页面中的一块渲染区域,并且有一套渲染规则,具有BFC特性的元素,可以看做是隔离了的独立容器,容器里的元素不会在布局上对外部元素产生影响
触发BFC
- body 根元素
- 浮动元素:float 除 none 以外的值
- 绝对定位元素:position (absolute、fixed)
- display 为 inline-block、table-cells、flex
- overflow 除了 visible 以外的值 (hidden、auto、scroll)
BFC的特性和应用
1、同一个BFC下边距会发生折叠(放在不同的BFC里)
2、BFC可以包含浮动的元素(清除浮动)
3、BFC可以阻止元素被浮动元素覆盖
Array的 includes
定义:includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。
arr.includes(searchElement)
arr.includes(searchElement, fromIndex)
- searchElement 必须。需要查找的元素值。
- fromIndex可选。从该索引处开始查找,如果为负值,则按升序从 array.length + fromIndex 的索引开始搜索。默认为 0。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true
结构赋值
定义:说白了就是解析等号两边的结构,然后把右边的对应赋值给左边。如果解构不成功,变量的值就等于undefined
未先声明变量再进行对象解构赋值
//对象的键名和键值一致时,可以只写一个变量名即可
let {aa,bb} = {aa:123,bb:456};
//等价于 let {aa:aa,bb:bb} = {aa:123,bb:456};
console.log(aa,bb); // 123 456
对象的键名和键值不一致时
//对象的键名和键值一致时,可以只写一个变量名即可
let {aa:a,bb:b} = {aa:123,bb:456};
console.log(a,b); // 123 456
console.log(aa); // 报错aa is not defined
有默认值的对象解构赋值
let {a = 3,b = 4} = {a:5}
console.log(a,b); // 5 4
应用场景
处理后端返回的json数据,取出自己想要的字段值
let dataJson = {
title:"abc",
name:"winne",
test:[{
title:"ggg",
desc:"对象解构赋值"
}]
}
//我们只需要取出需要的两个title值(注意结构和键名)
let {title:oneTitle,test:[{title:twoTitle}]} = dataJson;
//如果只需要其中一个数据,直接根据结构键名来写就好了
let { name } = dataJson; //相当于es5的 let name = dataJson.name;
console.log(oneTitle,twoTitle); // abc ggg
console.log(name); // winne
多层判断,节约代码,方便维护修改
function animalsDet({type, name, gender} = {}) {
if (!type) return
if (!name) return
if (!gender) return
return `这是一个${type},它的名字是${name}, 性别${gender}`
}
animalsDet({type: 'dog', name: '旺财', gender: '女'})
对象自面量代替 switch方法
const fruitColor = {
red: 'apple',
yello: 'banner'
}
function printFruits(color) {
return fruitColor[color] || []
}
console.log(printFruits('red')) // apple
map 使用
说明: Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
定义:
let map = new Map();
let map = new Map([[key,value],[key,value]]); //默认带初始化参数的定义
如果Map的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map将其视为一个键,包括0和-0。另外,虽然NaN不严格相等于自身,但Map将其视为同一个键
实例属性和方法: size、set、get、has、delete、clear
遍历方法: keys()、values()、entries()、forEach()
let item = {a: 1};
let set = new Set();
let map = new Map();
let obj = new Object();
//增
set.add(item);
map.set('a', 1);
obj['a'] = 1;
//查
set.has(item);// true
map.has('a');// true
'a' in obj;// true
//改
item.a = 2;
map.set('a', 2);
obj['a'] = 2;
//删
set.delete(item);
map.delete('a');
delete obj['a'];
console.log(set);
console.log(map);
console.log(obj);
23、array-some 和 array-every
检测所有水果是否是红色
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yello' }
]
function test() {
const isAllRed = fruits.every(f => f.color === 'red')
console.log(isAllRed) // false
}
test()
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yello' }
]
function test() {
const isAllRed = fruits.some(f => f.color === 'red')
console.log(isAllRed) // true
}
test()
浏览器缓存
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
强缓存
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下
强缓存规则
当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires(过期时间)和Cache-Control,其中Cache-Control优先级比Expires高。
Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
- public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- private:所有内容只有客户端可以缓存,Cache-Control的默认取值
- no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
- max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况
总结
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:
html5新特性
1、语义化标签 : header nav main article section aside footer
语义化意味着顾名思义,HTML5的语义化指的是合理正确的使用语义化的标签来创建页面结构,如
header,footer,nav,从标签上即可以直观的知道这个标签的作用,而不是滥用div。
语义化的优点有:
代码结构清晰,易于阅读,利于开发和维护
方便其他设备解析(如屏幕阅读器)根据语义渲染网页。
有利于搜索引擎优化(SEO),搜索引擎爬虫会根据不同的标签来赋予不同的权重
复制代码
2、 本地存储
h5提供了sessionStorage、localStorage和indexedDB加强本地存储。使用之前应该先判断支持情况
if(window.sessionStorage){
//浏览器支持sessionStorage
}
if(window.localStorage){
//浏览器支持localStorage
}
localStorage和sessionStorage的区别在于sessionStorage基于会话,关闭浏览器之后存储消失。localStorage在各浏览器中上限不同,最低的是2.6MB, 所以开发上限为2.6MB, 当然这比cookie强太多,如果还是不够的话只能借助indexedDB, indexedDB上限是250MB。
localStorage和cookie另一个区别是没有过期时间,不过这个可以在localStorage中加一个时间字段轻松解决这个问题。
存取localStorage可能遇到的坑是跨域问题,因为localStorage是域内安全,也就是同一个域才能对localStorage进行存储,可以通过postMessage来解决。以下是它的常用应用场景
另外,浏览器提供了storage事件来监听存储
window.addEventListener('storage', showStorageEvent, true)
3、离线web应用
页面缓存指的还是有网络状态下,而离线web应用指的是在没有网络状态可以运行应用
if(window.applicationCache){
//支持离线应用
}
manifest文件是核心,记录着哪些资源文件需要离线应用缓存,要使用manifest,只需要在html标签中添加属性
<html manifest="cache.manifest">
cache.manifest的文件格式如下
CACHE MANIFEST
#缓存的文件
index.html
test.js
#不做缓存
NETWORK
/images/
FALLBACK
offline.html index.html
缓存的文件下面是当网络不可用时,文件直接从本地缓存读取,#NETWORK下面的文件无论是否已经缓存,都要从网络中下载。FALLBACK后面,当无法获取到offline.html,则转到index.html
4、表单新增功能
以往input中的name和value要随着form表单一起提交,form表单必须包裹input , 而现在可以通过input的form属性綁定
<form id="testform">
<input type="text" />
</form>
<input form=testform />
- placeholder屬性
- autofocus属性,页面只能有一个
- 还有type为email、number等
5、css3
CSS3提供了更多的选择器,before、after、first-child、nth-child。提供的效果包括box-shadow、text-shadow、background-size,以background-size为例, 在具体语法不同浏览器有所区别,所以要使用以下写法
- 还可以使用media-query实现响应式布局
动画: transition animation
6、地理定位
h5提供了Geolocation API访问地理位置,即通过window.navigator.geolocation来实现访问。这个对象有三个方法:
getCurrentPosition()
watchPosition()
clearWatch
页面第一次使用这个api需要获得用户许可, watchPosition可以对位置变化事件进行监听。
7、音视频处理
8、canvas / webGL
9、history API
10、web scoket IO
11、requestAnimationFrame
rem 和em 的区别 ,rem 如果不设置 是多少
- px是固定的像素,一旦设置了就无法因为适应页面大小而改变。
- em和rem相对于px更具有灵活性,他们是相对长度单位,意思是长度不是定死了的,更适用于响应式布局。
- em是相对于其父元素来设置字体大小的,一般都是以
<body>
的“font-size”为基准。这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小。而Rem是相对于根元素<html>
,这样就意味着,我们只需要在根元素确定一个参考值。
总之:对于em和rem的区别一句话概括:em相对于父元素,rem相对于根元素。
em
-
子元素字体大小的em是
相对于父元素字体
大小 -
元素的width/height/padding/margin用em的话是相对于该元素的font-size
<div>
父元素div
<p>
子元素p
<span>孙元素span</span>
</p>
</div>
div {
font-size: 40px;
/*
即1em = 40px = font-size
*/
width: 7.5em; /* 300px */
height: 7.5em;
border: solid 2px black;
}
p {
font-size: 0.5em; /* 20px */
/*
上面的em以p的父亲的font-size为基准
下面的em以p的font-size为基准
*/
width: 7.5em; /* 150px */
height: 7.5em;
border: solid 2px blue ;
color: blue;
}span {
font-size: 0.5em;
width: 7em;
height: 6em;
border: solid 1px red;
display: block;
color: red;
}
rem
rem是全部的长度都相对于根元素
,根元素是谁?<html>
元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem
<div>
父元素div
<p>
子元素p
<span>孙元素span</span>
</p>
</div>
html {
font-size: 10px;
}
div {
font-size: 4rem; /* 40px */
width: 20rem; /* 200px */
height: 20rem;
border: solid 2px black;
}
p {
font-size: 2rem; /* 20px */
width: 10rem;
height: 10rem;
border: solid 1px blue;
color:blue ;
}
span {
font-size: 1.5rem;
width: 7rem;
height: 6rem;
border: solid 2px red;
display: block;
color: red;
}
需要注意的是:
样式的reset中需先设置html字体的初始化大小为50px,这是为了防止js被禁用或者加载不到或者执行错误。
而做的兼容样式的reset中需先设置body字体的初始化大小为16px,这是为了让body内的字体大小不继承父级html元素的50px,而用系统默认的16px
px 与 rem 的选择?
对于只需要适配少部分手机设备,且分辨率对页面影响不大的,使用px即可 。
对于需要适配各种移动设备,使用rem,例如只需要适配iPhone和iPad等分辨率差别比较挺大的设备。
rpx
rpx 是微信小程序解决自适应屏幕尺寸的尺寸单位。微信小程序规定屏幕的宽度为750rpx
。
无论是在iPhone6上面还是其他机型上面都是750rpx的屏幕宽度,拿iPhone6来讲,屏幕宽度为375px,把它分为750rpx后, 1rpx = 0.5px。
微信小程序同时也支持rem尺寸单位, rem 规定屏幕的宽度为20rem
, 所以 1rem = (750/20)rpx = 37.5 rpx
浏览器gc机制
- 浏览器的垃圾回收机制: v8的内存大小 ==》 1.4G (64位) 0.7G(32位) 最新版的node 是2GB
- 内存限制原因
1、
js单线程机制
,同一时间只能处理一个任务,那么也就意味着在V8执行垃圾回收时,程序中的其他各种逻辑都要进入暂停等待阶段 2、垃圾回收机制
:垃圾回收本身也是一件非常耗时的操作,假设V8的堆内存为1.5G
,那么V8做一次小的垃圾回收需要50ms以上,而做一次非增量式回收甚至需要1s以上
// 设置新生代内存中单个半空间的内存最小值,单位MB
node --min-semi-space-size=1024 xxx.js
// 设置新生代内存中单个半空间的内存最大值,单位MB
node --max-semi-space-size=1024 xxx.js
// 设置老生代内存最大值,单位MB
node --max-old-space-size=2048 xxx.js
process.memoryUsage()
方法来让我们可以查看当前node进程所占用的实际内存大小
回收策略
V8的垃圾回收策略主要是基于分代式垃圾回收机制
,其根据对象的存活时间将内存的垃圾回收进行不同的分代,然后对不同的分代采用不同的垃圾回收算法
新生代回收算法
Scavenge
算法的垃圾回收过程主要就是将存活对象在From
空间和To
空间之间进行复制,同时完成两个空间之间的角色互换,因此该算法的缺点也比较明显,浪费了一半的内存用于复制
-
对象晋升的条件主要有以下两个:
1、对象是否经历过一次`Scavenge`算法 2、`To`空间的内存占比是否已经超过`25%`
老生代回收算法
Mark-Sweep(标记清除)
和Mark-Compact(标记整理)
算法,Mark-Sweep
算法主要是通过判断某个对象是否可以被访问到
1、垃圾回收器会在内部构建一个`根列表`,用于从根节点出发去寻找那些可以被访问到的变量。比如在JavaScript中,`window`全局对象可以看成一个根节点。
2、然后,垃圾回收器从所有根节点出发,遍历其可以访问到的子节点,并将其标记为活动的,根节点不能到达的地方即为非活动的,将会被视为垃圾。
3、最后,垃圾回收器将会释放所有非活动的内存块,并将其归还给操作系统。
为了解决这种内存碎片的问题,Mark-Compact(标记整理)
算法被提了出来,回收过程中将死亡对象清除后,在整理的过程中,会将活动的对象往堆内存的一端进行移动,移动完成后再清理掉边界外的全部内存
主要发生在新生代和老生代,新生代的 semi space From 跟 semispace To 内存空间是完全对等的 大小是64M(64位) 老生代的空间分两个部分 Old pointer space (复杂的对象) 跟old data spac (基本的对象) 他们是连续的 大小是 1400M(64位)
垃圾回收算法: 1新生代简单来说就是copy,Scavenge算法(新生代互换)
为什么要采取复制的形式:牺牲空间换时间,copy操作不耗时
2.老生代就是标记,整理,清除 Mark-Sweep(标记清除) Mark-Compact(标记整理)
(1)先进行广度扫描 跟roots存在引用的就会被标记,不会被回收
(2) 扫描,标记,整理,再清除 整理时会覆盖一些要被回收的垃圾,等到回收时就回收的少了,更快
标记方法: 全停顿标记法 ----》增量标记法+三色标记法 先置灰一个入口节点,他的子节点置黑,下次来,把黑的变灰,黑的变白,然后重复 为社么需要整理?? 因为对象,数组都存储在堆中,堆是连续线性的空间,如果不整理,缝隙的地方塞不下
新生代如何晋升老生代? 1经历过一次垃圾回收的copy, 2 toSpac 已经占用了25%
避免内存泄露
- 尽可能少的创建全局变量
- 手动清除定时器
- 少用闭包
- 清除DOM引用
- 弱引用 // 在ES6中为我们新增了两个有效的数据结构
WeakMap
和WeakSet
,就是为了解决内存泄漏的问题而诞生的。其表示弱引用
,它的键名所引用的对象均是弱引用,弱引用是指垃圾回收的过程中不会将键名对该对象的引用考虑进去,只要所引用的对象没有其他的引用了,垃圾回收机制就会释放该对象所占用的内存
BOM 的理解
window 对象
通俗来讲就是,网页中的所有变量,对象,函数等,最终都是window对象的子属性
全局作用域
在ECMAScript中,window对象扮演着Global对象的角色,也就是说,所有在全局作用域声明的变量,函数都会变成window的属性和方法,都可以通过 window.属性名(或方法名)
直接调用
以下几点需要注意
- 使用
window.attr
声明的变量和var
声明的变量有个区别,使用var
声明的变量,不能使用delete
删除,使用window.
声明的变量可以被删除 - 在JavaScript中,尝试使用未声明的变量会抛出错误,但是通过
window
对象查询未声明的变量,只会返回undefined
导航和打开窗口
通过 window.open()
既可以导航到一个特定的URL,也可以打开一个新的浏览器窗口
该函数接收四个参数:
- URL
- 窗口目标
- 一个新窗口的特性字符串,例:
'height=400,width=400,top=10,left=10,resizable=yes'
- 布尔值,是否取代当前页面(仅在不打开新窗口的时候有效
// 如果存在topFrame窗口,则在topFrame窗口打开指定url,否则新创建一个窗口并命名为topFrame
window.open('htttp://www.maxiaofei.com','topFrame')
==> <a href="http://www.maxiaofei.com" target="topFrame"></a>
window.open()
会返回新窗口的引用,也就是新窗口的 window
对象
var myWin = window.open('http://www.maxiaofei.com','myWin')
复制代码
window.close()
仅用于通过 window.open()
打开的窗口
新创建的 window
对象有一个 opener
属性,该属性指向打开他的原始窗口对象
location 对象
注: window.location
和 document.location
引用的是同一个对象。location
既是 window
对象的属性,也是 document
对象的属性
location对象的属性
以该url举例:http://www.baidu.com:8080/web/javascript/test.js#12345?a=10&b=20
获取当前浏览器页面 url
window.location
修改location对象的属性
每次修改 location
的 hash
以外的任何属性,页面都会以新URL重新加载
window.location = 'http://www.maxiaofei.com'
location.search = '?name=mafei&age=18'
location.hostname = 'www.baidu.com'
location.pathname = 'web/html/a.html'
location.port = '1234'
除了 hash
值以外的任何修改,都会在浏览器的历史记录中生成一条新的记录,可以通过浏览器的回退按钮导航到前一个页面,可以通过 replace()
方法禁止这种行为,使用 replace
打开的页面,不能返回到前一个页面
// 尝试在浏览器控制台输入如下代码,浏览器将不支持回退
location.replace('http://www.baidu.com')
重新加载
通过 location.reload()
方法可以重新加载页面
location.reload()
: 重新加载(有可能会从缓存中加载)location.reload(true)
: 重新加载(从服务器重新加载)
navigator 对象
客服端标识浏览器的标准,主要用来记录和检测浏览器与设备的主要信息,也可以让脚本注册和查询自己的一些活动(插件)
screen 对象
单纯的保存客服端能力的对象。包含以下属性:
history 对象
history
对象保存着用户上网的历史记录,从窗口被打开的那一刻算起,因为 history
是 window
对象的属性,因此每个浏览器窗口,每个标签乃至每个框架,都有自己的 history
对象与特定的 window
对象关联
常用方法
1、history.go()
- 接收一个整数数字或者字符串参数:向最近的一个记录中包含指定字符串的页面跳转
history.go('maixaofei.com') //向前或者向后寻找指定字符串页面,没有找到则无响应
- 当参数为整数数字的时候,正数表示向前跳转指定的页面,负数为向后跳转指定的页面
history.go(3) //向前跳转三个记录
history.go(-1) //向后跳转一个记录
2、history.forward()
- 向前跳转一个页面
3、history.back()
- 向后跳转一个页面
4、history.length
- 获取历史记录数,如果是打开的第一个页面,则
history.length == 0
,可以用该属性来判断当前打开的网页是不是该窗口打开的首个网页
总结
本文主要介绍了浏览器对象模型(BOM)中几个常用的对象,主要包括 navigator
,window
, location
, history
- window既是 JavaScript 的全局对象,也是BOM的一个实例,所有的全局方法,属性,BOM中的属性,都可以通过
window.
来调用 - window作为BOM的实例,最常用的几个方法分别是:
window.open()
,window.close()
,,分别用来打开和关闭浏览器窗口页面,这里需要注意的是,通过 open 方法打开的页面,才能通过close 方法关闭 - location对象也是用的比较多的一个BOM对象,主要用来操作URL相关的一些信息,除了修改 Hash 之外的任何属性,页面都会重新加载,历史记录会多加一条历史记录
- location对象还有一个
reload()
方法用于手动重新加载页面,该方法接收一个可选参数,为true
的时候表示从服务器重新加载,否则可能从浏览器缓存中重新加载页面 - location对象 还有一个比较特殊的方法,
location.replace()
,该方法可以覆盖当前页面并重新加载,同时不会在 history 中生成历史记录 - navigator对象主要用来获取浏览器相关的一些信息,使用的时候需要注意兼容性。可以用来获取浏览器类(Chrome,safrai,FireFox,Edge,IE)等等
- history对象主要用来操作浏览器URL的历史记录,可以通过参数向前,向后,或者向指定URL跳转。可以通过
length
属性获取记录数,判断当前页面是否是打开的首个页面
html中 src 与 href的区别
src用于替换当前元素,href用于在当前文档和引用资源之间确立联系
src
src是source的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素
当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部
href
href是Hypertext Reference(超文本引用)的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接
<link href="common.css" rel="stylesheet"/>
那么浏览器会识别该文档为css文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用link方式来加载css,而不是使用@import方式
浏览器渲染机制、重绘、重排
网页生成过程:
HTML
被HTML解析器解析成DOM
树css
则被css解析器解析成CSSOM
树- 结合
DOM
树和CSSOM
树,生成一棵渲染树(Render Tree
) - 生成布局(
flow
),即将所有渲染树的所有节点进行平面合成 - 将布局绘制(
paint
)在屏幕上
重排(也称回流): 当DOM
的变化影响了元素的几何信息(DOM
对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。 触发:
- 添加或者删除可见的DOM元素
- 元素尺寸改变——边距、填充、边框、宽度和高度
重绘: 当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。 触发:
- 改变元素的
color、background、box-shadow
等属性
重排优化建议:
- 分离读写操作
- 样式集中修改
- 缓存需要修改的
DOM
元素 - 尽量只修改
position:absolute
或fixed
元素,对其他元素影响不大 - 动画开始
GPU
加速,translate
使用3D
变化
transform
不重绘,不回流 是因为transform
属于合成属性,对合成属性进行transition/animate
动画时,将会创建一个合成层。这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。浏览器会通过重新复合来创建动画帧。
CSS
less sass stylus 用法区别
基本用法
Less:
@contentBG:red;
.centerBox{
color:@contentBG;
}
Sass:
$contentBG:red;
.centerBox{
color:$contentBG;
}
Stylus:
contentBG = red;
.centerBox
color contentBG;
作用范围
Less:
@color: red;
.centerBox {
color: @color;
}
@color: black;
.leftBox {
color: @color;
}
/* 编译出来的CSS*/
.centerBox {
color: black;
}
.leftBox {
color: black;
}
less的作用范围不同于其他两个,如果出现了相同的变量定义,则会统一使用最后面定义的那个的值,即后定义的会覆盖前面定义的。这一特点应用在使用一些less组件库的时候会比较有用,当你想自定义一些组件库的某些属性时,可以直接覆盖掉,不会造成冲突。但是同时也会带来一定的隐患:如果不同的组件库或类库使用了相同的变量名,那么就会出现覆盖的情况,所以应该采用模块化的方式
Sass:
$color: red;
.centerBox {
color: $color;
}
$color: black;
.leftBox {
color: $color;
}
Stylus:
color = red
.centerBox
color color
color = black
.leftBox
color color
/* 两者编译出来的CSS*/
.centerBox {
color: red;
}
.leftBox {
color: black;
}
CSS水平垂直居中方式
这种问题在项目中是非常常见的,我简单说几种,但肯定不限于这几种
1、绝对定位方法
:不确定当前div的宽度和高度,采用 transform: translate(-50%,-50%); 当前div的父级添加相对定位(position: relative;)
`div{`
` ``background:red;`
` ``position: absolute;`
` ``left:50%;`
` ``top:50%;`
` ``transform: translate(-50%, -50%);`
`}`
2、绝对定位方法:
确定了当前div的宽度,margin值为当前div宽度一半的负值
`div{`
` ``width:600px;`
` ``height: 600px;`
` ``background:red;`
` ``position: absolute;`
` ``left:50%;`
` ``top:50%;`
` ``margin-left:-300px;`
` ``margin-top:-300px;`
`}`
3、绝对定位方法
:绝对定位下top left right bottom 都设置0
`div.child{`
` ``width: 600px;`
` ``height: 600px;`
` ``background: red;`
` ``position:absolute;`
` ``left:0;`
` ``top: 0;`
` ``bottom: 0;`
` ``right: 0;`
` ``margin: auto;`
`}`
4、flex布局
`.box{`
` ``height:800px;`
` ``-webkit-display:flex;`
` ``display:flex;`
` ``-webkit-align-items:center;`
` ``align-items:center;`
` ``-webkit-justify-content:center;`
` ``justify-content:center;`
` ``border:1px solid #ccc;`
`}`
`div.child{`
` ``width:600px;`
` ``height:600px;`
` ``background-color:red;`
`}`
5、table-cell
控制文本水平居中,可以设置子元素 display 属性是 inline 或者 inline-block ,然后父元素必须有固定宽高,实现水平垂直居中: table-cell middle center组合使用
`display: table-cell;`
` ``vertical-align: middle;`
` ``text-align: center;`
` ``width: 240px;`
` ``height: 180px;`
` ``border:1px solid #666;`
6、绝对定位:calc() 函数动态计算实现水平垂直居中
`.calc{`
` ``position: relative;<``br``> border: 1px solid #ccc;<``br``> width: 400px;<``br``> height: 160px;`
`}`
`.calc .child{`
` ``position: absolute;<``br``> width: 200px;<``br``> height: 50px;`
` ``left:-webkit-calc((400px - 200px)/2);`
` ``top:-webkit-calc((160px - 50px)/2);`
` ``left:-moz-calc((400px - 200px)/2);`
` ``top:-moz-calc((160px - 50px)/2);`
` ``left:calc((400px - 200px)/2);`
` ``top:calc((160px - 50px)/2);`
`} `
css盒子模型
所有HTML
元素可以看作盒子,在CSS中,"box model"
这一术语是用来设计和布局时使用。 CSS
盒模型本质上是一个盒子,封装周围的HTML
元素,它包括:边距,边框,填充,和实际内容。 盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。
IE模型
元素宽度 width = content + padding + border 高度计算相同
标准模型
元素宽度 width = content 只包含内容部分,不包含 padding
高度计算相同
css样式
实现一个三角形
border: 10px solid #000;
border-left-color: #f00;
width: 0;
height: 0;
实现一个梯形
border: 10px solid #000;
border-left-color: #f00;
width: 10px;
height: 10px;
实现一个三角线框
height: 10px;
width: 10px;
border-left: 1px solid #cc0000;
border-top: 1px solid #cc0000;
transform: rotate(135deg);
实现动画
@keyframes example {
from {background-color: red;}
to {background-color: yellow;}
}
/* 向此元素应用动画效果 */
div {
width: 100px;
height: 100px;
background-color: red;
animation-name: example;
animation-duration: 4s;
}
组合器选择器
在CSS中,组合器允许您将多个选择器组合在一起,这允许您在其他元素中选择元素,或者与其他元素相邻。四种可用的类型是:
- 后代选择器——(空格键)——允许您选择嵌套在另一个元素中的某个元素(不一定是直接的后代;例如,它可以是一个孙子)。
- 子选择器—— > ——允许您选择一个元素,该元素是另一个元素的直接子元素。
- 相邻兄弟选择器—— + ——允许您选择一个元素,它是另一个元素的直接兄弟元素(也就是说,在它的旁边,在层次结构的同一层)。
- 通用兄弟选择器—— ~ — —允许您选择其他元素的兄弟元素(例如,在层次结构中的相同级别,但不一定就在它的旁边)
样式优先级
!important>style>id>class
小笔记
- 类数组对象转换为数组对象
- Array.prototype.slice.call(arguments)
- ... 展开运算符
- 箭头函数没有 arguments 对象
2.为什么函数被调用时,代码中的 b 会变成一个全局对象
function fun() {
let a=b=0
}
// 由于代码中的赋值是从右向左进行的
所以上面的代码可以写成
let a=(b=0)
变量 b 没有声明直接赋值,则默认为全局变量
3、对象属性默认是 key:value 形式,如果 key不是字符串,会隐式调用 toString 方法 转为字符串 4、flex
- flex-direction 属性决定主轴的方向(即项目的排列方向)
flex-direction: row | row-reverse | column | column-reverse;
- flex-wrap 默认情况下,项目都排在一条线(又称"轴线")上
flex-wrap: nowrap | wrap | wrap-reverse;
-
flex-flow 是
flex-direction
属性和flex-wrap
属性的简写形式,默认值为row nowrap
-
justify-content 属性定义了项目在主轴上的对齐方式
flex-start | flex-end | center | space-between | space-around;
space-around
:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍
- align-items 属性定义项目在交叉轴上如何对齐
align-items: flex-start | flex-end | center | baseline | stretch;
stretch
(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。- align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
问:如果一个构造函数,bind了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?
不会继承,因为根据 this 绑定四大规则,new 绑定的优先级高于 bind 显示绑定,通过 new 进行构造函数调用时,会创建一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的情况下,返回这个新建的对象
问:用过 TypeScript 吗?它的作用是什么?
为 JS 添加类型支持,以及提供最新版的 ES 语法的支持,是的利于团队协作和排错,开发大型项目
问: 函数中的arguments是数组吗?类数组转数组的方法了解一下?
是类数组,是属于鸭子类型的范畴,长得像数组,
- ... 运算符
- Array.from
- Array.prototype.slice.apply(arguments)
问:数组能够调用的函数有那些?
- push
- pop
- splice
- slice
- shift
- unshift
- sort
- find
- findIndex
- map/filter/reduce 等函数式编程方法
- 还有一些原型链上的方法:toString/valudOf
问:如何判断一个对象是不是空对象?
Object.keys(obj).length === 0
问:NaN 是什么,用 typeof 会输出什么?
Not a Number,表示非数字,typeof NaN === 'number'
知道 ES6 的 Class 嘛?Static 关键字有了解嘛
为这个类的函数对象直接添加方法,而不是加在这个函数对象的原型对象上
node的事件循环跟浏览器的事件循环有什么区别?
(1)宏任务的执行顺序:
1.timer
2.pending callback 待定回调 :执行延迟到下一个事件循环的I/O回调
3.idle , prepare :仅系统内部使用
4.poll: 检索新的IO事件 ,执行与I/O相关的回调
5.check : 执行setImmediate() 的回调
6.close callback soket.on('close', () => {})
(2)宏任务跟微任务在node中的执行顺序:
node v10 及以前
1执行一个阶段的所有任务
2执行nextTick队列里的内容
3执行微任务里面的内容
node v10以后
和浏览器的行为统一了
事件的捕获和冒泡机制了解多少?
(1)基本概念:
捕获:自上而下
冒泡:自下而上
(2)window.addEventListener 监听的是什么阶段的事件?
window.addEventListener('click', () => {}, false/true) false 是冒泡阶段/true是捕获阶段
当容器元素及嵌套元素,即在捕获阶段
又在冒泡阶段
调用事件处理程序时:事件按DOM事件流的顺序执行事件处理程序
且当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序,按上面的例子为,先调用冒泡阶段的事件处理程序,再调用捕获阶段的事件处理程序。依次alert出“子集冒泡”,“子集捕获”。
事件是如何实现的
基于发布订阅模式,就是在浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发的时候才会执行。
比如点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。
在 Web 端,我们常见的就是 DOM 事件:
-
DOM0 级事件,直接在 html 元素上绑定 on-event,比如 onclick,取消的话,dom.onclick = null,同一个事件只能有一个处理程序,后面的会覆盖前面的。
-
DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件可以有多个事件处理程序,按顺序执行,捕获事件和冒泡事件
-
DOM3级事件,增加了事件类型,比如 UI 事件,焦点事件,鼠标事件
平时有关注过前端内存吗?
1.内存的生命周期?
内存分配
内存使用
内存回收
负载均衡, 先用dns服务分配一个健康的nginx服务器,然后再分配到相应的web服务器
0.1+0.2===0.3吗?为什么
在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,然后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度丢失。
所以总结:精度丢失可能出现在进制转换和对阶运算中
async await
- 执行 async 函数,返回的都是Promise 对象
async function test1 () {
return 1
}
async function test2 () {
return Promise.resolve(2)
}
const result1 = test1()
const result2 = test2()
console.log(result1) // Promise {<fulfilled> : 1}
console.log(result2) //Promise
- Promise.then 成功的情况,对应 await
直接跟 promise 对象
async function test3 () {
const p3 = Promise.resolve(3)
p3.then(data => {
console.log(data)
})
const data = await p3
console.log(data)
}
test3()
// 3
// 3
跟一个普通值
async function test4 () {
const data = await 4 // await Promise.resolve(4)
console.log(data)
}
test4()
跟一个异步函数
async function test5 () {
const data = await test1()// await Promise.resolve(4)
console.log(data)
}
test5()
- Promise.catch 异常情况,对应 try ...catch
async function test6 () {
const p6 = Promise.reject(6)
try {
const data = await p6
console.log(data)
} catch (error) {
console.log('error', error)
}
}
test6()
split()
split()方法用于把一个字符串分割成字符串数组
var str = 'How are you'
const a = str.split(' ')
const b = str.split('')
const c = str.split('', 3)
console.log('a', a, 'b', b, 'c', c)
// ["How", "are", "you"]
// ["H", "o", "w", " ", "a", "r", "e", " ", "y", "o", "u"]
// ["H", "o", "w"]
slice
slice() 方法可从已有的数组中返回选定的元素
arrayObject.slice(start,end)
// start 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推
// end 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素
返回值
返回一个新的数组,包含从 start (包括该元素) 到 end (不包括该元素)的 arrayObject 中的元素。
说明
请注意,该方法并不会修改数组,而是返回一个子数组。如果想删除数组中的一段元素,应该使用方法 Array.splice()。
splice()
splice() 方法用于添加或删除数组中的元素。
注意: 这种方法会改变原始数组
array.splice(index, howmany, item1, item2,...itemX)
// index 必需。规定从何处添加/删除元素。\
该参数是开始插入和(或)删除的数组元素的下标,必须是数字
// howmany
可选。规定应该删除多少元素。必须是数字,但可以是 "0"。\
如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素
//item...itemx
可选。要添加到数组的新元素