2021面试前期准备——JS基础(持续更新)

37 篇文章 0 订阅
26 篇文章 0 订阅

(1)、数据类型

1、js数据类型: 8种

Number、String、Boolean、Undefined、Null、Object、Symbol、BigInt(安全存储、操作大整数)

js基础类型:5种

String、Number、Boolean、Null、Undefined

js引用类型:3种

Array、function、Object 使用typeOf,结果都是object

2、null、undefined、isNaN,NaN

null和undefined的区别

null: typeof null 是一个object

null == undefined : true 因为没有发生隐式转换

Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。
Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。

null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。**

http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html

https://blog.csdn.net/u013592575/article/details/95087953

isNaN() 函数用于检查其参数是否是非数字值。

NaN 与任何值(包括其自身)相比得到的结果均是 false

3、如何判断数据类型?typeof/instanceOf/Object.prototype.toString**

typeof: 可以判断Number/String/Boolean/null/undefined,不能判断引用类型

toString :其他类型转换为string类型

toLocaleString :把数组转换为本地字符串

检测数组的方法:

instanceof: arr instanceof Array : true

对象的constructor属性:arr.constructor === Array

Array.isArray(arr): 检测arr是否为数组

Object.prototype.toString

console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]

如何检测一个对象是否为空

1、object.keys(xx).length == 0
2、JSON.stringnify(xx) == “{}”

4、 == 和 ===的区别,什么场景下使用?

区别:

== 检查值相等, 允许类型转换
=== 检查值和类型相等, 不允许类型转换

(1)如果要比较的两个值的任意一个(即一边)可能是 true 或者 false 值,那么要避免使用 ==,而使用 ===。

(2)如果要比较的两个值中的任意一个可能是特定值(0、"" 或者 []——空数组),那么避免使用 ==,而使用 ===

(3)在所有其他情况下,使用 == 都是安全的。不仅仅只是安全而已,这在很多情况下也会简化代码,提高代码的可读性

隐式转换:

1、布尔值与其他值比较,布尔值会转换为数字0,1
2、数字与字符串比较,字符串会转换为数字
字符串内容全部是数字,则转换为纯数字
空字符串,转换为数字0
其他情况转换为NaN

5、Symbol和BigInt

Symbol:表示独一无二的值,最大的用法是用来定义对象的唯一属性名

BigInt:数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。

要创建BigInt,只需在整数的末尾追加n即可。比较:
https://segmentfault.com/a/1190000019912017?utm_source=tag-newest

6、Number() 的存储空间是多大?如果后台发送了一个超过最大自己的数字怎么办

Math.pow(2, 53) ,53 为有效数字,会发生截断,等于 JS 能支持的最大数字。任何超出此范围的整数值都可能失去精度。

(2)、事件

参考:1、DOM事件机制:https://zhuanlan.zhihu.com/p/51611590

​ 2、对JS事件流的深入理解:https://zhuanlan.zhihu.com/p/114276880

1、一个事件发生后,会在父子元素间传播,分为三个阶段:

​ 1、捕获阶段:事件从window对象自上而下向目标节点传播的阶段;

​ 2、目标阶段:真正的目标节点正在处理事件的阶段;

​ 3、冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。

https://pic3.zhimg.com/80/v2-4fa0e50fe4db2b07d525c11cac81c172_720w.jpg

事件的捕获过程:

​ 捕获是从上到下,事件先从window对象,然后再到document(对象),然后是html标签(通过document.documentElement获取html标签),然后是body标签(通过document.body获取body标签),然后按照普通的html结构一层一层往下传,最后到达目标元素。

​ 给元素的事件行为绑定方法都是在当前元素事件行为的 冒泡阶段(或者目标阶段)执行的。

document
html
body
div
目标元素

2、阻止冒泡:e.stopPropagation()

​ 阻止默认事件:e.preventDefault() 防止表单提交submit、a标签跳转、锚点定位

3、事件代理(事件委托)

​ 由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

​ 优点:1、减少内存消耗,提升性能,不用给每个元素绑定时间,通过冒泡原理,给父元素绑定

​ 2、动态监听,使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以一样具有和其他元素一样的事件

4、target和currentText的区别:

​ target:返回触发事件的元素

​ currentTarget:是绑定事件的元素

5、冒泡和捕获阶段谁先执行呢?

​ 先执行捕获阶段的处理程序,后执行冒泡阶段的处理程序

​ 触发的目标元素上不区分冒泡还是捕获,按绑定的顺序来执行。

6、DOM级别和DOM事件级别?

​ DOM级别:0级、1级、2级、3级

​ DOM事件级别:0级、2级、3级

DOM事件0级:onClick只有冒泡

