web前端面试题(JavaScript)

数据类型

  • 基本数据类型

Number、String、Boolean、Null、Undefined、Symbol(独一无二的值)、bigInt(跨越Number 最大值得限度)
存放位置:存放于栈中

  • 引用数据类型

Object、Array、Date、Function、RegExp
存放位置:存放于堆中,栈中的数据的引用地址

var object1 = {
	a:'我可牛逼了'
}
var object2 = object1
object2.a = '我变了'
console.log(object1) // a 我变了
console.log(object2) // a 我变了

const、let、var

**共同点:**三者都是用来声明变量的
不同点:
var 声明的变量 在全局作用域 都可以生效 会变量提升
let、const 声明的变量 在块级作用域生效 有暂时性死区的问题 在声明前使用会报错
const:定义常量

暂时性死区

在代码块内,使用let和const命令声明变量之前,该变量都是不可用的,语法上被称为暂时性死区

变量提升和函数提升

js程序开始时会进行预编译
在js中变量和函数的声明hi提升到最顶部执行
函数的提升高于变量的提升

闭包

闭包就是能够读取其实函数内部变量的函数
闭包其实即使一个函数内部返回一个函数
好处
可以读取函数内部的变量
可以将变量始终保存在内存中
可以封装对象和私有属性和私有方法
坏处
比较耗费内存、使用不当会造成内存溢出的问题
实际场景

<body>
  <input type="text">
  <script>
  // 防抖
    function debounce(func, time) {
      let timer = 0;
      return function () {
        //若使用timer && clearTimeout(timer)会更加确保timer被清除记录,或者timer=0
        //注释这一行和注释下面的timer=0也可以(只是相对没那么严谨)
        // timer && clearTimeout(timer)
        clearTimeout(timer)
        timer = setTimeout(() => {
          // timer = 0
          func.apply(this)//反之this指向错乱
        }, time)
      }
    }
    // 节流
	function throttle(func,time){
		var lastTime=0;
		return function(){
		    var nowTime=new Date();
			if(nowTime-lastTime>time){
				func.call(this);
				lastTime=nowTime;//由于闭包的作用原理,这里调用外层函数的局部变量,每次调用这个lastTime变量都会发生改变
			}
		}
	}
    const input = document.querySelector('input')
    input.onkeypress = debounce(function () {
      console.log(input.value)//事件处理逻辑
    }, 500)
  </script>
</body>

防抖节流

  • 防抖:防抖是指在事件触发n秒后再执行后续的操作
    简单来时 就是 当你 在一个input框输入时调用接口,能够实现你在输入最后一个值时,再去调用。
    主要应用场景:
    a、scroll事件滚动触发,
    b、搜索框输入查询
    c、表单验证
    d、按钮提交事件
    e、浏览器窗口缩放,resize事件
  • 节流:节流是指如果持续触发某个事件,则每隔n秒执行一次
    主要应用场景:
    a、DOM元素的拖拽功能实现
    b、射击游戏类
    c、计算鼠标移动的距离
    d、监听scroll事件

原型、原型链

原型
在js中,每一个函数类型的数据,都有一个叫做prototype的属性,这个属性指向的是一个对象,就是所谓的原型对象
原型链
每一个对象都有原型对象,当我查找一个对象的某个属性时,它就会通过_proto_的指向,找到上一级的原型对象,终点是object的原型对象的上一级,为null

DOM事件流

DOM标准规定事件流包括
DOM事件流是有两种的,一种是捕获型事件流,另外一种是冒泡型事件流。

new一个新对象

 
function Person () {
    this.name = name;
    this.age = age;
    this.sex = sex
 
    this.sayName = function () {
        return this.name;
    };
}
 
var person = new Person("tom", 21, "famle");
 
console.log(person.name);

创建一个新对象
将新对象的_proto_指向构造函数的prototype对象
将构造函数的作用域赋值给新对象 (也就是this指向新对象)
执行构造函数中的代码(为这个新对象添加属性)
返回新的对象

var Obj = {};
Obj._proto_ =  Person.prototype();
Person.call(Obj);

this

// 在方法中,this指的是所有者对象
 var person = {
            firstName:"Bill",
            lastName:"Gates",
            id:678,
            fullName:function(){
                return this.firstName + " " + this.lastName;
            }
        };
 // 单独的this window
 var x = this;
 document.getElementById("demo").innerHTML = x;
 // 函数中的this
 // window (严格模式是undefined)
function myFunction(){
    return this;
 }
// 事件处理程序中的this
// this指的是button
<button onclick = "this.style.display='none'">
     点击来删除我!
</button>

call/apply/bind

回调函数

