区分:js表达式 和 js代码(语句)
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
- (1). a
- (2). a+b
- (3). demo(1)
- (4). x === y ? 'a' : 'b' 2.js代码(语句)
- (1). if(){}
- (2). for(){}
JavaScript中什么是基本数据类型什么是引用数据类型?以及各个数据类型是如何存储的?
基本数据类型:
- Number
- String
- Boolean
- Null
- Undefined
- Symbol(ES6新增数据类型)
- bigInt(ES6新增数据类型) 引用数据类型统称为Object类型(只要能new出来,就是引用数据类型)
- Object
- Array
- Date
- Function
- RegExp
数据存储有哪些方式,他们都有哪些区别
基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,每个对象在堆中有一个引用地址。引用类型在栈中会保存他的引用地址,以便快速查找到堆内存中的对象。
数据类型转换
转化为 String 类型:String() / toString()
- 显式:String()方法可以用来显式将值转为字符串。
String([1,2,3]) //"1,2,3" String({}) //"[object Object]" - 隐式:隐式转换通常在有 + 运算符并且有一个操作数是 string 类型时被触发。
“+”代表的字符串拼接,如果下面的情况存在时会触发转换。
有两边,一边是字符串,则会变成字符串拼接;
有两边,一边是对象
1 + '123' //"1123" 1 + {} //"1[object Object]"
转化为 Boolean 类型: Boolean()
- 显式:Boolean()方法可以用来显式将值转换成布尔型。
- 隐式:隐式类型转换通常在逻辑判断或者有逻辑运算符时被触发(|| && !)。
- boolean 类型转换只会有 true 或者 false 两种结果。除了“0/NaN/空字符串/null/undefined”五个值是false,其余都是true
转化为 Number 类型:Number() / parseFloat() / parseInt()
- 显式:Number()方法可以用来显式将值转换成数字类型。
-
- 字符串转换为数字:空字符串变为0,如果出现任何一个非有效数字字符,结果都是NaN
Number("") //0 Number("10px") //NaN Number("10") //10
- 字符串转换为数字:空字符串变为0,如果出现任何一个非有效数字字符,结果都是NaN
-
- 布尔转换为数字
Number(true) //1 Number(false) //0
- 布尔转换为数字
-
- null和undefined转换成数字
Number(null) //0 Number(undefined) //NaN
- null和undefined转换成数字
-
- Symbol无法转换为数字,会报错:Uncaught TypeError: Cannot convert a Symbol value to a number
-
- BigInt去除“n”
Number(12312412321312312n) //12312412321312312
- BigInt去除“n”
-
- 对象转换为数字,会按照下面的步骤去执行
- 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法
- 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值
- 再调用对象的 toString 把其变为字符串
- 最后再把字符串基于Number()方法转换为数字
let obj ={name:'xxx'} console.log(obj-10) // 数学运算:先把obj隐式转换为数字,再进行运算 //运行机制 obj[Symbol.toPrimitive] //undifined obj.valueof() // {name:xxx} obj.toString() // [object object] Number ("[object object]") // NaN NaN-10 // NaN
- 对象转换为数字,会按照下面的步骤去执行
- 隐式:number 的隐式类型转换是比较复杂的,因为它可以在下面多种情况下被触发。
- 比较操作(>, <, <=, >=)
- 按位操作(| & ^ ~)
- 算数操作(- + * / %), 注意:当 + 操作存在任意的操作数是 string 类型时,不会触发 number 类型的隐式转换
- 一元 + 操作
- juejin.cn/post/695617…
操作符==两边的隐式转换规则
如果两边数据类型不同,需要先转为相同类型,然后再进行比较,以下几种情况需要注意一下:
- 对象==字符串 比较时 将对象转换为字符串
[1,2,3]=='1,2,3' //true
[1,2,3][Symbol.toPrimitive] //undefined
[1,2,3].valueOf() //[1, 2, 3]
[1,2,3].toString() //"1,2,3"
- null/undefined ==liang两边都为null, undefined的时候,不会进行类型转化,得到的结果都是true。
console.log( null == null );//true
console.log( undefined == undefined );//true
console.log( null == undefined );//true
null==undefined //true
null===undefined //false
//null/undefined和其他任何值都不相等
- 对象==对象 比较的是堆内存地址,地址相同则相等
{}=={} //false 因为比较的是地址
- NaN NaN跟谁都不等,包括他自己
console.log( NaN == 0 );//false
console.log( NaN == undefined );//false
console.log( NaN == null );//false
console.log( NaN == NaN );//false
- 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较
console.log( 1 == '1' );//true 1 == Number('1')
console.log( false == '0' );//true Number(false) == Number('0') 0 == 0
console.log( '' == 0); // true Number('') == 0
console.log( 1 === true) // true Number(true) == 1
console.log( false == 0 ) // true
{} + [] === 0 // true
[] + {} === 0 // false
{} + []
/**
* 对于编译器而言,代码块不会返回任何的值
* 接着+[]就变成了一个强制转number的过程
* []通过oPrimitive变成'',最后''通过ToNumber操作转换成0
**/
{}; +[];
判断数据类型有几种方法
- typeof
- 优点:能够快速区分(除null以外的)基本数据类型
- 缺点:判断引用类型,除了function 全返回object类型, 所以不能将Object、Array和Null区分,都返回object
- instanceof
- 优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象
- 缺点:Number,Boolean,String基本数据类型不能判断
- constructor
- 由于undefined和null是无效的对象,因此是没有constructor属性的,这两个值不能用这种方法判断.
- toString
- 优点:精准判断数据类型
- 缺点:写法繁琐不容易记,推荐进行封装后使用
- juejin.cn/post/691980…
- typeof 适合基本类型和function类型的检测,无法判断null与object
- instanceof 适合自定义对象,也可以用来检测原生对象,在不同的iframe 和 window间检测时失效,还需要注意Object.create(null)对象的问题
- constructor 基本能判断所有类型,除了null和undefined,但是constructor容易被修改,也不能跨iframe使用
- tostring能判断所有类型,Object.prototype.toString方法返回对象的类型字符串,因此可用来判断一个值的类型。因为实例对象有可能会自定义toString方法,会覆盖Object.prototype.toString,所以在使用时,最好加上call。所有的数据类型都可以使用此方法进行检测,且非常精准
instanceof原理
instanceof 运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置.
基于原型链的原理:从实例对象的构造函数的原型开始向上寻找,构造函数的原型又有其原型,一直向上找,直到找到原型链的顶端Object.prototype为止。如果没有,则返回null.
换句话说,如果A instanceof B,那么 A 必须是一个对象,而 B 必须是一个合法的 JavaScript 函数。在这两个条件都满足的情况下:
判断 B 的 prototype 属性指向的原型对象(B.prototype)是否在对象 A 的原型链上。
如果在,则为 true;如果不在,则为 false。
juejin.cn/post/684490…
为什么typeof null是Object
这个bug是第一版Javascript留下来的,javascript中不同对象在底层都表示为二进制,而javascript 中会把二进制前三位都为0的判断为object类型,而null的二进制表示全都是0,自然前三位也是0,所以执行typeof时会返回 'object'。
==和===有什么区别
slice、splice、splite三者之间的区别与用法
call、apply、bind有什么区别(简单)手写call、apply、bind(进阶)
bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。 juejin.cn/post/684490…
Object.create()、new Object()和字面量{}的创建对象区别(简单)手写一个new(进阶)
面量和new关键字创建的对象是Object的实例,原型指向Object.prototype,继承内置对象Object Object.create(arg, pro)创建的对象的原型取决于arg,arg为null,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象 juejin.cn/post/684490…
为什么JS是单线程的?
因为JS里面有可视的Dom,如果是多线程的话,这个线程正在删除DOM节点,另一个线程正在编辑Dom节点,导致浏览器面对相互矛盾的两个同时进行的操作,不知道听谁的
什么是作用域,什么是作用域链?
- 全局作用域:代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
- 函数作用域:在固定的代码片段才能被访问
- 作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
- 作用域链参考链接一般情况下,变量到创建该变量 的函数的作用域中取值。但是如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
什么是执行栈,什么是执行上下文?
执行栈
执行栈,也就是“调用栈”,被用来存储代码运行时创建的所有执行上下文。 当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。 引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
执行上下文
执行上下文:理解为代码执行的环境,是当前代码执行的一个环境与作用域。
它产生的时间是代码正式执行之前
(区别下作用域什么时候产生的,作用域是在代码定义的时候产生的)
- 执行上下文的工作内容:
- 代码提升(收集变量名不赋值和收集函数)
- 确认this的指向(全局,局部)
- 创建作用域链(父级作用域链+当前的变量对象)
var let const 有什么区别
- var定义的变量
- var可以先使用,后声明,因为存在变量提升;
- var没有块的概念,可以跨块访问, 不能跨函数访问。
- var是允许在相同作用域内重复声明同一个变量的,let与const不允许这一现象
- let定义的变量
- let必须先声明后使用;
- 只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
- const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
变量和函数怎么进行提升的?优先级是怎么样的?
js引擎在代码正式执行之前会做一个预处理的工作
- 收集变量(var 将var后边的变量定义但是不赋值 var 变量名= undefined)
- 收集函数(提前定义该函数)
如何实现异步编程?事件循环(event loop)
既然JS是单线程的,只能在一条线程上执行,又是如何实现的异步呢? 是通过的事件循环(event loop),理解了event loop机制,就理解了JS的执行机制。
- 运行栈 任务队列 事件循环 JS的执行顺序:
- 同步程序
- process.nextTick(也属于微任务,但是是同步任务之后,异步任务之前,为了区分,单铃出来)
- 微任务(promise.then)
- 宏任务(计时器,ajax,读取文件)
- setImmediate
宏任务和微任务都有哪些?宏任务和微任务都是怎样执行的
- 宏任务:计时器,ajax,读取文件
- 微任务:promise.then JS的执行顺序:
- 同步程序
- process.nextTick(微任务)
- 微任务
- 宏任务
- setImmediate
什么是闭包?闭包的作用?闭包的应用?
闭包的概念
函数执行时形成的私有上下文EC(FN),正常情况下,代码执行完会出栈后释放;但是特殊情况下,如果当前私有上下文中的某个东西被上下文以外的事物占用了,则上下文不会出栈释放,从而形成不销毁的上下文。 函数执行函数执行过程中,会形成一个全新的私有上下文,可能会被释放,可能不会被释放,不论释放与否,他的作用是:
(1)保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,保护自己的私有变量不受外界干扰(操作自己的私有变量和外界没有关系);
(2)保存:如果当前上下文不被释放【只要上下文中的某个东西被外部占用即可】,则存储的这些私有变量也不会被释放,可以供其下级上下文中调取使用,相当于把一些值保存起来了;
我们把函数执行形成私有上下文,来保护和保存私有变量机制称为闭包。
闭包是指有权访问另一个函数作用域中的变量的函数--《JavaScript高级程序设计》
稍全面的回答: 在js中变量的作用域属于函数作用域, 在函数执行完后,作用域就会被清理,内存也会随之被回收,但是由于闭包函数是建立在函数内部的子函数, 由于其可访问上级作用域,即使上级函数执行完, 作用域也不会随之销毁, 这时的子函数(也就是闭包),便拥有了访问上级作用域中变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
闭包的特性:
1、内部函数可以访问定义他们外部函数的参数和变量。(作用域链的向上查找,把外围的作用域中的变量值存储在内存中而不是在函数调用完毕后销毁)设计私有的方法和变量,避免全局变量的污染。
1.1.闭包是密闭的容器,,类似于set、map容器,存储数据的
1.2.闭包是一个对象,存放数据的格式为 key-value 形式
2、函数嵌套函数
3、本质是将函数内部和外部连接起来。优点是可以读取函数内部的变量,让这些变量的值始终保存在内存中,不会在函数被调用之后自动清除
闭包形成的条件:
函数的嵌套
内部函数引用外部函数的局部变量,延长外部函数的变量生命周期
闭包的用途:
模仿块级作用域
保护外部函数的变量 能够访问函数定义时所在的词法作用域(阻止其被回收)
封装私有化变量
创建模块
闭包应用场景
闭包的两个场景,闭包的两大作用:保存/保护。 在开发中, 其实我们随处可见闭包的身影, 大部分前端JavaScript 代码都是“事件驱动”的,即一个事件绑定的回调方法; 发送ajax请求成功|失败的回调;setTimeout的延时回调;或者一个函数内部返回另一个匿名函数,这些都是闭包的应用。
闭包的优点:
延长局部变量的生命周期
闭包缺点:
会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
什么是原型?什么是原型链?如何理解
原型:
- 原型关系:
每个 class都有显示原型 prototype
每个实例都有隐式原型 _ proto_
实例的_ proto_指向对应 class 的 prototype - 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。
-原型: 在 JS 中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。
原型链:
- 多个__proto__组成的集合成为原型链
- 所有实例的__proto__都指向他们构造函数的prototype
- 所有的prototype都是对象,自然它的__proto__指向的是Object()的prototype
- 所有的构造函数的隐式原型指向的都是Function()的显示原型
- Object的隐式原型是null
- 原型链:函数的原型链对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针是指向上一层的原型对象,而上一层的原型对象的结构依然类似。因此可以利用__proto__一直指向Object的原型对象上,而Object原型对象用Object.prototype.__ proto__ = null表示原型链顶端。如此形成了js的原型链继承。同时所有的js对象都有Object的基本防范 特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?
箭头函数和普通函数的区别
- 箭头函数语法上比普通函数更加简洁
- 箭头函数没有自己的this,它里面出现this是继承函数所处上下文中的this,(使用call,apply等任何方式都无法改变this的指向)
- 箭头函数中没有arguments(类数组),只能基于…ARG获取传递的参数集合
- 箭头函数不能被new函数(因为:箭头函数没有this,也没有prototype) 注:回调函数的this一般指向window
为什么要使用箭头函数,箭头函数的使用场景
- 箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如:用在 map、reduce、filter 的回调函数定义中
- 箭头函数的亮点是简洁,但在有多层函数嵌套的情况下,箭头函数反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数
- 箭头函数要实现类似纯函数的效果,必须剔除外部状态。所以箭头函数不具备普通函数里常见的 this、arguments 等,当然也就不能用 call()、apply()、bind() 去改变 this 的指向
- 箭头函数不适合定义对象的方法(对象字面量方法、对象原型方法、构造器方法),因为箭头函数没有自己的 this,其内部的 this 指向的是外层作用域的 this
- 箭头函数不适合定义结合动态上下文的回调函数(事件绑定函数),因为箭头函数在声明的时候会绑定静态上下文
如何理解js中的this关键词,JS 中 this 的五种情况
- 作为普通函数执行时,this指向window。
- 当函数作为对象的方法被调用时,this就会指向该对象。
- 构造器调用,this指向返回的这个对象。
- 箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
- 基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new 时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。
说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点。
什么是内存泄漏
内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏
为什么会导致的内存泄漏
内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃
垃圾回收机制都有哪些策略?
- 标记清除法 垃圾回收机制获取根并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除
- 引用计数法 当声明一个变量并给该变量赋值一个引用类型的值时候,该值的计数+1,当该值赋值给另一个变量的时候,该计数+1,当该值被其他值取代的时候,该计数-1,当计数变为0的时候,说明无法访问该值了,垃圾回收机制清除该对象
深拷贝和浅拷贝有什么区别?(简单)手写浅拷贝深拷贝(进阶)
浅拷贝:仅仅复制对象的引用,而不是对象本身。 深拷贝:复制对象所引用的全部对象。
事件冒泡和事件捕捉有什么区别
- 事件冒泡:事件会从最内层的元素开始发生,一直向上传播,直到document对象。
- 事件捕获:与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
JS性能优化的方法
数组去重的方式 实现手写
- ES6 最常用Set 去重
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。
function unique(arr) { return Array.from(new Set(arr)) } let arr = [1, 1, 'true', 'true', true, true, 15, 15]; console.log(unique(arr))// [ 1, 'true', true, 15 ] let unique2 = arr => [...new Set(arr)] console.log(unique2(arr));// [ 1, 'true', true, 15 ] - 双重for循环
外层循环元素,内层循环时比较值。
function unique(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) { //第一个等同于第二个,splice方法删除第二个
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
let arr = [1, 1, 'true', 'true', true, true, 15, 15];
console.log(unique(arr))// [ 1, 'true', true, 15 ]
- indexOf或includes去重
当数组内没有该元素时,indexOf返回-1,则把它push进新数组。
function unique(arr) { var array = []; for (var i = 0; i < arr.length; i++) { if (array .indexOf(arr[i]) === -1) { array .push(arr[i]) } } return array; }function unique(arr) { var array = []; for (var i = 0; i < arr.length; i++) { if (!array.includes( arr[i]) ) { array .push(arr[i]) } } return array; } - filter去重
原始数组中元素的索引等于当前索引值时返回,否则返回当前元素
function unique(arr) { var res = arr.filter(function(item, index, array) { return array.indexOf(item) === index }) return res }
setTimeout、Promise、Async/Await 的区别
题目的本质,就是考察setTimeout、promise、async await的实现及执行顺序,以及JS的事件循环的相关问题。
Async/Await 如何通过同步的方式实现异步
说说 Promise 的原理?你是如何理解 Promise 的?(简单)手写promise(进阶)promise是同步任务还是异步任务?
www.jianshu.com/p/002003a38… Promise本身是同步的,他的then方法和catch方法是异步的
合并多个JavaScript的对象
Object.assign进行的拷贝是浅拷贝。也就是说,如果拷贝过来的属性的值是对象等复合属性,那么只能拷贝过来一个引用- Object.assign进行合并的时候,一旦碰到同名属性,就会出现覆盖现象。所以使用时务必小心。
- Object.assign是针对Object开发的API,一旦在源对象的参数未知接收到了其他类型的参数,会尝试类型转换。如果是数组类型的话,类型转换的结果是将每个数组成员的值作为属性键值,将数组成员在数组中的位置作为属性键名。多个数组组成参数一同传入的话还会造成覆盖。
原生JS操作DOM的方法
查找:
- getElementByid,
- getElementsByTagName,
- querySelector,
- querySelectorAll
- 插入:appendChild,insertBefore
- 删除:removeChild
- 克隆:cloneNode
- 设置和获取属性:setAttribute(“属性名”,”值”),getAttibute(“属性名”)
说一下ES6新增特性
ES6新增特性常用的主要有:let/const,箭头函数,模板字符串,解构赋值,模块的导入(import)和导出(export default/export),Promise,还有一些数组字符串的新方法
说一下JS数组内置遍历方法有哪些和区别
- forEach:这个方法是为了取代for循环遍历数组的,返回值为undefined
- filter:是一个过滤遍历的方法,如果返回条件为true,则返回满足条件为true的新数组
- map:这个map方法主要对数组的复杂逻辑处理时用的多,特别是react中遍历数据,也经常用到,写法和forEach类似
- some:这个some方法用于只要数组中至少存在一个满足条件的结果,返回值就为true,否则返回fasel, 写法和forEach类似
- every:这个every方法用于数组中每一项都得满足条件时,才返回true,否则返回false, 写法和forEach类似
说一下JS事件代理(也称事件委托)是什么,及实现原理?
JS事件代理就是通过给父级元素(例如:ul)绑定事件,不给子级元素(例如:li)绑定事件,然后当点击子级元素时,通过事件冒泡机制在其绑定的父元素上触发事件处理函数,主要目的是为了提升性能,因为我不用给每个子级元素绑定事件,只给父级元素绑定一次就好了,在原生js里面是通过event对象的targe属性实现
异步解决方案主要有三个:
- 回调函数
- promise(重点掌握)
- generator(了解)
- async和await(重点掌握)
构造函数 执行new到底做了什么
- 首先在内存中创建了一个新的空对象
- 然后让this指向这个对象
- 然后执行构造函数里面的代码,给这个新对象添加属性和方法
- 最后返回这个新对象(所以构造函数里面不需要return)