DOM事件2级:addEventLisener/removeEventLisener,有三个参数,第一个是方法名,第二个是函数,第三个默认为false,表示允许事件冒泡,改为true,则为事件捕获

DOM事件3级:DOM3级事件在DOM2级事件的基础上添加了更多的事件类型

UI事件:当用户与页面上的元素交互时触发 例如:load 和 scroll
焦点事件:当元素获得或失去焦点时触发 例如:blur 和 focus
鼠标事件:当用户通过鼠标在页面执行操作时触发 例如:dbclick 和 mouseup
滚轮事件:当使用鼠标滚轮或类似设备时触发 例如:mousewheel
文本事件:当在文档中输入文本时触发 例如:textInput
键盘事件:当用户通过键盘在页面上执行操作时触发 例如:keydown 和 keypress
合成事件:当为IME(输入法编辑器)输入字符时触发 例如:compositionstart
变动事件:当底层DOM结构发生变化时触发 例如:DOMsubtreeModifie

事件流: 事件流描述的就是从页面中接收事件的顺序。
事件是如何发生的?什么是事件?
​ JavaScript和HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口发生的一些特定的交互瞬间。可以使用监听器(或事件处理程序)来预定事件,以便事件发生时执行相应的代码。通俗的说,这种模型其实就是一个观察者模式。

————2021年3月22日18:52:04————

(3)、数组和对象的遍历

1、遍历数组的方法

1、map:

不修改原数组本身,会生成一个新数组
当你不打算使用返回的新数组却使用map是违背设计初衷的,请用forEach或者for-of替代。你不该使用map: A)你不打算使用返回的新数组,或/且 B) 你没有从回调函数中返回值。

例子:
var array1 = [1, 4, 9, 16];

const map1 = array1.map(x => {
if (x == 4) {
return x * 2;
}
});

console.log(map1);

打印结果为:

Array [undefined, 8, undefined, undefined]
之所以会出现undefined,是因为map()方法创建了一个新数组,但新数组并不是在遍历完array1后才被赋值的,而是每遍历一次就得到一个值。所以,下面这样修改后就正确了:

var array1 = [1, 4, 9, 16];

const map1 = array1.map(x => {
if (x == 4) {
return x * 2;
}
return x;
});

2、forEach:

不会改变原数组
除抛出异常以外,没有办法中止或跳出forEach
不对未初始化的值进行任何操作

如果要提前终止循环,可以使用:
1、 一个简单的for循环
2、 for of / for in 循环
3、 Arrary.prototype.every()/some()/find()…
….

例子:
function flatten(arr) {
const result = [];

arr.forEach((i) => {
if (Array.isArray(i))
result.push(…flatten(i));
else
result.push(i);
})

return result;
}

// Usage
const problem = [1, 2, 3, [4, 5, [6, 7], 8, 9]];

flatten(problem); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

2、for in /for of区别比较

for of
在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
可以由break,throw,continue或return终止
for in
遍历的是数组的索引(即键名)
遍历的是数组元素值

for…of 语句遍历可迭代对象定义要迭代的数据
for…in 语句以任意顺序迭代对象的可枚举属性。
Array.prototype.method=function(){}
var myArray=[1,2,4];
myArray.name=“数组”;

for (var index in myArray)
console.log(myArray[index]); //0,1,2,method,name

for (var value of myArray)
console.log(value); //1,2,4

for in的一些缺陷:
索引是字符串型的数字,因而不能直接进行几何运算
遍历顺序可能不是实际的内部顺序
for in会遍历数组所有的可枚举属性,包括原型。例如的原型方法method和name属性
故而一般用for in遍历对象而不用来遍历数组

这也就是for of存在的意义了,for of 不遍历method和name,适合用来遍历数组

总结来说:
for of 遍历数组
for in 遍历对象

3、Object的一些方法

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
Object构造函数的方法:
Object.assgin() 通过复制一个或多个对象来创建一个新的对象
Object.create() 使用指定的原型对象和属性创建一个新对象
Object.defineProperty() 给对象添加一个属性并指定该属性的配置
Object.defineProperties() 给对象添加多个属性并分别指定他们的配置
Object.entries() 返回给的对象自身可枚举属性的[key,value]数组
Object.freeze() 冻结对象:其他代码不能删除或更改任何属性
Object.keys() 返回一个包含所有给定对象自身可枚举属性名称的数组
Object.values() 返回给定对象自身可枚举值得数组

什么是可枚举性?

可枚举性概念:用来控制所描述的属性,是否将被包括在for…in循环之中。具体来说,如果一个属性的enumerable为false。下面三个操作不会取到该属性:
for…in
Object.keys()
JSON.stringify

(4)、深拷贝和浅拷贝