Promise

Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
以上是对promise的resolve用法进行了解释,相当于resolve是对promise成功时候的回调,它把promise的状态修改为
fullfiled,那么,reject就是失败的时候的回调,他把promise的状态修改为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。
与Promise对象方法then方法并行的一个方法就是catch,与try catch类似,catch就是用来捕获异常的,也就是和then方法中接受的第二参数rejected的回调是一样的,
与then同级的另一个方法,all方法,该方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后并且执行结果都是成功的时候才执行回调。
all是等所有的异步操作都执行完了再执行then方法,那么race方法就是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调

async、await

继承

// 基础
// 构造函数
function Animal(name) {
	this.name = name || 'Animal';
	this.sleep = function () {
		console.log(this.name + "正在睡觉");
	}
}
// 在原型上添加方法
Animal.prototype.eat = function (food) {
	console.log(this.name + '正在吃' + food);
}

// 原型链继承
// 核心 将父类的实例作为子类的原型
function Dog() {

}
Dog.prototype = new Animal(); // 将Animal的实例挂载到Dog的原型链上
Dog.prototype.name = 'dog';

var dog = new Dog();
console.log(dog.name); // dog
dog.eat('骨头'); // dog正在吃骨头
// 特点
// 实例是子类的实例,也是父类的实例
// 父类新增的原型方法/原型属性,子类都能访问的到
// 缺点
// 想要为子类新增属性和方法,必须要在创建实例之后执行,不能放到构造器中
// 无法实现继承多个
// 来自原型对象的所有属性被所有实例共享
// 创建子类实例时,无法向父类构造函数传参


// 构造继承
function Cat(name) {
	Animal.call(this)
	this.name = name || 'Tom'
}

var cat = new Cat()
console.log(cat.name);  // Tom
cat.sleep() // Tom 正在睡觉

// 特点
// 创建子类的实例时,可以向父类传递参数
// 可以实现多继承
// 缺点
// 实例并不是父类的实例,只是子类的实例
// 只能继承父类的实例属性和方法,不能继承原型属性/方法
// 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

// 实例继承
// 父类实例添加新特性,作为子类实例返回
//核心:为父类实例添加新特性,作为子类实例返回
function Cat(name) {
	var instance = new Animal();
	instance.name = name || 'Tom';
	return instance;
}

var cat = new Cat();
console.log(cat.name);		//Tom
cat.sleep();			//Tom正在睡觉!
console.log(cat instanceof Animal);		//true
console.log(cat instanceof Cat);		//false
// 特点
// 不限制条用方式,不管是new 子类 还是子类 返回的对象都具有相同的效果
// 缺点
// 实例是父类的实例,不是子类的实例
// 不支持多继承

// 拷贝继承
function Cat(name){
	var animal = new Animal();
	for(let i in animal) {
		Cat.prototype[i] = animal[i];
	}
	Cat.prototype.name = name || 'Tom';
}

var cat = new Cat();
console.log(cat.name);	//Tom
cat.sleep();	//Tom正在睡觉!
console.log(cat instanceof Animal); 	// false
console.log(cat instanceof Cat);	 // true
// 特点
// 支持多继承
// 缺点
// 效率极低,内存占用高
// 无法获取父类不可枚举的方法

// 组合继承
//核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name) {
	Animal.call(this);
	this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

var cat = new Cat();
console.log(cat.name);	//Tom
cat.sleep();		//Tom正在睡觉
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
// 特点
// 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
// 既是子类的实例,也是父类的实例
// 不存在引用属性共享问题
// 函数可复用
// 可传承
// 缺点
// 调用了两次构造函数,生成两份实例(子类实例将子类原型上的那份屏蔽了)

// 寄生组合继承
// 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免了组合继承额度缺点
function Cat(name) {
	Animal.call(this);
	this.name = name || 'Tom'
}
(function () {
	var Super = function () { // 创建一个没有实例的方法类
	}
	Super.prototype = Animal.prototype; // 
	Cat.prototype = new Super() // 将实例作为子类的原型
})()
let cat = new Cat();
console.log(cat.name);		//Tom
cat.sleep();		//Tom正在睡觉
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true

Cat.prototype.constructor = Cat;	//修复构造函数

浅拷贝、深拷贝

深拷贝与浅拷贝出现的情况只是在数据为引用类型的时候出现
浅拷贝Object.assign(),当修改新的值得时候,会改变原数据,深拷贝是直接把对象完全的赋值一份,修改新的不会改变原数据
浅拷贝
1.Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
深拷贝
用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

数组API

  • push、unshift、pop、shift
// push 在数组末尾添加元素
const arr = [1,2,3];
arr.push(4,5,6)
console.log(arr) // [1,2,3,4,5,6]

