# JavaScript 面试
# 1.js 的数据类型
基本类型 Number、String、Boolean、Undefined、null、symbol(ES6 新增的一种基本类型) 其中 Symbol 和 BigInt 是 ES6 中新增的数据类型: ●Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。 ●BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
引用数据类型 Object(Array、Function、Date、RegExp)
# 2.原始数据和引用数据类型的不同
存储位置不同 ● 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储; ● 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
传值方式不同 原始数据类型 按值传递,无法改变一个原始数据类型的值; 引用数据类型 按引用(地址)传递,引用数据类型值可以传递; 堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中: ● 在数据结构中,栈中数据的存取方式为先进后出。 ● 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。 在操作系统中,内存被分为栈区和堆区: ● 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 ● 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
# 3.JS 中的数据类型检测方案
1.typeof
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof "mc"); // string
console.log(typeof Symbol); // function
console.log(typeof function() {}); // function
console.log(typeof console.log()); // function
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof undefined); // undefined
优点:能够快速区分基本数据类型
缺点:不能将 Object、Array 和 Null 区分,都返回 object
2.instanceof
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log("str" instanceof String); // false
console.log([] instanceof Array); // true
console.log(function() {} instanceof Function); // true
console.log({} instanceof Object); // true
优点:能够区分 Array、Object 和 Function,适合用于判断自定义的类实例对象
缺点:Number,Boolean,String 基本数据类型不能判断
3.Object.prototype.toString.call()
var toString = Object.prototype.toString;
console.log(toString.call(1)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call("mc")); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call({})); //[object Object]
console.log(toString.call(function() {})); //[object Function]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
优点:精准判断数据类型
缺点:写法繁琐不容易记,推荐进行封装后使用
# 4. var && let && const
var 定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。 let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。 const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。 var 可以先使用,后声明,因为存在变量提升;let 必须先声明后使用。 var 是允许在相同作用域内重复声明同一个变量的,而 let 与 const 不允许这一现象。 在全局上下文中,基于 let 声明的全局变量和全局对象 GO(window)没有任何关系 ; var 声明的变量会和 GO 有映射关系; 会产生暂时性死区:
暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined 如:console.log(typeof a) //undefined 而:console.log(typeof a)//未声明之前不能使用 let a
let /const/function 会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于 let 的块作用特征即可解决 处。
# 5.闭包的两大作用:保存/保护
闭包的概念
闭包是指有权访问另一个函数作用域中的变量的函数--《JavaScript高级程序设计》 函数执行时形成的私有上下文EC(FN),正常情况下,代码执行完会出栈后释放;但是特殊情况下,如果当前私有上下文中的某个东西被上下文以外的事物占用了,则上下文不会出栈释放,从而形成不销毁的上下文。 函数执行函数执行过程中,会形成一个全新的私有上下文,可能会被释放,可能不会被释放,不论释放与否,他的作用是:
(1)保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,保护自己的私有变量不受外界干扰(操作自己的私有变量和外界没有关系);
(2)保存:如果当前上下文不被释放【只要上下文中的某个东西被外部占用即可】,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来了;
我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包。
稍全面的回答: 在 js 中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
闭包的特性:
1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。 1.1.闭包是密闭的容器,,类似于 set、map 容器,存储数据的 1.2.闭包是一个对象,存放数据的格式为 key-value 形式
2、函数嵌套函数
3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除
闭包形成的条件:
函数的嵌套 内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
闭包的用途:
模仿块级作用域 保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收) 封装私有化变量 创建模块
闭包应用场景 闭包的两个场景,闭包的两大作用:保存/保护。 在开发中, 其实我们随处可见闭包的身影, 大部分前端 JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送 ajax 请求成功|失败的回调;setTimeout 的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。
闭包的优点:延长局部变量的生命周期
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
# 6.JS 中 this 的情况
1.普通函数调用:通过函数名()直接调用:this 指向全局对象 window(注意 let 定义的变量不是 window 属性,只有 window.xxx 定义的才是。即 let a =’aaa’; this.a 是 undefined) 2.构造函数调用:函数作为构造函数,用 new 关键字调用时:this 指向新 new 出的对象 3.对象函数调用:通过对象.函数名()调用的:this 指向这个对象 4.箭头函数调用:箭头函数里面没有 this ,所以永远是上层作用域 this(上下文) 5.apply 和 call 调用:函数体内 this 的指向的是 call/apply 方法第一个参数,若为空默认是指向全局对象 window。 6.函数作为数组的一个元素,通过数组下标调用的:this 指向这个数组 7.函数作为 window 内置函数的回调函数调用:this 指向 window(如 setInterval setTimeout 等)
# 7.介绍节流防抖原理、区别以及应用
节流:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
防抖:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!
使用场景:
节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……
防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。
/**
* 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。
* @param fn要被节流的函数
* @param delay规定的时间
*/
function throttle(fn, delay) {
//记录上一次函数触发的时间
var lastTime = 0;
return function() {
//记录当前函数触发的时间
var nowTime = Date.now();
if (nowTime - lastTime > delay) {
//修正this指向问题
fn.call(this);
//同步执行结束时间
lastTime = nowTime;
}
};
}
document.onscroll = throttle(function() {
console.log("scllor事件被触发了" + Date.now());
}, 200);
/**
* 防抖函数 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
* @param fn要被节流的函数
* @param delay规定的时间
*/
function debounce(fn, delay) {
//记录上一次的延时器
var timer = null;
return function() {
//清除上一次的演示器
clearTimeout(timer);
//重新设置新的延时器
timer = setTimeout(function() {
//修正this指向问题
fn.apply(this);
}, delay);
};
}
document.getElementById("btn").onclick = debounce(function() {
console.log("按钮被点击了" + Date.now());
}, 1000);