前端高频面试题—JavaScript篇(二)
💻前端高频面试题—JavaScript篇(二) 🏠专栏:前端面试题
👀个人主页:繁星学编程🍁
🧑个人简介:一个不断提高自我的平凡人🚀
🔊分享方向:目前主攻前端,其他知识也会阶段性分享🍀
👊格言:☀️没有走不通的路,只有不敢走的人!☀️
👉让我们一起进步,一起成为更好的自己!!!🎁
文章目录
- 前端高频面试题—JavaScript篇(二)
- 一. let const var的区别
- (1) let/const 和 var的区别
- 1. 预解析
- 2. 重复变量名
- 3. 块级作用域
- (2) let 和 const区别
- 1. 变量值可以变
- 2. 初始化赋值
- (3) 总结
- 二. 讲讲this
- (1) this的指向
- (2) 如何改变this的指向方法
- 1. call()方法
- 2. apply()方法
- 3. bind()方法
- (3).改变this指向的方法有什么区别
- (4) call apply bind原理理解
- (5) 箭头函数没有this
- 三. 讲讲Map和Set?
- 数据结构 Set
- (1) 创建 Set 数据结构
- (2) Set 数据结构的属性和方法
- (3) Set中判断两个值是否不同
- 数据结构 Map
- (1) Map 基本概念
- (2) Map 特征
- (3) Maps 和 Objects 的区别
- (4) 创建一个 Map数据结构
- (5) Map 数据结构的属性和方法
- 四. new的过程发生了什么
- 五. localStorage sessionStorage cookies 有什么区别
- (1) 什么是会话级存储
- (2) 什么是持久性存储
- (3) 什么是cookie
- (4) cookie和storage的区别
- (5) localStorage和sessionStorage的区别
前端高频面试题—JavaScript篇(二)
本文主要讲述的前端高频面试题知识有:
- let const var的区别
- 讲讲this
- 讲讲Map和Set数据结构
- new的过程发生了什么
- localStorage sessionStorage cookies 有什么区别
一. let const var的区别
ES6新增了两种定义变量和常量的关键字let和const
目的:解决原有语法上的一些不足
let 定义变量
const 定义常量
(1) let/const 和 var的区别
1. 预解析
var 会进行预解析
let/const 没有预解析,必须先声明后使用
// eg: 使用var声明
console.log(username); // undefined
// var 会进行预解析
var username = "tom";
console.log(username); // tom
fn(); // 1
function fn() {
console.log(1);
}
fn(); // 1
// eg: 使用let声明
console.log(num);
let num = 100;
console.log(num);
// 报错:Uncaught ReferenceError: Cannot access 'num' before initialization
2. 重复变量名
var 定义的变量可以重名
let/const 不允许定义重复的变量
// eg:使用var重复声明变量(后面的会覆盖前面的)
var num = 100;
var num = 200;
console.log(num); // 200
// eg:使用let/const重复声明变量报错:(Uncaught SyntaxError: Identifier 'num' has already been declared)
let num = 100;
let num = 200;
const num = 100;
const num = 200;
3. 块级作用域
var 没有块级作用域,只有函数能限制变量的使用范围
let/const 有块级作用域,任何一个可以执行代码的 {} 都会限制变量的使用范围
if (10 > 1) {
let num = 100;
console.log(num); // 100
const a = 200;
console.log(a); // 200
}
console.log(a); // 报错:Uncaught ReferenceError: a is not defined
console.log(num);
(2) let 和 const区别
1. 变量值可以变
let 定义的变量可以修改值
const 对于普遍类型声明之后值不可改变,复杂类型声明后值可以改变,但是类型不可改变(因为赋值复杂数据类型的时候传的不是值,是地址)
let num = 100;
num = 200;
console.log(num); // 200
const num = 100;
num = 200;
console.log(num); // 报错:Uncaught TypeError: Assignment to constant variable
注:const存的值是引用数据类型的时候,只要地址没变就可以
const obj = {
username: "tom",
};
obj.username = "jerry";
console.log(obj); // {"username": "jerry"}
把const声明的常量的地址改变,就会报错
obj = { age: 18 };
console.log(obj);
// 报错:Uncaught TypeError: Assignment to constant variable
2. 初始化赋值
let 定义的时候可以不赋值
const 定义的时候必须赋值,而且一旦赋值不允许修改
let num;
num = 100;
console.log(num); // 100
const num;
num = 100;
console.log(num);
// 报错:Uncaught SyntaxError: Missing initializer in const declaration
(3) 总结
区别 | var | let | const |
---|---|---|---|
变量提升 | 可以 | 不可以 | 不可以 |
预解析 | 可以 | 不可以 | 不可以 |
定义的是 | 变量 | 变量 | 常量 |
重复声明 | 可以 | 不可以 | 不可以 |
块级作用域 | 没有 | 有 | 有 |
修改声明的变量 | 能 | 能 | 不能 |
先声明在赋值 | 可以 | 可以 | 不可以(一旦声明必须初始化) |
注:const
实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
二. 讲讲this
1.this指向调用者这个关系一定要清楚
2.要知道改变this指向的几种方式(call, bind, apply)
3.箭头函数中this的特殊性要能讲清楚
4.call apply bind原理理解
什么是this?
它是一个js内的关键字,是一个使用在作用域内的关键字。
(1) this的指向
-
全局中的this ==> window
-
函数中的this:
不管函数怎么定义,不管函数在哪定义,只看函数调用方式
- 普通调用 ==> window
- 对象中的this ==> .前面是谁就是谁
- 定时器中的this ==> window
- 事件处理函数 ==> 事件源
- 箭头函数中的this ==> 该作用域外部作用域的this
- 构造函数中的this ==> 本次调用 被自动创建的对象
//在全局使用this指向window
console.log(this); // window
console.log(window); // window
console.log(window === this); // true
// 函数中this指向
// 1. 普通调用 this===> window
function fn() {
console.log(this); // window
}
fn();
// 2. 对象调用
function fn() {
console.log(this); // {name: '小花', f: ƒ}
console.log(this.name); // 小花
}
// 把fn这个函数当做一个值存在对象中键名为f的位置
var obj = {
name: "小花",
f: fn,
};
obj.f();
// 3. 定时器调用 this 指向window
function fn() {
console.log(this); // window
}
setTimeout(fn, 2000);
var username = "tom";
function fn() {
console.log(this); // window
console.log(this.username); // tom
}
var obj = {
username: "小花",
f: fn,
};
setTimeout(obj.f, 2000);
// 4. 事件处理函数 this指向事件源
function fn() {
console.log(this); // #document
}
document.onclick = fn;
(2) 如何改变this的指向方法
1. call()方法
语法:函数名.call(参数)
参数:
- 参数1:该函数内的this指向(新指向)
- 参数2:依次给函数进行形参赋值
特点:立即调用函数
2. apply()方法
语法:函数名.apply(参数1, 参数2)
参数:
- 参数1:该函数内的this指向(新指向)
- 参数2:是一个数组,内部的每一项都是给函数形参赋值
特点:立即调用函数
3. bind()方法
语法:函数名.bind(参数)
参数:
- 参数1:该函数内的this指向(新指向)
- 从参数2开始,依次给函数的形参赋值
特点:
- 不会立即调用函数,而是返回一个新函数
- 有一个返回值,是一个和原始函数一模一样的新函数,只不过this被锁死了
var name = 'zjk';
function fun() {
console.log (this.name);
}
var obj= {
name: 'jake'
};
fun(); // zjk
fun.call(obj); //Jake
(3).改变this指向的方法有什么区别
- 三者都可以改变函数this对象的指向
- 三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,则默认指向全局window
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
- bind是返回绑定this之后的函数,apply、call 则是立即执行
(4) call apply bind原理理解
-
call
- 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 String、Number、Boolean
- 为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值
- 将函数作为传入的上下文(context)属性执行
- 函数执行完成后删除该属性
- 返回执行结果
Function.prototype.myCall = function(context,...args){ let cxt = context || window; //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) //新建一个唯一的Symbol变量避免重复 let func = Symbol() cxt[func] = this; args = args ? args : [] //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); //删除该方法,不然会对传入对象造成污染(添加该方法) delete cxt[func]; return res; }
-
apply
前部分与call一样
第二个参数可以不传,但类型必须为数组或者类数组
Function.prototype.myApply = function(context,args = []){ let cxt = context || window; //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) //新建一个唯一的Symbol变量避免重复 let func = Symbol() cxt[func] = this; //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); delete cxt[func]; return res; }
-
bind
需要考虑:
- bind() 除了 this 外,还可传入多个参数;
- bind 创建的新函数可能传入多个参数;
- 新函数可能被当做构造函数调用;
- 函数可能有返回值;
实现方法:
- bind 方法不会立即执行,需要返回一个待执行的函数;(闭包)
- 实现作用域绑定(apply)
- 参数传递(apply 的数组传参)
- 当作为构造函数的时候,进行原型继承
Function.prototype.myBind = function (context, ...args) { //新建一个变量赋值为this,表示当前函数 const fn = this //判断有没有传参进来,若为空则赋值[] args = args ? args : [] //返回一个newFn函数,在里面调用fn return function newFn(...newFnArgs) { if (this instanceof newFn) { return new fn(...args, ...newFnArgs) } return fn.apply(context, [...args,...newFnArgs]) } }
(5) 箭头函数没有this
箭头函数内的this 就是该作用域外部作用域的this
箭头函数外面的函数this是啥,箭头函数this就是啥
注意: 事件处理函数不要用箭头函数,会改变this指向
三. 讲讲Map和Set?
1.Map的key相比较普通对象来说更为灵活,普通对象的key只能以基础数据类型作为key值,并且所有传入的key值都会被转化成string类型,而Map的key可以是各种数据类型格式。
2.Set可以讲讲它去重的特性。
数据结构 Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set
本身是一个构造函数,用来生成 Set 数据结构。
由于Set是伪数组,并且伪数组里面的值都是唯一的,所以可以用在数组去重中
const s = new Set()
const arr = [7, 2, 3, 4, 2, 5, 5];
arr.forEach(item => { s.add(item) });
console.log(s); // Set(5) {7, 2, 3, 4, 5}
console.log([...s]); // (5) [7, 2, 3, 4, 5]
// 简化为
const set = new Set([7, 2, 3, 4, 2, 5, 5]);
[...set]// [7, 2, 3, 4, 5]
(1) 创建 Set 数据结构
语法:
var s = new Set([数据1, 数据2, 数据3....])
(2) Set 数据结构的属性和方法
属性:size
方法:add()、has()、delete()、clear()、forEach()
-
size 属性
该数据结构中有多少个数据
语法:数据结构.size
// eg: const s = new Set([100, 200, 300, 200]); console.log(s.size); // 3
-
add() 方法
向该数据结构添加内容
语法:数据结构.add(数据)
// eg: const s = new Set([100, 200, 300]); s.add(500) s.add(100) console.log(s); // Set(4) {100, 200, 300, 500}
-
has() 方法
查找Set中是否有该数据
语法:数据结构.has(数据)
true 该数据结构内有该数据
false 该数据结构内没有该数据// eg: const s = new Set([100, 200, 300]); console.log(s.has(100)); // true console.log(s.has(500)); // false
-
delete()方法
删除数据
语法:数据结构.delete(数据)
// eg: const s = new Set([100, 200, 300]); s.delete(100); console.log(s); // Set(2) {200, 300}
-
clear() 方法
清除该数据结构内所有内容
语法:数据结构.clear()
// eg: const s = new Set([100, 200, 300]); s.clear(); console.log(s); // Set(0) {size: 0}
-
forEach() 方法
语法 :数据结构.forEach(function(value, key, origin){})
// eg: const s = new Set([100, 200, 300]); s.forEach(function(value, key, origin){ console.log(value, key, origin); // 100 100 Set(3) {100, 200, 300} });
(3) Set中判断两个值是否不同
向 Set 加入值的时候,不会发生类型转换,所以5
和"5"
是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===
),主要的区别是向 Set 加入值时认为NaN
等于自身,而精确相等运算符认为NaN
不等于自身。
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
上面代码向 Set 实例添加了两次NaN
,但是只会加入一个。这表明,在 Set 内部,两个NaN
是相等的。
另外,两个对象总是不相等的。
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
上面代码表示,由于两个空对象不相等,所以它们被视为两个值。
数据结构 Map
Map 数据结构:类似于对象的数据结构,它的key可以是任何数据类型,可以被看做为一个 值 = 值 的数据结构
(1) Map 基本概念
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
(2) Map 特征
- Map 对象保存键值对,并且能够记住键的原始插入顺序。
- 任何值(对象或者原始值) 都可以作为一个键或一个值。
- Map 是 ES6 中引入的一种新的数据结构
(3) Maps 和 Objects 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
(4) 创建一个 Map数据结构
语法:
var m = new Map( [ [key, value], [key, value] ] )
(5) Map 数据结构的属性和方法
属性:size
方法:set()、get()、has()、delete()、clear()、forEach()
-
size 属性
该数据结构内有多少个数据
语法:数据结构.size
// eg: const m = new Map([['name', 'zs'], ['age', 18]]); console.log(m.size); // 2
-
set() 方法
向该数据结构添加内容
语法:数据结构.set(key, value)
// eg: const m = new Map([['name', 'zs'], ['age', 18]]); m.set('time', '两年半'); console.log(m); // Map(3) {'name' => 'zs', 'age' => 18, 'time' => '两年半'}
-
get() 方法
语法:数据结构.get(key)
返回值:该数据结构内key对应的value
// eg: const m = new Map([['name', 'zs'], ['age', 18]]); console.log(m.get('name')); // zs
-
has() 方法
该数据结构中是否有该数据
语法:数据结构.has(key)
// eg: const m = new Map([['name', 'zs'], ['age', 18]]); console.log(m.has('name')); // true
-
delete() 方法
删除该数据结构内某一个数据
语法:数据结构.delete(key)
// eg: const m = new Map([['name', 'zs'], ['age', 18]]); m.delete('name'); console.log(m); // Map(1) {'age' => 18}
-
clear() 方法
清除该数据结构里面的所有内容
语法:数据结构.clear()
// eg: const m = new Map([['name', 'zs'], ['age', 18]]); m.clear(); console.log(m); // Map(0) {size: 0}
-
forEach()
语法:
数据结构.forEach(function(value, key, origin){})
// eg: const m = new Map([['name', 'zs'], ['age', 18]]); m.forEach(function (value, key, origin) { console.log(value, key, origin); // zs name Map(2) {'name' => 'zs', 'age' => 18} // 18 'age' Map(2) {'name' => 'zs', 'age' => 18} })
四. new的过程发生了什么
- 首先创建了一个新对象
- 设置原型,将对象的原型设置为函数的prototype对象
- 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
- 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象
五. localStorage sessionStorage cookies 有什么区别
(1) 什么是会话级存储
window.sessionStorage
特点:
- 生命周期结束为关闭浏览器窗口
- 在同一窗口(页面)下数据可以共享(不能跨页面共享)
- 以键值对的形式存储使用
(2) 什么是持久性存储
window.localStorage
特点:
- 生命周期永久生效,除非手动删除否则关闭页面也会存在
- 可以多窗口(页面)共享(同一浏览器可以共享)
- 以键值对的形式存储使用
(3) 什么是cookie
特点:
-
只能存储字符串,并且有自己的固定格式
key=value;key2=value2;key3=value3
-
cookie 存储有大小限制
存储空间:4kb左右
-
存储的时效性
默认是会话级别,可以手动设置过期时间
-
操作依赖服务器
本地打开的页面不能操作cookie
必须用服务器打开
-
跟随前后端请求自动携带
-
前后端都可以操作
-
存储的时候依赖域名
使用哪个域名存储,就用哪个域名
不能跨域名通讯
(4) cookie和storage的区别
-
出现时间
cookie 在JavaScript刚出现的时候就有了
storage 是H5新增的新特性
-
存储大小
cookie 4kb
storage 20M
-
前后端交互
storage 存储的数据不会跟随页面携带
cookie 存储的数据会跟随页面请求自动携带
-
前后端操作
storage 只能利用JavaScript操作
cookie 前后端都可以操作
-
过期时间
cookie 默认是会话级,可以手动设置过期时间
storage 不能手动设置过期时间
(5) localStorage和sessionStorage的区别
-
过期时间
localStorage 永久存储
sessionStorage 临时存储
-
跨页面通讯
localStorage 直接跨页面共享数据
sessionStorage 只能是从当前窗口跳转的
-
共同点
只能存字符串,不能存复杂数据类型
结束语:
希望对您有一点点帮助,如有错误欢迎小伙伴指正。
👍点赞:您的赞赏是我前进的动力!
⭐收藏:您的支持我是创作的源泉!
✍评论:您的建议是我改进的良药!
一起加油!!!💪💪💪