// unshift 在数组前面添加元素
const arr = [1,2,3];
arr.unshift(4,5,6)
console.log(arr) // [4,5,6,1,2,3]

// pop 用于取下数组中最后面的元素(尾部),返回取出的值
const arr = [1,2,3];
const last = arr.pop()
console.log(arr) // [1,2]
console.log(last) // 3

// shift 用于取下数组中最前面的元素(头部),返回取出的值
const arr = [1,2,3];
const first = arr.shift()
console.log(arr) // [2,3]
console.log(first) // 1

// indexOf:用于查找数据中是否包含指定的值,返回指定值得索引,如果没有存在返回-1
const list = ['A','B','C'];
console.log(list.indexOf('C')); // 2
console.log(list.indexOf('D')); // -1

// map 对指定数组内的每个元素进行函数调用,返回一个数组
const list = ['A','B','C'];
console.log(list.indexOf('C')); // 2
console.log(list.indexOf('D')); // -1

// join 将数组中的元素使用指定的拼接符进行拼接,返回拼接好的字符串
const arr = [1,2,3];
const res = arr.join(',')
console.log(res) // "1,2,3"

// sort 用于将数组进行排序操作,默认按字符串方式进行排序,也可以传入一个函数作为参数,返回一个新数组
const arr = [9,12,3,21,18];
const res = arr.sort((a,b)=>a-b)
console.log(res) // [3,9,12,18,21]

// filter 检查数组中符合条件的元素,返回数组中符合条件的所有元素。不会改变原数组,返回一个新数组
const arr = [1,2,3,4,5,6,7,8,9,10];
const arr2 = arr.filter(item=>item%2!==0)
console.log(arr2) // [1,3,5,7,9]

// some 判断数组中的元素是否有一个或以上符合条件,如果有,返回true,否则为false
const arr = [9,12,3,21,18];
const res = arr.some((item)=>item<5)
console.log(res) // true

// every 判断数组中的元素是否全部符合条件,如果符合,返回true,否则为false
const arr = [9,12,3,21,18];
const res = arr.every((item)=>item<5)
console.log(res) // false

// forEach方法,它与上面的map对象类似,也是对数组内的每个元素进行函数调用。不一样的是forEach没有返回值( undefined )
const arr = [1,2,3];
arr.forEach((item)=>{
    console.log(item*2)
});
// 2
// 4 
// 6

// reduce 对数组中的每一个元素执行由您提供的reducer函数,再将结果汇总为单个返回值
const list = [1, 2, 3]
const sum = list.reduce(((accumulator, currentValue)=>accumulator + currentValue)
, 0);
console.log(sum) // 6

// reverse 颠倒数组中元素的位置。返回一个颠倒后的数组,会修改原数组
const arr = ['ABC','age',12,3];
const res = arr.reverse()
console.log(res) // [3,12,"age","ABC"]

// includes 用于判断数组里否包含一个指定的值,如果包含则返回 true,否则返回false
const arr = ['A','B','C'];
console.log(arr.includes('A')) // true
console.log(arr.includes('D')) // false

字符串API

垃圾回收

垃圾回收: JavaScript代码运行时,需要分配内存空间来存储变量和值。当变量不在参与运行时,就需要系统回收被占用的内存空间,这就是垃圾回收
回收机制:

  • JavaScript具有自动垃圾回收机制,会电器对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
  • JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
  • 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
    垃圾回收的方式
    标记清除
  • 标记清楚是浏览器常见的垃圾回收方式,当比那里进入执行环境时,就标记这个变量“进行环境”,被标记记为“进入环境”的变量是不能被回收的,因为它们正在被使用。当变量离开环境时,就会标记“离开环境”,被标记为离开环境的变量会被内存释放。
  • 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
    引用计数
  • 另外一种垃圾回收机制就是引用计数,这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值得引用次数就是1。相反,如果包含对这个值引用的遍历又取得了另外一个值,则这个值得引用次数就减1。当这个引用次数变为0时,说明这个变量已经 没有价值,因此,在垃圾回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。
    减少垃圾回收
    虽然浏览器可以进行垃圾自动回收,但是代码比较复杂时,垃圾回收所带来的代价比较大,所以应尽量减少垃圾回收。
    对数组进行优化:在清空一个数组是,最简单的就是给其赋值为[],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数据的目的。
    对object进行优化:对象尽量复用,对于不再使用的对象,就将器其设置为null,尽快被回收。
    对函数进行优化:在循环中的函数表达式,如果可以复用,尽量放在函数的外面。
    内存泄漏
    意外的全局变量:由于使用为声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
    被遗忘的计时器或回调:设置了setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
    脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
    闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值