文章目录
1.let,const,var区别
我们可以从以下5点来阐述这个问题
- 是否存在变量提升
- 是否存在块级作用域
- 是否存在暂存性死区
- 是否可以重复声明
- 是否能修改声明的变量
- var存在变量提升,会把声明提升到作用域的最顶端,let和const声明的变量,不存在变量提升,如果在声明之前使用会报错
- var不存在块级作用域,let和const存在块级作用域(块级作用域ES6新增作用域)
- var不存在暂时性死区,let和const存在暂时性死区,只要块级作用域内存在let命令,它声明的这个变量就绑定在这个区域,不受外部影响
- var允许重复声明变量,let和const在同一作用域内不允许重复声明变量
- let和var声明的是一个变量所以能修改声明的变量,const声明的是一个常量,不能修改,const一旦声明就必须赋值,否则会报错
2.箭头函数和普通函数的区别
- 箭头函数是一个匿名函数,不能使用new关键字,不能作为构造函数
- 箭头函数没有arguments,可以使用展开运算符解决
let sum = (...arguments) => {
console.log(arguments) // 1,2,3,4
}
sum(1, 2, 3, 4)
arguments是参数的结合,是伪数组,js把传入的全部参数存储到arguments中
function fun(){
// 伪数组 不能使用数组方法
console.log(arguments)
// 伪数组转换为数组
let arr = [...arguments]
console.log(arr)
let arr = [6,7,8]
// 伪数组使用数组方式
// apply 改变指向 传递的参数是一个数组
Array.prototype.push.apply(arguments,arr)
console.log(arguments)
}
fun(1,2,3,4,5)
- 箭头函数的this,始终指向父级上下文(箭头函数的this取决于定义位置父级的上下文,跟使用位置没关系,普通函数this指向调用的那个对象
- 箭头函数不能通过call() 、 apply() 、bind()方法直接修改它的this指向。(call、aaply、bind会默认忽略第一个参数,但是可以正常传参)
- 箭头函数没有原型属性
3.ES6解构赋值
解构赋值就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。
// 解构赋值 交换a,b的值
var a = 1,b = 2;
[a,b] = [b,a];
console.log(a); // 2
console.log(b); // 1
// 对象解构赋值 根据属性名来赋值
var a,b;
({a,b} = {b:20,a:10})
console.log(a); // 10
console.log(b); //20
// 数组解构赋值 左右一一对应进行赋值
var a,b;
[a,b] = [100,200]
console.log(a); // 100
console.log(b); // 200
// 剩余运算符 数组
var a,b,rest;
[a,b,...rest] = [22,33,44,55,66]
console.log(a); // 22
console.log(b); // 33
console.log(rest); // [44,55,66]
// 剩余运算符 对象
var a,b,rest;
({a,b,...rest} = {c:300,d:400,a:100,b:200});
console.log(a);
console.log(b);
console.log(rest);
// 忽略不需要的值
var a,b;
[a,,b] = [100,300,200]
console.log(a);// 100
console.log(b);// 200
// 解析一个从函数返回
function fun(){
return [100,200]
}
var a,b;
[a,b] = fun()
console.log(a);
console.log(b);
// 给新的变量名赋值
var a = {p:3,p1:2}
var {p:oo,p1:ii} = a
console.log(oo); // 3
console.log(ii); // 2
// for of 迭代结构
var people = [
{
name: 'Mike Smith',
family: {
mother: 'Jane Smith',
father: 'Harry Smith',
sister: 'Samantha Smith'
},
age: 35
},
{
name: 'Tom Jones',
family: {
mother: 'Norah Jones',
father: 'Richard Jones',
brother: 'Howard Jones'
},
age: 25
}
];
for (var {name: n, family: {father: f}} of people) {
console.log('Name: ' + n + ', Father: ' + f);
}
// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"
4.for … in 和for…of区别
for…in遍历数组会得到下标
var arr = [200,300,400]
for(var a in arr){
console.log(a) // 得到的是下标0 1 2
}
for…in遍历对象得到是对象的key值
var obj = {a:100,b:200,c:300}
for(var a in obj){
console.log(a) // a b c
}
for…of遍历数组得到的是value
var arr = [200,300,400]
for(var a of arr){
console.log(a) // 200 300 400
}
for…of直接遍历对象会报错
var obj = {a:100,b:200,c:300}
for(var a of obj){ // Uncaught TypeError: obj is not iterable
console.log(a)
}
tip:for…of遍历对象需要配合Object.keys(),得到的也是对象的key值
var obj = {a:100,b:200,c:300}
for(var a of Object.keys(obj)){
console.log(a) // 得到的也是对象的key值 a b c
console.log(a+':'+obj[a]) // a:100 b:200 c:300
}
for …in 配合Objectkeys()遍历对象,得到的是下标
let obj = {a:100,b:200,c:300}
for(let a in Object.keys(obj)){
console.log(a) // 0 1 2
}
Objectkeys():方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
let arr = [111,333,222]
console.log(Objecr.keys(arr)) // 以数组的形式返回下标["0","1","2"]
let obj = {a:100,b:200,c:300}
console.log(Object.keys(obj)) // 以数组的形式返回key ["a","b","c"]
5.深拷贝和浅拷贝
深拷贝:创建一个新的对象和数组,将原对象的各项属性的值(数组的所有元素)拷贝过来,是值而不是’引用’
浅拷贝:将原对象或数组的引用直接赋给新对象或新数组,新数组/新对象只是原对象的一个引用
function deepClone(obj){
// 判断传入的参数是不是对象或数组,如果传入的是null或者不是对象或数组return 返回
// 用typeof来检测 数组的检测结果也是object
if(typeof obj !== 'object' || obj == null){
return obj
}
let result; // 初始化返回结果
// 判断传入的是数组还是对象
// 用instanceof 来判断obj 是否在Array的原型链上 如果在就是数组
if(obj instanceof Array){
result = [] // 如果是数组 resule = []
}else{
result = {} // 如果是对象 result = {}
}
// for ... in 遍历obj
for(var key in obj){
// 保证key不是原型上的属性
if(obj.hasOwnProperty(key)){
// 递归调用
result[key] = deepClone(obj[key])
}
}
//返回结果
return result
}
6.数组去重
filter过滤去重
var arr = [1, 2, 1, 2, 'true', 'false', true, 'true', 'false']
arr1 = arr.filter((item,index)=>{
return arr.indexOf(item) === index
})
console.log(arr1)
indexOf去重
var arr = [1, 2, 1, 2, 'true', 'false', true, 'true', 'false']
var arr1 = []
for(var a = 0;a < arr.length;a++){
if(arr1.indexOf(arr[a]) === -1){
arr1.push(arr[a])
}
}
ES6new Set()去重
var arr = [1, 2, 1, 2, 'true', 'false', true, 'true', 'false']
var arr1 = new Set(arr)
console.log(arr1)
7.ES6类
classES6新增的语法,class来定义一个类方法,方法之间不需要逗号隔开。类里面有一个constructor构造方法,里面有类的默认方法,通过new生成实例时会调用constructor方法,
类中必须有这个方法,如果没有定义,会默认添加一个空的constructor方法。
在constructor内部定义的属性可以称为实例属性,constructor外声明的属性都是定义在原型上的
class不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部
ES6 class继承
使用class来创建一个子类,使用extends关键字来继承,使用super来调用父类的方法
class在语法上更加贴合面向对象的写法
class在实现继承上更加易读,易理解
本质上还是prototype
hasOwnProperty和in区别
hasOwnProperty判断属性是否是实例属性,true为实例属性,false不是实例属性
in 通过对象能访问到的值返回true,无论属性存在实例中还是原型中
// 判断属性是否是原型上的方法
function Fun(name,age){
this.name = name
this.age = age
}
Fun.prototype.sex = '男'
var fun = new Fun('小高',18)
console.log(fun)
console.log("sex" in fun) //true
console.log(fun.hasOwnProperty("sex")) //false
if("sex" in fun == true || fun.hasOwnProperty("sex") == false){
console.log('原型中的属性')
}
8.Promise
Promise异步加载图片
// Promise异步加载图片 race()
function imgOnlad(){
let p = new Promise((resolve,reject)=>{
// 创建img
let img = new Image()
img.src = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1608185373119&di=634266a884fdbadc7da7537f3be3db75&imgtype=0&src=http%3A%2F%2F01.minipic.eastday.com%2F20170721%2F20170721212631_c195b10d28c04da33503cf28bb45b34e_15.jpeg'
img.onload= function(){
resolve(img)
}
})
return p
}
// 超时加载
function timeOut(){
var p = new Promise((resolve,reject)=>{
setTimeout(()=>{
let span = document.createElement('span')
span.innerText = '加载失败'
resolve(span)
},50)
})
return p
}
//Promise race()
Promise.race([imgOnlad(),timeOut()]).then(res=>{
document.body.appendChild(res)
})
Promise all方法
// Promis all() 模拟验证用户名和手机号
// 模拟用户名验证通过
var p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 1000)
})
// 模拟手机号通过
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 2000)
})
p.then(res => {
console.log(res)
})
p.then(res => {
console.log(res)
})
// 两个验证必须都通过才能进行下一步操作哟
// Promise all() 所有异步都执行完 才会执行回调
Promise.all([p,p1]).then(res=>{
let data = res.every(item=>{
return item === 'ok'
})
console.log(data)
if(data){
document.querySelector('button').disabled = false
}
})
// every 不会改变原数组,会返回一个新数组
// every 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)
// 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
// 如果所有元素都满足条件,则返回 true。
Promise封装原生Ajax
// Promise 封装Ajax
function Fun(method,url){
// 创建Promise
let p = new Promise((resolve,reject)=>{
// 创建Ajax核心对象
var xhr = new XMLHttpRequest()
//开启请求
xhr.open(method,url,true)// 请求方式 请求地址 是否为异步
// 发送请求
xhr.send(null)
// 监听异步回调
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
resolve(xhr.responseText)
}else if(xhr.status == 400){
reject('请求失败')
}
}
})
return p
}
let url='http://wthrcdn.etouch.cn/weather_mini?city=%E5%8C%97%E4%BA%AC'
Fun('get',url).then(res=>{
console.log(res)
})
9.async和await
async: 是异步的简写,async用于声明一个异步的的Function
await: 用来等待一个异步的方法,await后面是一个promise
特点
- async用法,他作为一个关键字放到函数的前面,这样普通函数就变成了异步函数
- 异步async函数调用跟普通的函数使用方式一样
- 异步的async函数返回一个promise对象
- async配合await关键字使用(阻塞代码往下执行)是异步的方式,但是是阻塞式的。
优点
- 方便级联调用
- 同步代码的编写方式
- 多个参数传递
- 同步代码和异步代码一起编码
- async/await是对promise的优化。
使用场景
- async/await主要来处理异步操作
- 最终版的处理回调地狱。
- 使用同步的方式写异步的代码,代码采用阻塞式的方式执行下去。解决地狱回调问题
- 在项目开发中请求接口中,简化promise的操作
10.原型和原型链
- 每一个函数都有一个prototype属性,被称为显示原型
- 每个实例对象都会有一个
__proto__
,被称为隐式原型,每一个实例对象的隐式原型__proto__
属性指向自身构造函数的显示原型prototype
- 每个
prototype
原型都一个constructor属性,指向他关联的构造函数- 原型链:获取对象属性时,如果对象本身没有这个属性,那就会去他的原型
__proto__
上去找,如果还查不到,就去找原型的原型,一直找到最顶层(Object.prototype
)为止。Object.prototype对象也有__proto__属性值为null。Object是属于原型链的顶层,所有构造函数的的prototype都指向 Object.prototype
11.作用域
作用域:一个变量可以起作用的范围,分为全局作用域,局部作用域,ES6中新增的块级作用域
作用域的种类
- js中首先有一个最外层的作用域,为全局作用域
- js可以通过函数来创建一个独立的作用域称为函数作用域,函数可以嵌套,作用域也可以嵌套
- ES6新增了块级作用域
{}
,比如if{}
for{}
,只适用于const let
作用域链
自由变量向上级一层一层寻找,直到找到为止,最高找到全局作用域.
自由变量
概念: 当前作用域没有定义的变量
- 一个变量在当前作用域没有定义,但被使用啦
- 向上级作用域,一层一层一次寻找,直到找到为止
- 如果全局作用域都没有找到,则报错
xx is not defined
面试题
// 点击输出下标
let a
for (var i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
console.log(i)
})
document.body.appendChild(a)
}
点击效果
分析
修改
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
console.log(i)
})
document.body.appendChild(a)
}
修改后效果
12.变量提升
var声明的变量,function声明的函数存在变量提升
let const 不会变量提升
-
var 声明的变量会把声明提前
console.log(a) // undefined var a = 10 console.log(a) // 10
-
函数声明也会把整个函数提升到作用域的最上面
n('jack');//jack function fn (name){ console.log(name) }
-
函数表达式不能变量提升,只会把申明的var fn 提升到作用域的最顶端
fn("jack");//报错 var fn = function(name) { console.log(name); };
面试题
// 函数提升 var x = 30; function test() { alert(x); // 函数 var x = 10; alert(x); // 10 x = 20; function x() { }; alert(x); // 20 } test();
13. 闭包
-
闭包的产生: 当函数作为参数被传递时,函数作为返回值被返回时
-
什么是闭包
javascript语言的特殊处就是函数内部可以读取外部作用域中的变量。
我们有时候需要得到函数内的局部变量,但是在正常情况下,这是不能读取到的,这时候就需要用到闭包。在js中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用
-
闭包的应用场景
实际应用(隐藏数据):为什么说隐藏数据了呢,因为普通用户只能通过get、set等api对数据进行查看和更改等操作,没法对data直接更改,达到所谓隐藏数据的效果;jquery就利用了这一特性,必须调用$.ajax()才能访问内部属性方法。
封装功能时(需要使用私有的属性和方法),
函数防抖、函数节流 -
闭包的优点
- 变量长期驻扎在内存中
- 另一个就是可以重复使用变量,并且不会造成变量污染
①全局变量可以重复使用,但是容易造成变量污染。不同的地方定义了相同的全局变量,这样就会产生混乱。”
②局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。
③闭包结合了全局变量和局部变量的优点。可以重复使用变量,并且不会造成变量污染
-
闭包的缺点
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
13. js数据类型判断
typeof
- 可以检测所有的基本/值类型
- 能判断函数
- 可以判断是否时引用类型,引用类型都为object
instanceof
instanceof运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上
console.log( 100 instanceof Number, //false 'dsfsf' instanceof String, //false false instanceof Boolean, //false undefined instanceof Object, //false null instanceof Object, //false [1,2,3] instanceof Array, //true {a:1,b:2,c:3} instanceof Object, //true function(){console.log('aaa');} instanceof Function, //true new Date() instanceof Date, //true /^[a-zA-Z]{5,20}$/ instanceof RegExp, //true new Error() instanceof Error //true )
基本数据类型中:Number,String,Boolean。字面量值不可以用instanceof检测,但是构造函数创建的值可以
var num = 123 console.log(num instanceof Number) // false var num = new Number(123) console.log(num instanceof Number) // true
null和undefined都返回了false,这是因为它们的类型就是自己本身,并不是Object创建出来它们,所以返回了false。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Igc73U7h-1609842858267)(C:\Users\86183\Desktop\instanceof.png)]
constructor
constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的。
除了undefined和null之外,其他类型都可以通过constructor属性来判断类型。
在类继承时会出错
var num = 123; var str = 'abcdef'; var bool = true; var arr = [1, 2, 3, 4]; var json = {name:'wenzi', age:25}; var func = function(){ console.log('this is function'); } var und = undefined; var nul = null; var date = new Date(); var reg = /^[a-zA-Z]{5,20}$/; var error= new Error(); function Person(){ } var tom = new Person(); // undefined和null没有constructor属性 console.log( tom.constructor==Person, num.constructor==Number, str.constructor==String, bool.constructor==Boolean, arr.constructor==Array, json.constructor==Object, func.constructor==Function, date.constructor==Date, reg.constructor==RegExp, error.constructor==Error ); //所有结果均为true
14.this指向
-
- 在浏览器里,在全局范围内this 指向window对象;
- 在函数中,this永远指向最后调用他的那个对象;
- 构造函数中,this指向new出来的那个新的对象;
- call、apply、bind中的this被强绑定在指定的那个对象上;
- 箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来;
- apply、call、bind都是js给函数内置的一些API,调用他们可以为函数指定this的执行,同时也可以传参。
// 普通函数中this指向window
function fun() {
console.log(this) // window
}
fun()
--------------------------------------------------------
// call apply bind中调用, this指向被传入的对象
function fun() {
console.log(this) // {a:100}
}
fun.call({
a: 100
}) // call改变this指向
----------------------------------------------------------
// call apply bind中调用, this指向被传入的对象
function fun() {
console.log(this) // {a:100}
}
// bind改变this指向 返回一个新函数
const fun1 = fun.bind({
a: 100
})
fun1()
----------------------------------------------------------
// 对象中的this指向
var obj = {
name:'小高',
say(){
// 对象方法中调用,this指向当前对象
console.log(this) // this指向对象本身
},
run(){
setTimeout(function(){
console.log(this) // this指向window
},1000)
},
sleep(){
// 箭头函数,this就是父级上下文中的this
setTimeout(()=>{
console.log(this) // this指向对象本身
},1000)
},
}
obj.say() // 这里的this指向对象本身,对象方法中的this,指向当前对象(因为当前对象执行了方法)
obj.run() //setTimeout函数中的this,相当于普通函数中的this,因为setTimeout触发的函数执行,并不是外部对象执行的。
obj.sleep() // setTimeout中函数是箭头函数,this为当前对象。因为箭头函数中的this始终是父级上下文中的this.
15. cookie、localStorage、sessionStorage区别
-
cookie
- Cookie设计初衷是用来和服务器通讯,而不是本地存储,他只是被‘借用’到本地存储。
- cookie的缺点
- 存储大小,最大为4kb
- http请求时需要发送到服务器,增加请求数据量
- 只能用document.cooke = “”,来修改
-
localStorage,sesssionStorage
- 在H5中新增的特性,这个特性主要是用来作为本地存储,解决了cookie存储的空间不足的问题,localStorage中一般浏览器支持5M大小
-
localStorage,sessionStorage与cookie的区别
- H5专门为存储而设计的,存储大小右5M
- API简单易用
- 不会随着HTTP请求出去
-
localStora与sessionStorage的区别
- localStorage的数据会永久存储,除非代码或手动删除
- sessionStorage数据只保存与当前会话,浏览器关闭则清空
- 一般localStorage用的比较多
-
localStorage,sessionStorage的API
setItem()
设置存储内容 两个参数,第一个存储的key值,存储的内容getItem()
获取存储的内容 一个参数要读取的key值removeItem()
删除存储内容 一个参数要删除的key值clear()
清空所有存储内容
本地存储时必须转换为字符串
JSON.stringify()
读取本地存储时需要转换为数组
JSON.parse()