关于原生js的部分问题和面试题

js中的作用域和作用域链

在javascript中有两种作用域类型

  1. 局部作用域:只能在函数内部访问他们
  2. 全局作用域:网页的所有脚本和函数都能够访问它。

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]);
}

img

Object.defineProperty()

Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性

Object.defineProperty(obj, prop, desc)
  1. obj 需要定义属性的当前对象
  2. prop 当前需要定义的属性名
  3. 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)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值