2023.3.9日,距离上次投递已经过去了一周了,目前没有收到回复,这一周主要写了一部分八股文,还学了一部分vue,emm,还有一些其他的考试,总之过完这一周,就要全身心投入了.......
注:本文中内容,很多是参考网络文章,大佬都写的特别好,我也是整理一下,方便自己背!
6.怎么判断js的数据类型
typeof
let obj={
name:'dawn',
age:21
}
let fn=function() {
console.log('我是 function 类型');
}
console.log(typeof 1); //number
console.log(typeof 'abc'); //string
console.log(typeof true); //boolean
console.log(typeof undefined); //undefined
console.log(typeof fn); //function
console.log(typeof (new Date) ); //object
console.log(typeof null); //object
console.log(typeof [1,2,3]); //object
console.log(typeof obj); //object
通过typeof只能够测试出undefined,number,string,boolean,function,
而null array object钧检测为object
instanceof
obj instanceof Object
构造函数(右边)的prototype属性是否出现在某个实例对象的原型链上。
let arr=[1,2,3,4,5,6,7]
let obj={
name:'dawn',
age:21
}
let fn=function() {
console.log('我是 function 类型');
}
console.log(arr instanceof Array); //true
console.log(obj instanceof Object); //true
console.log(fn instanceof Function); //true
console.log((new Date) instanceof Date); //true
array和object可以分开了但是null还没有
toString.call()
在任何值上调用 Object 原生的 toString() 方法,都会返回一个 [object NativeConstructorName] 格式的字符串。每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。
但是它不能检测非原生构造函数的构造函数名。
let obj = {
name: 'dawn',
age: 21
}
let fn = function () {
console.log('我是 function 类型');
}
console.log(Object.prototype.toString.call(1)); // [object Number]
console.log(Object.prototype.toString.call('Hello tomorrow')); // [object String ]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(fn)); // [object Function]
console.log(Object.prototype.toString.call(new Date)); // [object Date]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(obj)); // [object Object]
constructor
不能检测null和undefined
let arr = [1, 2, 3, 4, 5, 6, 7]
let obj = {
name: 'dawn',
age: 21
}
let fn = function () {
console.log('我是 function 类型');
}
console.log((9).constructor === Number); //true
console.log('hello'.constructor === String); //true
console.log(true.constructor === Boolean); //true
console.log(fn.constructor === Function); //true
console.log((new Date).constructor === Date); //true
console.log(obj.constructor === Object); //true
console.log([1, 2, 3].constructor === Array); //true
7.set和map的区别
set
es6新增,类似于数组,特性:元素唯一,不重复。我们一般称之为集合
set打印出来的数据结构是一个对象
创建:需要提供一个array作为输入
//初始化一个Set ,需要一个Array数组,要么空Set
var set = new Set([1,2,3,5,6])
console.log(set) // {1, 2, 3, 5, 6}
数组去重
var arr = [2, 3, 5, 3, 5, 2];
var arrSet = new Set(arr);
console.log(arrSet); // Set(6) {2, 3, 5}
//方法一:用ES6的...结构
let newArr1 = [...arrSet];
console.log(newArr1 ); // [2, 3, 5]
字符串去重
var str = "2234332244";
var newStr = [...new Set(str)].join("");
console.log(newStr); //234
键值相等,键就是值,值就是键
var list = new Set(["a", 1, 2, 3]);
list.forEach((value, key) => {
console.log(key + ":" + value); // a:a 1:1 2:2 3:3
});
map
一个key只能对应一个value。多次放入会覆盖。
初始化Map需要一个二维数组,或者直接初始化一个空Map
//初始化`Map`需要一个二维数组(请看 Map 数据结构),或者直接初始化一个空`Map`
let map = new Map();
//添加key和value值
map.set('Amy','女')
map.set('liuQi','男')
//是否存在key,存在返回true,反之为false
map.has('Amy') //true
map.has('amy') //false
//根据key获取value
map.get('Amy') //女
//删除 key为Amy的value
map.delete('Amy')
map.get('Amy') //undefined 删除成功
区别:
底层容器都是红黑树(?)
查找速度块
键不允许重复
初始化map需要二维数组,set需要数组
map使键值对存在,键值分开,set使键值对合在一起
8.js set和array的区别
构造方式不同,obj可以通过字面量,map要么内置方法new要么二维数组
obj的键只能为字符串,map的键可以是任意类型
obj可以从原型对象上继承原型链上的属性
map保留键的顺序不变,obj不会
map提供了更好的接口访问键值对 get has...
对象需要foreach访问所有键值对
对象可以和json互相转换
9.set和array的区别
set的值不能重复
10.遍历对象的方法
for in
for in还会得到 对象原型链上的属性
obj.foo = 'foo'
for (let key in obj) {
console.log(obj[key]) // foo, bar
}
可以通过对象的hasOwnProperty()方法过滤掉原型链上的属性
Object.keys
Object.keys() 返回对象自身属性名组成的数组,它会自动过滤掉原型链上的属性,然后可以通过数组的 forEach() 方法来遍历
Object.keys(obj).forEach((key) => {
console.log(obj[key]) // foo
})
另外还有 Object.values() 方法和 Object.entries() 方法,这两方法的作用范围和 Object.keys() 方法类似,因此不再说明
forEach遍历
Object.getOwnPropertyNames(targetObj)
Object.getOwnPropertyNames(targetObj)
Reflect.ownKeys(targetObj)
11.浏览器缓存
根据HTTP报文的缓存表示进行的
HTTP报文
Request报文
报文格式:请求行(get/post方法,协议类型等)
HTTP头(通用信息头、请求头、实体头)
请求报文主体(post)
Response报文
状态行(协议 状态码 状态描述)
HTTP头(通用信息头,响应头,实体头)
响应报文主体()
优点
节省带宽, 提高访问速度, 降低服务器压力
缓存到哪里
内存,优先级较高,关闭浏览器后数据被释放
磁盘
缓存过程
强制缓存
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),
发起请求,缓存中没有缓存结果和标识,向服务器发起请求
发起请求,缓存中有结果但是超时,使用协商缓存,向服务器发请求
发起请求,缓存有结果未超时,直接获取
服务器在响应报文中返回给浏览器的报文中关于强制缓存的字段:
expires
HTTP1.0,设置超时日期
缺点,时差的误差
cache-control 优先级高
http1.1,设置关键字取值(public,no-stroe,max-age)
在多少时间后到期
协商缓存
浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况
协商缓存生效,返回304
协商缓存失效,返回200和请求结果
字段:
Last-Modified / If-Modified-Since
返回该文件在服务器中最后被修改的时间
Etag / If-None-Match优先级高
根据Etag找到资源进行比对
12.web安全漏洞
CSRF
指伪造用户发送请求,让服务器误以为是用户发起的,利用用户权限......
防护:java防护,CSRF令牌,必须在每个修改状态(post,put,patch,delete)的HTTP中提交给服务。
XSS
嵌入恶意脚本到网页中,执行恶意行为,窃取cookie,弹出窗口等等
原因:对数据过滤不严格
防范:VUE通过第三方插件防御,后端采取filter过滤器,关键是过滤特殊字符,html标签
sql注入
通过查询字符串获取数据库资料,权限,密码
防御:参数化查询,过滤和转义,或者将参数进行预编译。。。。。
13浏览器数据存储方式
cookie
在用户端存储
在同源http请求总是自动将cookie放到请求报文中发送给服务器
大小不超过4k
可以设置过期时间,默认关闭浏览器清除,系统通过cookie提示用户保持登陆状态的时间
跟踪用户行为,个性化推荐
sessionStorage
在关闭窗口前有效,不能持久保存
用于存储一个会话中的数据,只有在同一个会话的页面才能访问,会话结束后数据消失。
localStorage
始终有效,永远不过期
在同源窗口中共享
14闭包
function outer(){
const a=1
function f(){
console.log(a)
}
f()
}
outer()
闭包不一定return 也不一定产生内存泄漏
function outer(){
let a=1
return function f(){
console.log(a)
}
f()
}
const fn=outer()
fn()
闭包概念
闭包是一个存在内部函数的引用关系,引用指向的是外部函数的局部变量
闭包的作用
减少全局变量的污染
延长变量生命周期。
封装,外界无法修改,但可以间接引用这个变量。
闭包的特点
被闭包函数访问的父级及以上的函数局部变量会一直存在于内存中不会被垃圾机制回收。
实现了对其他函数内部变量的访问
缺点
内存泄漏
在函数外部被修改
js垃圾回收机制
一个对象不再被引用就被回收
两个对象互相引用,但不被第三者引用就会被回收
应用
var elements = document.getElementsByTagName('li');
var length = elements.length;
for (let i = 0; i < length; i++) {
elements[i].onclick = function () {
alert(i);
};
}
15原型和原型链
构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.species = '人类';
this.say = function () {
console.log("Hello");
}
}
let per1 = new Person('xiaoming', 20);
原型对象
在js中,每一个函数类型的数据,都有一个叫做prototype的属性,这个属性指向的是一个对象,就是所谓的原型对象。
对于原型对象来说,它有个constructor属性,指向它的构造函数。
最主要的作用就是用来存放实例对象的公有属性和公有方法
原型链
1. 显示原型
显示原型就是利用prototype属性查找原型,只是这个是函数类型数据的属性。
2. 隐式原型
隐式原型是利用__proto__属性查找原型,这个属性指向当前对象的构造函数的原型对象,这个属性是对象类型数据的属性,所以可以在实例对象上面使用:
console.log(per1.__proto__ === Person.prototype); // true
console.log(per2.__proto__ === Person.prototype); // true
原型链就是自己和原型对象上没有属性和方法就会向原型对象的原型对象上去找,知道找到null
函数
函数在js中,也算是一种特殊的对象,所有函数都可以看做是Function()的实例,而Person()和Object()都是函数,所以它们的构造函数就是Function()。Function()本身也是函数,所以Function()也是自己的实例,听起来既怪异又合理,但是就是这么回事。
16作用域和作用域链
全局作用域和局部作用域
全局作用域
局部作用域包括函数作用域和es6带来的块级作用域
函数作用域函数体内访问
块级作用域
es6新增的块级作用域做一个总的概括:
在ES6中只要{ }没有和函数结合在一起,那么应该就是“块级作用域”。
在块级作用域中,var定义的变量是全局变量,let定义的变量是局部变量。
而在函数作用域中,无论是用var定义的变量还是用let定义的变量都是局部变量。
无论是在块级作用域还是局部作用域,省略变量前面的var或者let都会变成一个全局变量。
var a = 'jack';
var b = 'andy';
function fn() {
var a = 'frank';
console.log(a);
console.log(b);
}
fn();
console.log(a);
.
这个时候就有了作用域链的概念了,简单的说作用域表示区域,作用域链表示次序
javascript会先看函数内有没有这个变量a,如果没有再去函数的外围看有没有这个变量,这里作用域链就帮我们安排好了这个次序。
所以,函数内定义了变量a为'frank',那么第二行就会输出'frank',第三行要输出变量b,我们先看函数内有没有这个变量,发现没有,再去外围发现有全局变量b,那么输出的就是这个值,我们再来看最后一个console.log(a),因为他在全局范围内,所以只能访问全局变量a。
也就是说:作用域链只能向上查找,最终找到全局。不能同级(局部)或者向下查找
17执行期上下文
全局执行上下文
全局执行上下文:这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象;2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
函数执行上下文
函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论
执行上下文的生命周期
执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段
执行栈
全局执行上下文在代码开始执行时就创建,有且只有一个,永远在执行上下文栈的栈底,浏览器窗口关闭时它才出栈。
函数被调用的时候创建函数的执行上下文环境,并且入栈。
只有栈顶的执行上下文才是处于活动状态的,也即只有栈顶的变量对象才会变成活动对象。
执行上下文创建阶段
JS执行上下文的创建阶段主要负责三件事:确定this—创建词法环境组件(LexicalEnvironment)—创建变量环境组件(VariableEnvironment)
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
//全局执行上下文
GlobalExectionContext = {
// this绑定为全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
//环境记录
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 let const创建的变量a b在这
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
// 全局环境外部环境引入为null
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object", // 对象环境记录
// 标识符绑定在这里 var创建的c在这
c: undefined,
}
// 全局环境外部环境引入为null
outer: <null>
}
}
// 函数执行上下文
FunctionExectionContext = {
//由于函数是默认调用 this绑定同样是全局对象
ThisBinding: <Global Object>,
// 词法环境
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 arguments对象在这
Arguments: {0: 20, 1: 30, length: 2},
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 声明性环境记录
// 标识符绑定在这里 var创建的g在这
g: undefined
},
// 外部环境引入记录为</Global>
outer: <GlobalEnvironment>
}
}
执行器上下文一般谈论作用域活动对象和变量对象,是es3的概念,在es5中进行了其他的解释,就是词法环境和变量环境
词法环境:
环境记录与对外部环境引入记录两个部分组成
环境记录用于存储当前环境中的变量和函数声明的实际位置
外部环境引入记录很好理解,它用于保存自身环境可以访问的其它外部环境,那么说到这个,是不是有点作用域链的意思?
全局词法环境组件:
对外部环境的引入记录为null,因为它本身就是最外层环境,除此之外它还记录了当前环境下的所有属性、方法位置。
函数词法环境组件
包含了用户在函数中定义的所有属性方法外,还包含了一个arguments对象。函数词法环境的外部环境引入可以是全局环境,也可以是其它函数环境,这个根据实际代码而来。
这里借用译文中的伪代码(环境记录在全局和函数中也不同,全局中的环境记录叫对象环境记录,函数中环境记录叫声明性环境记录,说多了糊涂,下方有展示):
18.Promise
then返回值
如果then回调函数返回一个值,那么then返回的promise将变为接受状态,并且把返回值作为接收状态的回调函数的参数值。
如果then中回调无返回值,那么then返回的promise变为接收状态,并返回undefined作为回调函数的参数值。
如果then中回调函数抛出一个错误,那么then返回的promise变拒绝状态,并将抛出的错误作为拒绝状态的回调函数的参数值。
如果then中回调函数进入catch后,因为catch本身是then 的语法糖 实现为then(null,rejected),所以他return的结果与上面then回调保持一致。
如果then回调返回一个已经拒绝状态的promise,那么then返回的也是一个拒绝状态的promise并把刚才的promise作为参数值返回该promise的参数值。
手撸
撸不出来?!
promise的方法:
promise.all
promise.race
promise.allSettled 确定一组异步操作是否都结束了,不管成功或失败,成功的走then,失败的走catch
promise.any只要有一个成功,实例就会变成fulfilled状态,如果所有的rej了,那么实例就会变为rej
async和await
await 等待的是一个Promise对象,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值
async 表示这是一个async函数, await只能用在async函数里面,不能单独使用
优点:
同步代码和异步代码可以一起编写:使用Promise的时候最好将同步代码和异步代码放在不同的then节点中,这样结构更加清晰;async/await整个书写习惯都是同步的,不需要纠结同步和异步的区别,当然,异步过程需要包装成一个Promise对象放在await关键字后面;
同步代码编写方式:Promise使用then函数进行链式调用,一直点点点,是一种从左向右的横向写法;async/await从上到下,顺序执行,就像写同步代码一样,更符合代码编写习惯;
19.事件循环eventloop、宏任务、微任务
js
js单线程的同一时间只能做一件事 避免线程1添加dom线程2修改dom
耗时的任务阻塞线程
同步任务和异步任务
js引擎立马会执行的属于同步代码
同步任务立即执行,异步代码先放入宿主环境(浏览器、node)
异步:settimeout ajax 事件绑定 (都是耗时的)
执行过程
从宿主环境放到任务队列,先请求完毕的先添加到任务队列,
先把执行栈执行完毕,再交给宿主环境,再异步任务执行完毕,再去看执行栈,形成一个循环就是事件循环。
宏任务和微任务
异步任务分为宏任务和微任务,js引擎自身也能够发起异步任务
注意:promise本身是同步的,但then和catch是微任务
setTimeout属于宏任务
Promise本身是同步的立即执行函数,Promise.then属于微任务
async方法执行时,遇到await会立即执行表达式,表达式之后的代码放到微任务执行
await执行完后,会让出线程。async标记的函数会返回一个Promise对象
20防抖和节流
防抖
连续触发事件但在一段时间内中,频发触发事件,只执行最后一次(从新开始)(王者回城)
应用场景:搜索框搜索输入,文本编辑器实施保存
思路:利用定时器,每次触发先清掉以前的定时器(从新开始)
let time = null
doc.qs('.ipt').onkeyup=function(){
if(time!== null){
clearTimeout(time)
}
time=setTimeout(()=>{
log('我是防抖')
},1000)
}
节流
在设定的一段时间内频发触发事件只执行一次,类似于 技能cd
应用:快速点击鼠标滑动、resize、scroll、下拉加载
思路:等定时器执行完毕才开启定时器
let timer = null
document.querySelector('.ipt').onmouseover = function () {
if(timer!=null){
return
}
timer = setTimeout(()=>{
console.log('我是节流');
timer=null
},100)
}
lodash库debounce和throttle
21电梯导航
22.this指向问题
this
this总是指向一个对象,而具体指向哪个对象是运行时基于函数的执行环境动态绑定的。
一般有如下的情况:
作为对象的方法调用
指向该对象。
var obj = {
a: 'yuguang',
getName: function(){
console.log(this === obj);
console.log(this.a);
}
};
obj.getName(); // true yuguang
作为普通函数的调用
当函数不作为对象属性被调用,而是以普通函数的方式,this指向全局对象
window.name = '老王'
var obj = {
name: 'yuguang',
getName: function(){
console.log(this.name);
}
};
var getNew = obj.getName;
getNew(); // 老王
构造器调用
构造器与普通函数没什么区别,主要是调用方式不同,new构造函数的时候,返回的对象实例,就是this的指向
var MyClass = function(){
this.name = 'yuguang';
}
var obj = new MyClass();
obj.name; // yuguang
call apply调用
跟普通的函数调用相比,用call和apply可以动态的改变函数的this
var obj1 = {
name: 1,
getName: function (num = '') {
return this.name + num;
}
};
var obj2 = {
name: 2,
};
// 可以理解成在 obj2的作用域下调用了 obj1.getName()函数
console.log(obj1.getName()); // 1
console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4
console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4
箭头函数中,this指向函数上层作用域的this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
因此,在下面的代码中,传递给getVal函数内的this并不是调用者自身,而是外部的this~
this.val = 2;
var obj = {
val: 1,
getVal: () => {
console.log(this.val);
}
}
obj.getVal(); // 2
题目
window.name = 'dsb'
var obj = {
name: '1',
getName: function (params) {
console.log(this.name)
},
}
var getName2 = obj.getName
getName2()
此时返回的时dsb,因为getName2作为普通函数调用。
call&apply
1.区别
传参格式不一样,
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的参数集合(可以是数组或类数组)
call接收的参数不固定,第一个参数指定了函数体内this对象的指向,第二个参数及以后为函数调用的参数
2.干什么?
调用构造函数来实现继承
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price); //
this.category = food;
}
var hotDog = new Food('hotDog', 20);
//category: "food" name: "hotDog" price: 20
调用函数并且指定上下文的 this
function showName() {
console.log(this.id + ':' + this.name);
};
var obj = {
id: 1,
name: 'yuguang'
};
showName.call(obj)
使用call单纯的调用某个函数
Math.max.apply(null, [1,2,3,10,4,5]); // 10
bind
var a = {
b: function() {
var func = function() {
console.log(this.c);
}
func();
},
c: 'hello'
}
a.b();
console.log(a.c);
bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值,例如:f.bind(obj),实际上可以理解为obj.f(),这时f函数体内的this自然指向的是obj;
c = 10;
var a = {
b: function () {
console.log(this);
var func = function () {
console.log(this.c);
}.bind(this);
func();
},
c: 'hello'
}
var d = a.b;
d();