js中的作用域和作用域链
在javascript中有两种作用域类型
- 局部作用域:只能在函数内部访问他们
- 全局作用域:网页的所有脚本和函数都能够访问它。
javascrip拥有函数作用域:每个函数都能创建一个新的作用域。
作用域链
当查找变量的时候,会从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是去全局对象。这样由多个执行上下文的变量对象构成的链表叫做作用域链
关于闭包
函数和函数内部能够访问到的变量的总和就是一个闭包。
凡是访问函数内部的变量的都叫闭包。由于作用域链的存在,在作用域内部能够访问到外部的变量,函数处于全局作用域中,所以函数可以访问全局变量。
闭包常用来间接访问一个变量,闭包在一般浏览器中不会造成内存泄漏(内存泄漏指访问不到或无法释放的变量),但是在IE浏览器中存在bug,由于IE在使用完闭包之后无法回收闭包里面引用的变量。
而通过new Function创建的函数无法形成闭包,因为这种方式创建的函数不包含对函数创建时的词法环境的引用。闭包占用内层空间,大量使用闭包会造成栈溢出,由于闭包会一直占用内存空间,直到页面销毁,我们可以在使用完闭包以后主动将闭包销毁,将闭包函数值赋为null就可以销毁闭包。
闭包的应用场景
- 保护函数内的变量安全
- 在内存中维持一个变量
- 通过保护变量的方法实现js私有属性和私有方法
- 为节点绑定事件传参。
原型
原型:
被用于复制现有实例来生成新实例的函数。js中每个函数都有一个prototype属性,这个属性指向函数的原型对象,每一个由原型对象派生的子对象都有相同的属性。子对象就叫构造函数,从实例原型中获取相同的属性。prototype上的所有属性和方法都会被构造函数的实例继承,我们就可以把公用的属性和方法直接定义在prototype对象属性上。
prototype就是调用构造函数所创建的那个实例对象的原型。prototype可以让所有对象实例共享他所包含的属性和方法,我们就可以不在构造函数中定义对象信息,可以直接将这些信息定义在原型中。
原型链:
在实例对象与原型之间的连接,叫做原型链。每个对象都有自己的原型对象,一层层的就形成原型链。原型链的最上端就是OBje.prototype,他没有原型对象(null)
js在创建对象的时候都有一个proto内置属性,他会指向创建它的函数对象的原型对象prototype。
节流和防抖
节流:
高频事件触发,在短时间内大量触发某一事件。
解决方案:设计一种类似阀门的函数,让函数执行一次后在某个时间段失效,过了这段时间重新激活。借助setTimeout实现,加上一个表示状态的变量valid。
function throttle(fn,delay){
let valid = true
return function() {
if(!valid){
//休息时间 暂不接客
return false
}
// 工作时间,执行函数并且在间隔期内把状态位设为无效
valid = false
setTimeout(() => {
fn()
valid = true;
}, delay)
}
}
/* 请注意,节流函数并不止上面这种实现方案,
例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
*/
// 以下照旧
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000)
防抖:
触发高频事件后n秒内只会执行一次,如果n秒内再次触发该事件重新计算事件。
解决方案:借助setTimeout函数,和一个变量去保存计时,考虑维护全局纯净,借助闭包实现
function debounce(fn,delay){
let timer = null //借助闭包
return function() {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(fn,delay) // 简化写法
}
}
// 然后是旧代码
function showTop () {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置
柯里化和反柯里化
柯里化:
提前接收部分参数,延迟执行,不立即输出结果,返回的是一个接收剩余参数的函数,也被成为部分计算函数,这是一个逐步接收参数的过程
function currying(fn,...rest1){
return function(...rest2){
//这里用apply 是为把数组形式的参数直接传入原函数 null是因为不需要改变this
return fn.apply(null,rest1.concat(rest2));
}
}
function sayHello(name,age,fruit){
console.log(`我叫${name},我${age}岁了,我喜欢吃${fruit}`);
}
//传入第一个参数
let curryingShowMsg = currying(sayHello,'小明');
//执行传入剩余参数
curryingShowMsg(22,'芒果');
反柯里化:
为了让方法使用场景更广,使用反柯里化,可以把原生方法解出来,让任何对象拥有原生对象的方法。当我们去调用某个对象的方法时,可以不用去关心该对象原本是否拥有这个方法,这是动态类型语言的特点,通过反柯里化让一个对象去借用一个不属于他的方法。
function curryingHelper(fn,len){
//这里先说个基本知识点 fn.length 返回的是这个方法需要传入的参数个数
//这里第一次运行时通过fn.length 后面递归都是用的传入的len len为剩余的参数个数
const length = len || fn.length;
return function(...rest){
//判断这次传入的参数是否大于等于剩下的参数 如果不是递归返回下一个方法继续传参
return rest.length >= length
? fn.apply(this,rest)
: curryingHelper(currying.apply(this,[fn].concat(rest)),length-rest.length)
}
}
//还是刚才的sayHello 函数
let betterShowMsg = curryingHelper(sayHello);
//自动控制传参层数
betterShowMsg('zyx')(22)('葡萄');
betterShowMsg('zyx',22)('葡萄');
async/await和promise的区别
Promise
promise是异步编程的解决方案,比回调函数这一方案更强大,promise好比容器,里面放着未来才会执行完毕的事件的结果,这些结果一旦生成是无法改变的。promise解决了回调地狱的问题,但是纵向发展形成了一个回调链,当遇到复杂的场景也是不美观的
源码:
有两个数组分别存放成功回调的方法和失败回调的方法,根据promise返回的状态调用resolve和reject的方法,遍历各自数组中的方法然后执行。
const PENDING = 'pending'; //初始状态
const FULFILLED = 'fulfilled'; // 成功状态
const REJECTED = 'rejected'; // 失败
function Promise(extutor){
let self = this;
self.status = PENDING; // 设置状态
// 存放成功回调的数组
self.onResolveCallbacks = [];
// 存放失败回调的数组
self.onRejectedCallbacks = [];
function resolve(value){
if(self.status === PENDING){
self.status = FULFILLED;
self.value = value;
self.onResolveCallbacks.forEach(cb => cb(self.value))
}
}
function reject(reason){
if(self.status === PENDING){
self.status = REJECTED;
self.value = reason;
self.onRejectCallbacks.forEach(cb => cb(self.value))
}
}
try{
excutor(resolve, reject)
} catch(e) {
reject(e)
}
}
简单实现
//极简的实现
class Promise {
callbacks = [];
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
}
_resolve(value) {
this.callbacks.forEach(fn => fn(value));
}
}
//Promise应用
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
}).then((tip) => {
console.log(tip);
})
Promise的构造函数接收一个回调,这个回调就是执行器,参数resolve、reject也是两个函数,负责改变promise实例的状态和值,then函数中的回调在状态改变后执行,then方法会将其中的回调放到执行队列,等promise的状态改变后再将队列中的函数一一执行。
all方法
Promise.all()接收一个参数数组,返回一个新的promise实例,当数组内的promise都resolve后或者都执行完毕后,心返回的promise才会resolve。数组内任何一个promise失败(reject),新返回的promise都会失败,会返回最先被reject失败状态的值。
Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
race方法
和all方法相似也是接收一个参数数组,返回新的promise,数组中哪个promise返回的结果快就返回哪个结果,不管结果是成功还是失败。
async/await
async/await也是异步编程的解决方案,遵循的是generator函数的语法糖,拥有内置执行器,不需要额外的调用会直接自动执行并输出结果,返回一个promise对象。
async/await代码看起来会更简洁,让异步代码更像同步代码,await的本质是可以提供等同于同步效果的等待异步返回能力的语法糖,只有这一句代码执行完毕才会执行下一句。
async/await是基于promise实现的,算是改良版的promise,不能用于普通的回调函数。
finally方法
返回一个promise,在promise结束时不论结果是失败还是成功,都会执行回调函数,返回新promise的状态和值都取决于原来的promise
allSettled方法
返回一个promise,在多个promise是否成功后都需要执行代码,一旦所有promise都完成,就可以获得这个时间点,其返回的心promise总是被resolve的,值是所有promise执行结果的描述。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// expected output:
// "fulfilled"
// "rejected"
排序
冒泡排序
比较相邻的两个元素,如果第一个比第二个大就互相交换,
var arr=[52,98,66,2]
function maopao(arr){
//判断传入的参数是不是数组
if (!Array.isArray(arr)) throw Error('必须是数组')
//外层循环控制循环的次数,每次找到一个值用作比较
for(var i=0;i<arr.length-1;i++){
//内层循环,控制比较的次数,判断两个数的大小
for(var j=0;j<arr.length-1-i;j++){
//判断两个数的大小,如果前面的数大,交换两个数的位置
if(arr[j]>arr[j+1]){
var temp=arr[j]
arr[j]=arr[j+1]
arr[j+1]=temp
}
}
}
return arr
}
选择排序
在数组中假设找到一个最小的数,记下该坐标,把每一个数都与这个数比较,如果小于这个数就交换位置,重复n-1轮。
var arr = [5, 98, 55, 1, 58]
function chose(arr){
//判断传入的参数是不是数组
if (!Array.isArray(arr)) throw Error('必须是数组')
for(var i=0;i<arr.length-1;i++){
var min=i//假设一个数,与后面的数比较
//遍历数组比较
for(var j=i+1;j<arr.length;j++){
if(arr[min]>arr[j]){
min=j //保存最小值的索引
}
}
if(i!==min){
var temp=arr[i]
arr[i]=arr[min]
arr[min]=temp
}
}
return arr
}
快速排序
选择中间的数作为基数,从数组中取出这个基数,准备两个数组,遍历要排序的数组,比基数大的放在右边,比基数小的放在左边,递归这个那两个数组中的元素,最后将两个数组合并为一个数组返回。
function auick(arr){
if(arr.length<=1) return arr
//取得数组的中间值的索引
var index=Math.floor(arr.length/2)
//根据这个索引取得中间值
var mid=arr.splice(index,1)[0]
var left=[],right=[]
for(var i=0;i<arr.length;i++){
if(arr[i]>mid){
right.push(arr[i])
}else{
left.push(arr[i])
}
}
//将处理完以后的两个数组和中间值合并在一起
return quick(left).concat([mid],quick(right))
}
graphql facebook开发
axios并发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-psSUGc8K-1629115058586)(C:\Users\yt\AppData\Roaming\Typora\typora-user-images\image-20210803194118397.png)]
观察者模式
这是一种创建松散耦合代码的技术,它定义对象间 一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知。由主体和观察者组成,主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。主体并不知道观察者的任何事情,观察者知道主体并能注册事件的回调函数。当主体对象状态发生改变时,会通知所有观察者,使他们能够自动更新自己。
观察者模式和发布订阅模式是不一样的:观察者模式只有观察者和被观察者,而发布订阅模式除了订阅者和发布者以外还有一个中间层,发布者通知给中间层=>中间层接收并通知订阅者=>订阅者收到通知发生变化。
let、const、var的区别
- var 声明的变量,作用域在所在的函数内,存在变量提升的现象,并且可以重复声明,var声明的变量可以定义之前访问,只不过是undefined
- let和const不能再定义之前访问该变量(暂存性死区) ,块级作用域,只能在代码块内访问,不存在变量提升,const声明一个常量,之后不能再修改,定义时必须赋值,
垃圾回收
-
引用计数 跟踪每个值被引用的次数,当引用次数为0时就可以回收,但是当两个对象的属性互相引用,并且存在一个函数中,当函数执行完毕引用还会存在,就会造成内存泄漏。手动解除设为null
-
标记清除 最常用。当变量进入环境是会标记变量为“进入环境”,离开时标记为“离开环境”。进入环境的变量永远不能被释放,都会用到
垃圾收集器会给在内存的中的变量加上标记,然后会去除环境中的变量和被变量引用的标记,在这之后加上标记的变量被看作准备删除的变量,环境中无法访问到这些变量了。在IE浏览器中有些DOM和BOM对象是使用C++以COM对象实现的,而COM对象的回收机制采用的引用计数策略,会存在循环引用的问题。
H5新特性
- 语义化标签 header footer nav section
- 增强型表单 可以使用input的 form属性绑定form表单
- canvas绘图
- 地理定位 window.navigator.geolocation
- 媒体标签 video和audio
- 本地存储 localStorage和sessionStorage
- webSocket协议 没有同原协议,建立在TCP基础上,握收阶段http协议,端口80和443
- postMessage 可以实现跨文本档、多窗口、跨域消息传递。
CSS3新增伪类
1. :link 选择所有未访问的链接
2. :visited 选择所有访问过的链接
3. :active 选择正在活动的链接(或理解为鼠标点击瞬间效果)
4. :hover 鼠标放到链接后的状态
5. :focus 选择元素输入后具有焦点
6. :before 在元素之前插入内容
7. :after 在元素之后插入内容
CSS3动画
- transform 改变元素的形状,roate(旋转)、scale(缩放)、translate(移动)静态属性 需要配合transition使用
- transition 设置样式属性是如何从一种状态转变到另一种状态 ,四个属性
- transition-property 变换的属性 大小、位置等
- transition-duration 变换延续的时间
- transition-timing-function变换的速率
- transition-delay 变换的延时
- animation 类似于flash的逐帧率动画,transition只指定了开始和结束的状态。animation是由keyframes完成的。
这三种动画的区别
transform我们可以理解为元素的几何变形,它是有规律可寻的,这种变形并不会产生动画效果仅仅是原有形状的改变;
transition和animation它们很像 flash 中的补间动画和逐帧动画;transition是从一个状态变化到另外一种状态,当变化有了平滑的效果后就产生了动画,它是一个公式化的变化,在比较规则的动画效果中我们可以使用,例如:旋转的风车、行驶的汽车、颜色的渐变等等;
animation的动画效果更加灵活,可以实现像影片一样的复杂无规则的动画
<style>
/* .left{
width: 200px;
height: 200px;
background-color: brown;
transform: scale(1,1);
transition: 1s;
}
.left:hover{
border: 10px green solid;
} */
@keyframes demo {
0% {background-color: red;width: 50px;}
10% {background-color: pink;width: 150px;}
40% {background-color: red;width: 200px;}
60% {background-color: purple;width: 250px;}
100% {background-color: green;width: 300px;}
}
.left{
animation: demo 5s;
}
</style>
</head>
<body>
<div class="left">left</div>
<div class="right">right</div>
</body>
盒模型
W3C标准盒模型
在标准的盒子模型中,width指content部分的宽度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uwwtPTiQ-1629115058600)(C:\Users\yt\Desktop\面试\20180324150509906.jpg)]
盒子模型,顾名思义,盒子就是用来装东西的,它装的东西就是HTML元素的内容。或者说,每一个可见的 HTML 元素都是一个盒子
盒模型的组成为分为以下几个部分:
content(内容区):元素的宽和高;
border(边框区):盒子的边缘;
padding(填充区):为了使页面布局看起来美观大方,盒子里的内容区会用padding来解决父元素和子元素的位置关系;
margin(外边界区):控制同辈元素之间的位置关系。
IE盒模型
在IE盒子模型中,width表示content+padding+border这三个部分的宽度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jk0GGJJx-1629115058601)(C:\Users\yt\Desktop\面试\20180324150533356.jpg)]
box-sizing的使用
如果想要切换盒模型也很简单,这里需要借助css3的box-sizing属性
box-sizing: content-box 是W3C盒子模型
box-sizing: border-box 是IE盒子模型
box-sizing的默认属性是content-box
for in 和for of 的区别
for in循环只能获得对象的健名,不能获得健值,for…in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。
for of 循环允许遍历获得健值,只能遍历部署iterator(迭代器)接口的原生对象,foreach遍历的本质就是使用iterator迭代器遍历。
forEach 循环无法跳出循环,for of 可以配合break和return 跳出循环
无论是 for…in 还是 for…of 都不能遍历出 Symbol 类型的值,遍历 Symbol 类型的值需要用 Object.getOwnPropertySymbols() 方法
for…in 循环主要是为了遍历对象而生,不适用于遍历数组
for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象
2.优化版for循环
使用变量,将长度缓存起来,避免重复获取长度,数组很大时优化效果明显
for(var j = 0,len = arr.length; j < len; j++){
console.log(arr[j]);
}
Object.defineProperty()
Object.defineProperty()
的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
Object.defineProperty(obj, prop, desc)
- obj 需要定义属性的当前对象
- prop 当前需要定义的属性名
- desc 属性描述符
https://www.jianshu.com/p/8fe1382ba135
没有中间件,全是第三方中间件
ctx.body ctx.res.end 都可以返回信息
app.use(async(context,next)=>{
})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cszTsyUX-1629115058606)(C:\Users\yt\AppData\Roaming\Typora\typora-user-images\image-20210804215048169.png)]