参考:https://juejin.cn/post/6844904197595332622
浅拷贝:
拷贝的基本类型和引用类型,基本类型修改不会影响,引用类型的修改会相互影响,引用类型是公用的一个内存地址
深拷贝:
拷贝出一个新的内存地址,新旧对象不会互相影响

https://user-gold-cdn.xitu.io/2020/3/1/170965259fb768fd?imageView2/0/w/1280/h/960/format/webp/ignore-error/1

https://user-gold-cdn.xitu.io/2020/3/1/1709652a7948d1b8?imageView2/0/w/1280/h/960/format/webp/ignore-error/1

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

赋值和深/浅拷贝的区别

赋值:当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。

深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。

实现方式:

浅拷贝:

  1. Object.assgin
  2. Lodash的.clone方法
  3. 扩展运算符 …
  4. Array.prototype.concat()
  5. Array.prototype.slice()

深拷贝:

  1. JSON.parse(JSON.stringify()),这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则
  2. 函数库lodash的_.cloneDeep方法
  3. jQuery.extend()方法:$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
  4. 手写递归方法:
    递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
    初版:
    function deepClone(obj){
    let copyObj = {}
    for(let key in obj){
    if(typeof obj[key] === ‘object’){
    copyObj[key] = deepClone(obj[key])
    }else{
    copyObj[key] = obj[key]
    }
    }
    return copyObj
    }

终极版:
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== “object”) return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);

————剩余问题记录——

4、map/forEach/for in /for of 区别,return,braek能跳出吗?
map:方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
5、构造函数,calss
6、原型、原型链、constructor
7、new一个函数发生了什么
9、变量提升、作用域
10、闭包
11、纯函数
12、冒泡排序、常见排序
13、观察者模式、工厂模式
14、柯里化函数
15、地狱回调

兼容:

1、IE9以下的IE浏览器不支持 addEventListener()和removeEventListener(),使用 attachEvent()与detachEvent() 代替,因为IE9以下是不支持事件捕获的

创建对象的方式:
1、 字面量
2、 构造函数
3、 原型
4、 工厂模式
5、 …

实现继承的方式:
1、 原型链
2、

(5)、原型、构造函数、实例、原型链

参考:https://blog.csdn.net/m0_37585915/article/details/80843945

每个构造函数的proptotype都指向原型对象,原型对象的constructor又指向构造函数,构造函数的实例的_propto_指向原型对象
当这个原型对象是另一个类型的实例,那么这个原型对象的_propto_就指向另一个原型,以此类推就是一条原型链。原型链的根本就是Object.prototype

原型链

从一个实例对象向上找有一个构造实例的原型对象,这个原型对象又有构造它的上一级原型对象,如此一级一级的关系链,就构成了原型链

new一个函数发生了什么?

创建了一个对象
执行构造函数,并且把属性方法设置给了对象
把this的指向指向给对象
将对象的__proto__ 跟函数的Prototype做对应

类数组转换为数组的方法

1、 slice()
2、 Array.from()
3、 扩展运算符

柯里化函数:

https://www.jianshu.com/p/2975c25e4d71

(6)、变量提升、作用域、作用域链

变量提升:
用var声明一个函数或定义一个变量时,这个定义会提升到方法体的最顶端
函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。
函数声明提升的优先级高于变量声明的提升

作用域:
作用域就是变量和函数的可访问范围,当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain),来保证对执行环境有权访问的变量和函数的顺序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象。然后会一层层向外查找,直到发现第一个指定的变量为止。

var a = {name: ‘xuxi’};
function b(a){
a.age = 12;
a = {num: 1};
return a
}
var a1 = b(a);
console.log(a, a1)

1)我们根据之前介绍的作用域和作用域链的概念可以知道,在函数体内,变量会就近查找,而函数参数会存在于函数体内部作用域中,所以当我们把全局变量a当作入参传递给函数时,又由于全局a是引用类型,此时只是引用了它的地址,那么我们通过a.age设置属性时,全局a也会改变。
(2)第二步是将a赋予了一个新的值,此时的a根据就近查找其实是参数a,本质上是将参数a赋予了一个新的对象,这个时候和全局变量的a没有任何关系了,此时函数最后会返回一个新的对象。
综上两步分析,我们就会明白为什么打印a时输出的是{name: ‘xuxi’, age: 12},打印a1会输出{num: 1}了。
参考自:https://juejin.cn/post/6844903985695080455

作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为我们提供了‘块级作用域’,可通过新增命令let和const来体现。

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行

暂时性死区、let/const
块级作用域通过Let/const声明,会产生暂时性死区,在指定块的作用域外无法被访问
声明变量不会提升到代码块顶部
参考:https://juejin.cn/post/6844903797135769614

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值