菜鸟看前端(面试题未完待续)

1.let,const,var区别

我们可以从以下5点来阐述这个问题

  1. 是否存在变量提升
  2. 是否存在块级作用域
  3. 是否存在暂存性死区
  4. 是否可以重复声明
  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

特点

  1. async用法,他作为一个关键字放到函数的前面,这样普通函数就变成了异步函数
  2. 异步async函数调用跟普通的函数使用方式一样
  3. 异步的async函数返回一个promise对象
  4. async配合await关键字使用(阻塞代码往下执行)是异步的方式,但是是阻塞式的。

优点

  1. 方便级联调用
  2. 同步代码的编写方式
  3. 多个参数传递
  4. 同步代码和异步代码一起编码
  5. async/await是对promise的优化。

使用场景

  1. async/await主要来处理异步操作
  2. 最终版的处理回调地狱。
  3. 使用同步的方式写异步的代码,代码采用阻塞式的方式执行下去。解决地狱回调问题
  4. 在项目开发中请求接口中,简化promise的操作

10.原型和原型链

  1. 每一个函数都有一个prototype属性,被称为显示原型
  2. 每个实例对象都会有一个__proto__,被称为隐式原型,每一个实例对象的隐式原型__proto__属性指向自身构造函数的显示原型prototype
  3. 每个prototype原型都一个constructor属性,指向他关联的构造函数
  4. 原型链:获取对象属性时,如果对象本身没有这个属性,那就会去他的原型__proto__上去找,如果还查不到,就去找原型的原型,一直找到最顶层(Object.prototype)为止。Object.prototype对象也有__proto__属性值为null。

Object是属于原型链的顶层,所有构造函数的的prototype都指向 Object.prototype

在这里插入图片描述

11.作用域

作用域:一个变量可以起作用的范围,分为全局作用域,局部作用域,ES6中新增的块级作用域

作用域的种类

  1. js中首先有一个最外层的作用域,为全局作用域
  2. js可以通过函数来创建一个独立的作用域称为函数作用域,函数可以嵌套,作用域也可以嵌套
  3. ES6新增了块级作用域{},比如if{} for{},只适用于const let

作用域链

自由变量向上级一层一层寻找,直到找到为止,最高找到全局作用域.

自由变量

概念: 当前作用域没有定义的变量

  1. 一个变量在当前作用域没有定义,但被使用啦
  2. 向上级作用域,一层一层一次寻找,直到找到为止
  3. 如果全局作用域都没有找到,则报错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 不会变量提升

  1. var 声明的变量会把声明提前

    console.log(a) // undefined
    var a = 10
    console.log(a) // 10
    
  2. 函数声明也会把整个函数提升到作用域的最上面

    n('jack')//jack
    function fn (name){
    console.log(name)
    }
    
  3. 函数表达式不能变量提升,只会把申明的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. 闭包

    1. 闭包的产生: 当函数作为参数被传递时,函数作为返回值被返回时

    2. 什么是闭包

      javascript语言的特殊处就是函数内部可以读取外部作用域中的变量。

      我们有时候需要得到函数内的局部变量,但是在正常情况下,这是不能读取到的,这时候就需要用到闭包。在js中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用

    3. 闭包的应用场景

      实际应用(隐藏数据):为什么说隐藏数据了呢,因为普通用户只能通过get、set等api对数据进行查看和更改等操作,没法对data直接更改,达到所谓隐藏数据的效果;jquery就利用了这一特性,必须调用$.ajax()才能访问内部属性方法。
      封装功能时(需要使用私有的属性和方法),
      函数防抖、函数节流

    4. 闭包的优点

      1. 变量长期驻扎在内存中
      2. 另一个就是可以重复使用变量,并且不会造成变量污染
        ①全局变量可以重复使用,但是容易造成变量污染。不同的地方定义了相同的全局变量,这样就会产生混乱。”
        ②局部变量仅在局部作用域内有效,不可以重复使用,不会造成变量污染。
        ③闭包结合了全局变量和局部变量的优点。可以重复使用变量,并且不会造成变量污染
    5. 闭包的缺点

      由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    13. js数据类型判断

    typeof

    1. 可以检测所有的基本/值类型
    2. 能判断函数
    3. 可以判断是否时引用类型,引用类型都为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指向

  1. 在浏览器里,在全局范围内this 指向window对象;
  2. 在函数中,this永远指向最后调用他的那个对象;
  3. 构造函数中,this指向new出来的那个新的对象;
  4. call、apply、bind中的this被强绑定在指定的那个对象上;
  5. 箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来;
  6. 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区别

  1. cookie

    1. Cookie设计初衷是用来和服务器通讯,而不是本地存储,他只是被‘借用’到本地存储。
    2. cookie的缺点
      • 存储大小,最大为4kb
      • http请求时需要发送到服务器,增加请求数据量
      • 只能用document.cooke = “”,来修改
  2. localStorage,sesssionStorage

    1. 在H5中新增的特性,这个特性主要是用来作为本地存储,解决了cookie存储的空间不足的问题,localStorage中一般浏览器支持5M大小
  3. localStorage,sessionStorage与cookie的区别

    1. H5专门为存储而设计的,存储大小右5M
    2. API简单易用
    3. 不会随着HTTP请求出去
  4. localStora与sessionStorage的区别

    1. localStorage的数据会永久存储,除非代码或手动删除
    2. sessionStorage数据只保存与当前会话,浏览器关闭则清空
    3. 一般localStorage用的比较多
  5. localStorage,sessionStorage的API

    1. setItem()设置存储内容 两个参数,第一个存储的key值,存储的内容
    2. getItem()获取存储的内容 一个参数要读取的key值
    3. removeItem()删除存储内容 一个参数要删除的key值
    4. clear()清空所有存储内容

    本地存储时必须转换为字符串JSON.stringify()

    读取本地存储时需要转换为数组JSON.parse()

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值