经典必考JavaScript面试题
1.let和const
- 使用
let
关键字(和const
)声明的变量也会存在变量提升,但与var
不同,初始化没有被提升。 - 在变量未初始化或赋值之前,不允许访问, 这被称为“暂时性死区”。 当我们在声明变量之前尝试访问变量时,
JavaScript
会抛出一个ReferenceError
。 - const . let和var的区别?
-
var:只有全局作用域和函数作用域概念;变量可以多次声明,存在变量提升(就是函数任意地方声明变量都可以)
-
const:const声明的变量必须经过初始化,const只有块级作用域概念;不允许在相同作用域内重复声明同一个变量,不存在变量提升
-
let:let只有块级作用域概念;不允许在相同作用域内重复声明同一个变量,不存在变量提升
-
2.ES6解构赋值
- 按照一定模式从数组和对象中提取值,对变量进行赋值
2.1数组的解构
const F4 = ['陆军', '海军', '空军']
let [lu, hai, kong] = F4
console.log(lu);//陆军
console.log(hai);//海军
console.log(kong);//空军
2.2对象的解构
const hai = {
name: 'navy',
age: 490423,
};
let {name, age} = hai;
console.log(name);//navy
console.log(age);//490423
3.闭包
-
指有权访问另一个函数作用域中变量的函数
-
函数嵌套(return返回)函数,一个作用域中的函数可以访问另一个函数中的的局部变量
-
作用:
- 延伸了变量的作用范围(使我们在函数外部能够访问到函数内部的变量)
- 使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包会保留对这个变量对象的引用,所以这个变量对象不会被回收。
-
缺点:
- 内存泄露:闭包使用过度而导致的内存占用无法释放的情况,可能会导致应用程序卡顿或者崩溃
-
内存泄露解决方案
- 使用严格模式
- 关注
DOM
生命周期,在销毁阶段记得解绑相关事件 - 避免过度使用闭包
4.严格模式
- 消除了JS语法的一些不合理、不严谨之处,减少了一些怪异行为
- 消除代码运行的一些不安全之处,保证代码运行的安全
- 提高了编译器效率,增加运行速度
4.1开启严格模式
- 为脚本开启严格模式
//1
<script>
'use strict';
//下面的js代码就会开启严格模式
</script>
//2 定义一个立即执行函数
<script>
(function() {
'use strict';
//下面的js代码就会开启严格模式
})()
</script>
- 为函数开启严格模式
<script>
function() {
'use strict';
//下面的js代码就会开启严格模式
}
</script>
4.2严格模式的变化
- 变量规定:
- 必须先声明,在使用
- 不能随意删除已经声明的变量
- this指向问题:
- 全局作用域中函数的this是underfined(非严格模式指向window)
- 如果构造函数不加new调用,this指向会报错
- 定时器里的this还是指向window
5.call、apply、bind总结
-
相同点:
- 都可以改变函数内部的this指向
-
不同点:
- call和apply会调用函数,并且改变函数内部this指向
- call和apply传递的参数不一样,call传递参数aru1,aru2…形式,applt必须数组形式
- bind不会调用函数,可以改变函数内部this指向
-
应用场景
- call:经常做继承
- apply:经常跟数组有关系
- bind:不调用函数,但要改变this指向
6.浅拷贝和深拷贝
- 防止父对象数据被篡改
6.1浅拷贝(只是拷贝了地址)
-
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
-
实现方式:
Object.assign()
方法: 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
let obj = { id: 1, name: 'andy', }; let o = {}; //for (let k in obj) { // o[k] = obj[k]; //} //将obj拷贝给o Object.assign(o, obj);
Array.prototype.slice()
:slice() 方法返回一个新的数组对象,这一对象是一个由 begin和end(不包括end)决定的原数组的浅拷贝- 拓展运算符
...
:(最简单)
let a = { name: "Jake", flag: { title: "better day by day", time: "2020-05-31" } } let b = {...a};
6.2深拷贝(新开辟一个空间存放)
-
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
-
实现方式:
JSON.parse()
,缺点诸多(会忽略undefined、symbol、函数;不能解决循环引用;不能处理正则、new Date())
var origin_data = { a: 1, b: 2, c: [1, 2, 3] } var copy_data = JSON.parse(JSON.stringify(origin_data)) origin_data.a = 3; origin_data.b = 4; origin_data.c[0] = '呵呵哒'; console.log(origin_data) console.log(copy_data)
8.this指向
-
在浏览器里,在全局范围内this 指向window对象;
-
在函数中,this永远指向最后调用他的那个对象;
-
构造函数中,this指向new出来的那个新的对象;
-
call、apply、bind中的this被强绑定在指定的那个对象上;
-
箭头函数没有
this
,它的this
指向函数定义位置的上下文this
9.原型与原型链
- 每个函数对象都有一个
prototype
属性,这个属性指向的是原型对象=>共享方法 - 对象身上系统会自己添加一个
__proto__
指向我们构造函数的原型对象prototype
,之后就可以使用原型对象上的方法 - 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链
10.浏览器三种事件模型
- DOM0级事件模型
- 这种模型不会传播,所以没有事件流的概念,它可以在网页中直接定义监听函数,也可以通过 js属性来指定监听函数
- IE事件模型
- 该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。
- 事件处理阶段会首先执行目标元素绑定的监听事件
- 事件冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数
- DOM2级事件模型
- 在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。
11.事件委托
- 本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件
- 可以不必要为每一个子元素都绑定一个监听事件,可以减少内存上的消耗。并且使用事件代理还可以实现事件的动态绑定
12.事件传播
- 当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。
12.1事件传播三阶段
- 捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素事件或event.target。
- 目标阶段–事件已达到目标元素
- 冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window
12.2事件捕获
- 当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在捕获阶段,事件从window开始,一直到触发事件的元素。
window----> document----> html----> body ---->目标元素
12.3事件冒泡
- 事件冒泡刚好与事件捕获相反,
当前元素---->body ----> html---->document ---->window
。当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。在冒泡阶段,事件冒泡,或者事件发生在它的父代,祖父母,祖父母的父代,直到到达window为止
13.JS创建对象的方式
13.1直接创建Object实例
- 每次创建一个对象就需要手动设置它的每一个属性,造成大量代码重复,JS可以使用工厂模式的变体解决这个问题
//new Object()
var person = new Object();
person.name = 'zzx';
person.age = 20;
person.sayName = function(){
console.log(this.name);
};
//使用字面量{}
var person = {
name: 'lx';
age: 20;
job: 'Programmer';
sayName: function(){
console.log(this.name);
}
}
13.2工厂模式
- 解决了创建多个相似对象的问题,但是却不知道当前创建的对象是什么类型
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var person1 = createPerson('lx', 22, 'Programmer');
var person2 = createPerson('kylin', 20, 'Teacher');
console.log(person1);
13.3构造函数模式
- 构造函数名开头大写借鉴了其他面向对象语言,是为了区别普通函数。任何一个函数不通过new操作符调用,就是一个普通函数
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}
function Robot(name, age){
this.name = name;
this.age = age;
}
var person1 = new Person('lx', 22, 'Programmer');
var person2 = new Person('kylin', 20, 'Teacher');
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Robot); //false
13.4原型模式
- 可以让所有对象实例共享它所包含的属性和方法
function Person(){
}
Person.prototype.name = 'lx';
Person.prototype.age = 18;
Person.prototype.job = 'Programmer';
Person.prototype.sayName = function(){
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); //lx
var person2 = new Person();
console.log(person1.sayName == person2.sayName); //true
13.5组合使用构造函数模式和原型模式
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['kk', 'wz'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
};
13.6动态原型模式
- 这里只在 sayName()方法不存在的情况下,才会将它添加到原型中
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}
var person1 = new Person('lx', 22, 'Programmer');
person1.sayName();
13.7寄生构造函数模式
- 这一种模式除了使用 new 操作符并把使用的包装函数叫做构造函数之外和工厂模式的实现基本相同
- 基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数
function Person(name, age, job){
var o = new Object()
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var person1 = new Person('lx', 18, 'Programmer');
person1.sayName();