1.javaScript有哪些数据类型,它们的区别
js有基本数据类型和引用数据类型
- 基本数据类型为null、undefined、symbol、boolean、string、number
- 引用数据类型为object 区别:
- 基本数据类型是存储在栈空间中,
- 占据的空间小,且大小一定
- 引用数据类型是存储在堆空间中
- 引用数据存放在堆空间中,但保存数据的变量存储的是一个地址指针,该指针指向堆空间中的数据位置
栈和堆的理解:
- 栈采用先进后出原则,其中存储的是基本数据类型和引用数据的地址,不直接存储引用数据
- 堆用于存放引用数据,如对象、数组
- 在堆中分配的内存需要手动进行释放,否则容易导致内存泄露
- 对象在堆中分配的内存由垃圾回收器负责管理,通过标记清除法等算法来识别不再使用的对象并释放
2.数据类型检测的方法有哪些
- typeof:只能判断基本数据类型
- instanceof:只能判断引用数据类型,用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
- 缺点是当原型链被改后,无法准确进行判断
- Object.prototype.toString.call:可以用于判断基本数据类型和引用数据类型
- constructor:用于判断对象是否为某个构造函数的实例
- obj.proto.constructor === Object
3.判断数组的方式
- Object.prototype.call
- constructor
- instanceof
- Array.isArray
- 原型链判断 arr.proto === Array.prototype
- Array.prototype.isPrototypeOf(arr):用于检测对象是否是另一个对象的原型
- prototypeObj.isPrototypeOf(object)
- prototypeObj: 用于检查的原型对象。
- object: 要检查的对象。
- isPrototypeOf 返回一个布尔值,如果prototypeObj 是 object 的原型,那么返回 true,否则返回 false。
4.typeof null的结果
typeof null为对象,这是js的一个bug
5.instanceof操作符的实现原理及实现
instanceof用于判断构造函数的prototype是否在对象的原型链上
function myInstanceof(obj,constructor){
// 获取对象的原型
let objPro = Object.getPrototypeOf(obj);
// 获取构造函数的原型
const constPro = constructor.prototype;
while(objPro) {
if(constPro === objPro) {
return true
}
objPro = Object.getPrototypeOf(objPro)
}
return false
}
6.如何安全的获取undefined
因为undefined不是js的关键字,所以undefined可以用作变量名,可以通过void 0 来安全的获取
7.typeof NaN
NaN表示不是一个数字,用来指出数字类型中的错误情况,typeof NaN结果为number
8.isNaN和Number.isNaN的区别
- isNaN:在运算时,它会先尝试能否转换为数字,若不能则返回true
- isNaN('abc') 会返回true
- Number.isNaN:运算时不会进行数字转换,即只有当结果真的为NaN时才会返回true
- Number.isNaN('avc') 返回false
9.==
操作符的转换规则
10.其他值到字符串的转换规则
- 使用String() 对其他值进行包裹,将会
- 字符串 -> 字符串
- 数字 -> 用引号包裹的数字
- 布尔值
- true -> 'true'
- false -> 'false'
- null -> 'null'
- undefined -> 'undefined'
- 对象
- 先调用对象的toString方法
- 若返回的是基本数据类型,则调用String方法
- 若返回的是对象,则调用对象的valueOf方法
- 若返回的是基本数据类型,则调用String方法
- 若返回的是对象,则直接报错
- 先调用对象的toString方法
- 数组(相当于就是把数组左右的花括号去掉)
- 空数组 -> '' 空字符串
- [1,2,3] -> '1,2,3'
- ['a','b','c'] -> 'a,b,c'
11.其他值到数字的转换规则
- 使用Number() 对其他类型进行包裹,将会
- 数字 -> 数字
- 字符串
- 判断字符串中除了数字是否有其他类型的
- 若为空字符串 -> 0
- 若为纯数字的字符串 -> 转为数字
- 若含有非数字的成员 -> NaN
- 判断字符串中除了数字是否有其他类型的
- 布尔值
- true -> 1
- false -> 0
- null -> 0
- undefined -> NaN
- 对象
- 先调用对象的valueOf方法
- 若返回的是基本数据类型,则调用Number进行判断
- 若返回的是对象,则继续调用对象的toString方法
- 若返回的是基本数据类型,则调用Number进行判断
- 若返回的还是对象,则直接报错
- 先调用对象的valueOf方法
- 数组
- 空数组 -> 0
- 长度为1的数组
- 若为数字 -> 直接就是数字
- 若为非数字 -> NaN
- 长度大于1的数组 -> NaN
// 正常情况下 对象的toString会返回一个字符串,表示该对象的类型 [object Object]
Number({
valueOf() { return { a: 123 } },
toString() { return '[object object]' }
})
// 若最后的toString返回的还是对象,则报错
Number({
valueOf() { return { a: 123 } },
toString() { return { age: 1232 } }
})
当然,对应的valueOf和toString方法也可以自定义
const obj = {
valueOf(){
return 1212
},
toString(){
return '1212'
}
}
12.其他值到布尔值的转换规则
- 使用Boolean()进行包裹,将会
- 以下五个将转为false
- '' 空字符串
- 0、-0、+0
- null
- undefined
- NaN
- false
- 其余将转为true
- 以下五个将转为false
13.Object.is()和 ==、=== 的区别
- == 操作符进行比较时,当两侧数据类型不一致时,会尝试进行类型转换,当转换后仍然不一致,才返回false
- === 操作符进行比较时,不会进行类型转换,直接进行比较,结果不一致直接返回false,缺点是
- -0和+0 结果是false
- NaN和NaN比较也是false
- Object.is() 比较时,在===基础上,对-0和+0、NaN进行优化,即它们都是true
14.什么是包装类型
当对基本类型数据进行对象操作时,js会隐式的将基本类型包装成对象类型,然后进行对象操作后,再将包装类型移除
const a = 'abc';
a.length // 3
// js会隐式的包装成对象
{
let str = String(a)
str.length
str = null
}
15.js如何进行隐式类型转换
在js中,隐式转换是建立在强制转换的基础上的。
- 自动转换为布尔值
- 在if判断条件中,会自动进行布尔值转换
- !!变量,也会进行布尔值转换
- 自动转换为字符串
- 当使用+运算符且有一边是字符串时,会自动进行字符串转换,然后进行拼接
- 如,一边是字符串,另一边是数字,则会先将数字进行字符串隐式转换,然后再进行拼接
- 当使用+运算符且有一边是字符串时,会自动进行字符串转换,然后进行拼接
- 自动转为数字
- 当使用除+外的运算符时,会自动进行数字类型转换,然后进行计算
- 若使用+,则必须保证两边都是数字,若一边为字符串,则进行字符串转换,然后进行拼接
- 在变量前面加个运算符,也会进行数字转换
16.let、const、var的区别
- var
- 存在变量提升,即进行预编译时,会将var定义的变量提前,并赋值为undefined
- 可以重复声明,也可以重复赋值
- 在全局定义时,会将变量添加到window上
- let
- 块级作用域
- 若在函数内定义,函数外无法访问到
- 只能声明一次,若没有赋值,其值为undefined
- 存在暂时性死区,即未定义前无法访问
- 块级作用域
- const
- 定义时必须赋值,否则会报错
- 无法修改值
- 块级作用域,存在暂时性死区
17.const对象的属性可以修改吗
const定义的变量不能修改值,即定义的是基本数据类型,则不能修改,若定义的是引用数据类型,因为const定义的变量保存是引用数据的指针地址,所以可以修改引用数据内部的数据,但不能修改指针地址,即重新赋值
18.new一个对象会发生什么
- 先创建一个空对象
- 将this指向这个对象(为这个对象添加属性和方法)
- 将空对象的原型指向构造函数的原型
- 返回这个对象
19.箭头函数和普通函数的区别
- 箭头函数没有自己的this,它的this为当前的上下文
- 箭头函数的this无法改变,即无法通过call、apply、bind改变
- 箭头函数没有自己的arguments,若箭头函数是在普通函数内部,则它的arguments为普通函数的arguments
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
20.对rest参数的理解
当拓展运算符用在函数形参上时,它会把剩余的形参合并成一个数组,常用于不确定参数个数时使用
21.new操作符的实现原理
- 创建一个空对象,并将空对象的原型指向构造函数
- 将this指向空对象,用于给对象添加属性和方法
- 根据构造函数执行结果
- 若结果是对象,则返回这个执行的结果
- 若是其他,则返回这个对象
function myNew(constructor,...args){
if(typeof constructor !=='function') throw new Error('')
// 创建对象,并指向原型
const obj = Object.create(constructor.prototype)
const res = constructor.apply(obj,args)
return typeof res === 'object' ? res : obj
}
22.call、apply、bind实现
// call实现
Function.prototype.call2 = function (context, ...args) {
context = context || window
const symbol = Symbol()
context[symbol] = this;
const res = context[symbol](...args)
delete context[symbol]
return res
}
// apply实现
Function.prototype.apply2 = function (context, args) {
context = context || window
const symbol = Symbol()
context[symbol] = this;
const res = context[symbol](...args)
delete context[symbol]
return res
}
// bind实现
Function.prototype.bind2 = function (context, ...args) {
context = context || window
const self = this;
function Fn(...args2) {
return self.apply(this instanceof Fn ? this : context, [...args, ...args2])
}
function middle() { }
middle.prototype = this.prototype
Fn.prototype = new middle()
return Fn
}
23.Map和Object的区别
- Map是有序的,其键可以是任意类型
- Object是无序的,其键只能是字符串或者symbol
- Map可以通过size属性获取键值对个数
- Object只能自己去获取
- Map是可迭代的
- Object迭代需要先获取键然后才能迭代
24.Map和WeakMap的区别
- Map的键可以是任意类型
- WeakMap的键只能是对象(null除外)的若引用
- Map的键引用的对象被释放后,但因为Map中存在该对象,所以垃圾回收机制不会回收
- WeakMap则与Map不同,它存放的键外若引用,即当对象的其他引用被释放后,WeakMap对应的对象也会被释放(即不作为垃圾回收机制的引用)
25.JSON的理解
JSON是一种文本的轻量级格式,可以被任何编程语言读取,并作为数据格式来传递。
JSON提供了两个api(JSON.stringify、JSON.parse)来进行js和json之间的转换,但优缺点:
- 不能转换具有symbol类型的属性
- 不能转换函数、日期
- 不支持循环引用
- 性能问题,因为它会遍历整个对象
26.js脚本延迟加载的方式有哪些
- async
- 当html解析遇到带有async的script标签时,会另开一个线程去下载js脚本,不会阻塞DOM的解析,但当async标签的脚本下载完毕后,会立即执行该脚本,此时会阻塞HTML的解析
- 当存在多个async修饰的script标签时,它们不会按书写顺序执行,而是谁先下载完毕谁先执行
- defer
- 当html解析遇到带有defer的script标签时,会另开一个线程去下载js脚本,不会阻塞html的解析,且只有当html解析完毕后,才会执行对应的脚本
- 当存在多个defer修饰的script标签时,会按照书写顺序执行脚本
- 动态生成script标签
- 定时器延迟加载js脚本
- 让js脚本放在文档底部
27.js类数组对象的定义
一个拥有length属性和若干索引属性的对象就可以成为类数组,类数组和数组类似,但它不能调用数组的方法,如arguments和DOM操作获取的数组对象,函数也可以看作类数组,因为它含有lenght属性(接收形参的个数)
若想要调用数组的方法,可以通过call、apply、bind等实现,如Array.prototype.slice.call()
28.数组的原生方法
- length
- slice
- indexOf
- sort
- reverse
- splice
- join
- Array.prototype.isArray
- push
- pop
- shift
- unshift
- includes
- concat
- forEach
- reduce
- map
- every
- some
- filter
29.如何将类数组转为数组
- Array.from()
- Array.prototype.slice.call()
- Array.prototype.slice.apply()
- 拓展运算符 [...xxx]
- Array.prototype.concat.call([],类数组)
30.什么是DOM和BOM
- DOM:文档对象模型,它指的是把文档当作一个对象,这个对象定义了处理网页内容的方法和接口
- BOM:浏览器对象模型,它指的是把浏览器当作一个对象,这个对象定义了与浏览器进行交互的方法和接口。 BOM的核心是window,window对象具有双重角色,它即是全局对象,又是通过js访问浏览器窗口的一个接口,在网页中定义的任意对象、变量和函数,都作为全局对象的一个属性或方法存在,window有location、navigator、document对象等
31.对ajax的理解,并实现一个ajax
ajax是指通过js的异步通信,从服务器获取XML文档,并从文档中提取对应数据,最终操作DOM进行数据更新,从而实现不用刷新整个页面
const ajax = options => {
return new Promise((resolve, reject) => {
const { method = 'get', data = {}, url } = options
let xhr
function objToString(data) {
let arr = []
data.t = new Date().getTime()
for (let key in data) {
arr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
return arr.join('&')
}
const str = objToString(data)
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
xhr = new ActiveXObject('Mircrosoft.xmlHttp')
}
if (method.toLowerCase() === 'get') {
// get请求
xhr.open(method, url + '?' + str, true)
xhr.send()
} else {
// post请求
xhr.open(method, url, true)
xhr.setRequestHeaher('Content-Type', 'application/www-form-urlencoded')
xhr.send(str)
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
resolve(xhr.responseText)
} else {
reject(xhr.responseText)
}
}
}
})
}
32.js为什么会进行变量提升,它导致了什么问题
33.ES6模块与CommonJS模块有什么异同?
- CommonJS是浅拷贝,ES6是对模块的引用,即只能读不能改,类似于const声明的变量,即指针不能改变
- 也就是说CommonJs可以修改引用指针,也可以修改引用数据内部的变量
- ES6只能修改引用数据内部的数据,无法重新赋值
- CommonJs使用require和module.export
- ES6使用import和export
34.常见的DOM操作有哪些
1.DOM节点的获取
- document.querySelect()
- document.querySelectAll()
- document.getElementById()
- document.getElementsByClassName()
- document.getElementsByTagName()
2.DOM节点的创建
- document.createElement() 创建节点
- el.appendChild(dom) 添加节点到页面上
3.删除DOM节点
// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)
35.如何判断一个对象是否属于某个类?
- instanceof
- 通过constructor来判断,但constructor容易被篡改
- Object.prototype.toString() 来判断
36.for in 和 for of 的区别
for in 主要用于遍历对象,它会遍历对象的整个原型链,性能较差 for of是es6新出的,可以用于遍历带有iterator接口的数据结构(如对象、数组、set、map)
37.ajax、fetch、axios的区别
38.原型和原型链
在js中,每个对象都有与之对应的原型,可以通过__proto__访问到,当我们通过构造函数创建对象时,实例对象的__proto__ 指向构造函数的原型,这个原型包含所有通过这个构造函数创建的实例的共享属性和方法。
当访问对象的某个属性或方法时,会先在对象自身上查找是否存在这个属性,若不存在,则沿着原型链(__proto__
)继续往上查找,一直找到原型为null时为止
39.执行上下文/作用域链/闭包
1.对闭包的理解
在js中,闭包是指在当前作用域访问另一个作用域中的变量,即一个函数中返回另一个函数,另一个函数中使用到上一个函数的变量。 用途:
- 通过闭包可以访问另一个函数作用域中的变量的特点,我们可以通过函数返回一个函数,内部使用上一个函数的变量,从而使得可以在外部访问函数内部的私有的变量
- 正常情况下,函数执行完毕(即函数执行上下文)后会被弹出栈,但由于闭包的特性可以访问函数内部的变量,导致执行上下文的变量仍然存在内存中,因为闭包保存了对这个变量的引用,所以这个变量不会被垃圾回收机制回收,容易造成内存溢出
2.对作用域、作用域链的理解
-
作用域分为全局作用域、函数作用域、块级作用域。
-
全局作用域
- window对象上的属性拥有全局作用域
- 没有声明直接使用的变量具有全局作用域
- 最外层函数及最外层函数外声明的变量(var)具有全局作用域
- 过多的全局作用域变量容易造成变量污染
-
函数作用域
- 在函数内部声明的变量(var)具有函数作用域
- 内部的作用域可以访问外部作用域的变量
- 反之外部作用域无法访问到内部的作用域
-
块级作用域
- 使用let、const声明的变量具有块级作用域
- 比较适合用于for循环
-
-
作用域链
- 在当前作用域中查找某个属性时,若没有该属性,则沿着作用域链(即外层/父级作用域)上查找,一直找到顶级作用域(全局作用域),这一层层关系就是作用域链
- 作用域链本质就是一个指向变量对象的指针列表,变量对象包含当前作用域定义的变量、函数、参数。第一位是当前作用域,最后一位是全局作用域
-
自由变量
- 在当前作用域查找所需的变量,但当前作用域没有该变量,则这个变量就是自由变量
40.对执行上下文的理解
1.执行上下文类型
- 全局执行上下文
- 在非函数内部的都是全局执行上下文,它会在全局全局一个window对象,并且this指向这个window
- 函数执行上下文
- 函数执行上下文是通过函数调用创建的,它可以有多个
- eval执行上下文
2.执行上下文栈
代码执行时,会先创建全局执行上下文,并压入到栈底,然后代码执行过程中遇到函数调用,会创建函数执行上下文,并压入栈中,当函数执行完毕后,会进行出栈操作,在这一个过程中,全局执行上下文只能有一个,且一直是栈底,只有当页面销毁时才会释放,栈顶是当前正在执行的函数上下文
3.创建执行上下文
创建执行上下文有两个阶段:创建阶段和执行阶段
- 创建阶段会为执行阶段做一些准备
- 变量对象
- 首先会确定函数形参
- 确定arguments(形参个数length、形参赋值)
- 确定函数字面量声明,并赋值
- var变量声明,但不赋值
- this
- 作用域
- 变量对象
- 执行阶段是进行代码的执行(对创建阶段中的变量进行赋值,执行代码)
在执行一点JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。 在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。
全局上下文:变量定义,函数声明 函数上下文:变量定义,函数声明,this,arguments