重点:原来是整理给自己看看的,所以有点潦草哈哈
刚毕业面试提到最多:
ES6 新增内容
普通函数和箭头函数的区别?
Call bind apply的原理和区别
let const var 的区别
深拷贝和浅拷贝
基本类型引用类型区别
get和post区别
基本类型引用类型区别
目录
setTimeout、Promise、Async/Await 的区别
new 一个构造函数,如果函数返回 return {} 、 return null , return 1 , return true 会发生什么情况?
ESlass 继承,constructor()和 super()关键字
3.object.prototype.toString.call
1.Object.assign(target,source1,source2,...)
事件 onxx 和 addEventListener 有什么区别
Number() 的存储空间是多大?如果后台发送了一个超过最大自己的数字怎么办
知道 ES6 的 Class 嘛?Static 关键字有了解嘛
如果一个构造函数,bind了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?
setTimeout(fn, 0)多久才执行,Event Loop
什么是"use strict";?使用它的好处和坏处分别是什么?
Array.from() 和 Array.of() 的使用及区别?
说说attribute 和 property 的区别是什么?
说说 window.onload 和 $(document).ready 的区别
做一个Dialog组件,说说你的设计思路?它应该有什么功能?
ES6 新增内容
1、模板字符串 变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式
2、箭头函数
3 class 类的继承
class Animal {}
class Dog extends Animal {
constructor(name) {
super();
// this.name = name
}
}
3、module
4、promise
5、const / let
6、 扩展运算符(...)
用于取出参数对象所有可遍历属性然后拷贝到当前对象
可用于合并两个对象
7、 解构赋值
解构赋值语法是一种 Javascript 表达式。通过解构赋值,可以将属性/值从对象/数组中取出,赋值给其他变量。
let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// expected output: 10
console.log(b);
// expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// expected output: Array [30,40,50]
8、 Symbol 数据类型 一种新的原始数据类型,主要是为了避免属性名的重复
9、Map 一种新的数据结构,类似对象,Set 一种新的数据结构,类似数组
普通函数和箭头函数的区别?
1、箭头函数没有自己的 this 指向,它的 this 指向来源于它的上级,并且继承而来的 this 指向是无法改变的。
2、 箭头函数由于没有自己的 this,所以不能作为构造函数。
3、箭头函数中没有 arguments(形参数组),但是可以访问外围函数的arguments对象
补充:ES6 箭头函数中的 this 和所在环境(外层)中的 this 指向一致
1、语法更加简洁、清晰
2、箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
3、箭头函数继承而来的this指向永远不变
4、.call()/.apply()/.bind()无法改变箭头函数中this的指向
5、箭头函数不能作为构造函数使用
6、箭头函数没有自己的arguments,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表
7、箭头函数没有原型prototype
8、箭头函数不能用作Generator函数,不能使用yeild关键字
箭头函数和普通函数有啥区别?箭头函数能当构造函数吗
普通函数通过 function 关键字定义, this 无法结合词法作用域使用,在运行时绑定,只取决于函数的调用方式,在哪里被调用,调用位置。(取决于调用者,和是否独立运行)
箭头函数使用被称为 “胖箭头” 的操作 => 定义,箭头函数不应用普通函数 this 绑定的四种规则,而是根据外层(函数或全局)的作用域来决定 this,且箭头函数的绑定无法被修改(new 也不行)。
箭头函数常用于回调函数中,包括事件处理器或定时器
箭头函数和 var self = this,都试图取代传统的 this 运行机制,将 this 的绑定拉回到词法作用域
没有原型、没有 this、没有 super,没有 arguments,没有 new.target
不能通过 new 关键字调用
一个函数内部有两个方法:[[Call]] 和 [[Construct]],在通过 new 进行函数调用时,会执行 [[construct]] 方法,创建一个实例对象,然后再执行这个函数体,将函数的 this 绑定在这个实例对象上
当直接调用时,执行 [[Call]] 方法,直接执行函数体
箭头函数没有 [[Construct]] 方法,不能被用作构造函数调用,当使用 new 进行函数调用时会报错。
function foo() {
return (a) => {
console.log(this.a);
}
}
var obj1 = {
a: 2
}
var obj2 = {
a: 3
}
var bar = foo.call(obj1);
bar.call(obj2);
箭头函数特点
1. 省略function换成=> 一个参数的时候()可以省略 一个return的时候{}可以省略
2. 不绑定this,其中的this指向函数定义位置的上下文this
3. 内部不存在arguments和new.target,使用的都是外部的
4. 没有原型,占用内存空间小
如何改变 this 指向
可以使用call()、apply()、bind() 来改变 this
JS 中 this 的五种情况
作为普通函数执行时,this指向window。
当函数作为对象的方法被调用时,this就会指向该对象。
构造器调用,this指向返回的这个对象。
箭头函数 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则this绑定到最近的一层对象上。
基于Function.prototype上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this指向除了使用new `时会被改变,其他情况下都不会改变。若为空默认是指向全局对象window。
Js事件循环机制
Event Loop 包含两类:
1. 一类是基于 Browsing Context ,
2. 一种是基于 Worker
二者是独立运行的。
JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程。
任务队列
任务可以分为同步任务和异步任务,同步任务,顾名思义,就是立即执行的任务,同步任务一般会直接进入到主线程中执行;
而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列( Event Queue )的机制来进行协调。
在事件循环中,每进行一次循环操作称为tick,通过阅读规范可知,每一次 tick 的任务处理模型是比较复杂的,其关键的步骤可以总结如下:
在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次)
检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue
更新 render
主线程重复执行上述步骤
Call bind apply的原理和区别
- call 方法 call()是 apply()的一颗语法糖,作用和 apply()一样,同样可实现继承,唯一的区别就在于 call()接收的是参数列表,而 apply()则接收参数数组。
- bind 方法 bind()的作用与 call()和 apply()一样,都是可以改变函数运行时上下文,区别是 call()和 apply()在调用函数之后会立即执行,而 bind()方法调用并改变函数运行时上下文后,返回一个新的函数,供我们需要时再调用.
1、第一个参数都是 this 的指向对象
2、 apply 的第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
3、call 传入的第二个参数是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
4、 bind 传入的第二个参数也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数,供我们需要时再调用.。
promise 用法以及相关原理 用法 有那些 API
1、promise是异步编程的一种解决方案,解决多个异步方法串行的问题,比如回调地狱等;
2、promise,简单地说就是一个容器,里面保存着某个未来才会结束的事件,从语法说promise是一个对象,从他可以获取异步操作的消息。promise提供统一的api,各种操作都可以用相同的方法进行处理.;
3、它接受一个 function 作为参数。function 中有两个形参,第一个表示请求成功的回调,第二个表示请求失败的回调,分别是 resolve 和 reject ;
4、.then 在成功的时候触发 .catch 在失败的时候触发
5、promise 的状态不受外界影响不可逆,三个状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
promise对象的状态改变只有两种可能:从pending变为fulfilled或从pending变为rejected;
6、有两个很重要的 api:
Promise.all() 表示所有的 Promise 数组中的方法都成功之后触发
返回一个新的promise,只有所有的promise都成功才能成功,只要有一个失败了就直接失败;
Promise.race() 表示数组中只要有一个完成就结束
返回一个promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝;
Promise的三个缺点
1)无法取消Promise,一旦新建它就会立即执行,无法中途取消
2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
Promise是为解决什么问题而产生的?
promise是为解决异步处理回调金字塔问题而产生的
回调地狱
回调函数中嵌套回调函数的情况就叫做回调地狱。它就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
promise 和 async await 的区别
1、Promise是 ES6 中处理异步请求的语法,使用 .then() 来实现链式调用,使用 .catch() 来捕获异常。
2、async/await 是对 Promise 的升级,async用于声明一个函数是异步的,await是等待一个异步方法执行完成
3、async/await 用同步的写法写异步(await一个Promise对象),async/await 的捕获异常可以使用 try/catch 语法。(也可以使用 .catch 语法)用同步的方法写异步的代码;
setTimeout、Promise、Async/Await 的区别
setTimeout
settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行。
Promise
Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行。
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function(){
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
async/await
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
传送门 ☞ # JavaScript Promise 专题
Async/Await 如何通过同步的方式实现异步
Async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式,第一个请求的返回值作为后面一个请求的参数,其中每一个参数都是一个promise对象.
let const var 的区别
共同点:都能声明变量
不同点:var 在ECMAScript 的所有版本中都可以使用,而const和let只能在ECMAScript6【ES2015】及更晚中使用
ES6之前创建变量用的是var,之后创建变量用的是let/const
var let const
作用域 函数作用域 块作用域 块作用域
声明提升 能 不能 不能
重复声明 能 不能 不能
全局声明时为window对象的属性 是 不是 不是
var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
var 关键字:
var 定义变量,可以声明同名变量,可以不进行初始化;(不初始化的情况下保存一个特殊值 undefined )
var 不仅可以改变保存的值,也可以改变值的类型(合法但不推荐);
var 定义的变量在函数作用域内有效;
var 声明变量存在变量提升,var 声明的变量可以挂载在 window 上;
let 声明:
let 定义变量,可以不进行初始化;(不初始化的情况下保存一个特殊值 undefined )
let 不仅可以改变保存的值,也可以改变值的类型(合法但不推荐);
let 用来定义局部变量,仅在块级作用域内有效;
let 不允许重复声明;
不存在变量提升的现象,所以一定要在定义后使用,否则报错(暂时性死区);
在全局作用域中用 let 声明变量不会成为 window对象的属性;
for循环使用let来声明迭代变量不会导致迭代变量外渗透。
const 声明:
const 定义的是常量,必须进行初始化(设置初始值);
const 定义好之后“不能改变”;(不能改变栈里面的数据,堆里面的数据可改变;例如数字不可变,对象的属性等可变)
仅在块级作用域内有效;
const 不允许重复声明;
在全局作用域中用 const 声明变量不会成为 window对象的属性;
详细分析查看:https://blog.csdn.net/leoxingc/article/details/127813133
常见数组方法
push() 向数组尾部添加元素,返回添加后的数组长度,原数组改变
pop() 从数组的尾部删除一个元素,返回删除的元素,原数组改变
unshift() 向数组头部添加元素,返回添加后的数组长度,原数组改变
shift() 从数组的头部删除一个元素,返回删除的元素,原数组改变
Slice() 提取数组,(1 起 2 止止不算)原数组不变
splice() 剪接数组,(1 起 2 长 3 添加)原数组改变
reverse() 反转数组,原数组改变
sort() 按位比较
arr.sort(function(a,b){return a - b;}); 从小到大
arr.sort(function(a,b){return b - a;}); 从大到小
Join() 参数最为连接字符,连接数组,转换为字符串,原数组不变
concat() 合并数组,原数组不变 arr1.concat(arr2,arr3)
find 查找符合条件的项
findIndex 查找符合条件项目的下标
toString()把数组转字符串(之间默认用逗号隔开)
ES5 数组的常用方法
indexOf() 查找数组中某元素第一次出现的索引,若无返回-1
lastindexOf() 查找数组中某元素最后一次出现的索引,若无返回-1
forEach() 遍历数组,不接受返回值
map() 映射数组,接受返回值
Filter() 筛选满足条件数组
Every() 判断数组里每一个元素是否符合条件
Some() 判断数组里是否有一个符合条件的元素
reduce() 数组值累加(两个值累加完传递给第一个元素,直到最后)
遍历
1、基本 for 循环
2、for...of...遍历
3、for...in...遍历
4、forEach()遍历
5、map() 映射
6、filter()
7、some()
8、every()
9、reduce()
数组去重
1、利用数组的 indexOf 方法,新建一个空数组,循环遍历旧数组,判断空数组中是否有当前的元素,如果有就跳过,如果没有就执行 push 方法。
let arr = [1, 1, 2, 2, 3, 3, 4, 5];
let newArr = [];
arr.forEach(item => {
if (newArr.indexOf(item) < 0) {
newArr.push(item);
}
});
2、利用数组的 splice 方法,先利用 sort 方法对数组进行排序,然后循环遍历数组,比较前一项与后一项是否相同,如果相同就执行 spilce 方法删除当前项。
3. 利用 ES6 中 Set 不能存放相同元素的特点,配合...展开运算符进行去重。
let arr=[1,2,3,4,3,2,1,5,3];
let set=new Set(arr);
//因为set结构并不是数组,所以需要转为数组
set=[...set];
4. lodash 插件也可以去重。
判断是不是数组
let a = [1, 3, 4];
Array.isArray(a); //true
a instanceof Array; //true
a.constructor === Array; //true
Object.prototype.toString.call(a) === "[object Array]"; //true
for in 和 for of 的区别
for...in 只能获得对象的键名,对数组来说是下标,对象是属性名。并且手动添加的属性也能遍历到;
for...of 只能获得键值(数组),遍历对象会报错;
数组排序
(1)冒泡排序:数组中的元素两两进行比较,如果前一个比后一个大,交换值,第一轮结束
(2)选择排序:选出一个位置,这个位置上的数,和后面所有的数进行比较,如果比较出大小就交换两个数的位置
(3)/*sort排序 */
arr.sort((a,b)=>{
return a-b;
})
什么是伪数组
1、伪数组具备如下特点:
伪数组具有 length 属性;
可以通过下标(索引)找到具体的值;
不具有数组的方法;
伪数组也可以通过遍历去查找每一项。
例如,我们通过 js 从 html 中获取到的 ul 中的所有 li 标签,就是一个伪数组
2、伪数组转为真正的数组
(1)通过循环遍历,将伪数组的每一项push到新数字中
<div class="box">
<p>伪数组</p>
<p>伪数组</p>
<p>伪数组</p>
</div>
<script>
var pList = document.querySelectorAll('.box p');
var newArr = [];
for(var i = 0; i < pList.length; i++) {
newArr.push(pList[i])
}
</script>
(2)使用slice.call(eleArr)方法
var pList = document.querySelectorAll('.box p');
var newArr = [].slice.call(pList)
(3)使用扩展运算符...
var pList = document.querySelectorAll('.box p');
var newArr = [...pList]
(4)使用Array.form()
var pList = document.querySelectorAll('.box p');
var newArr = Array.from(pList)
将伪数组转换为真数组
1、Array.from() 将伪数组转成真数组
arr=Array.from(arr);
2、arguments 将伪数组转成真正的数组
function test (){
arguments.__proto__ = Array.prototype;
arguments.push(10)
console.log(arguments)
}
test(1,2,3,4)
随机取值
数据中有 800 个元素,随机取一个
生成一个 0-800 之间的随机数作为数组下标
const arr = [1,2,3,4,5,6,7...]
// 随机获取一个下标
// Math.random的取值范围 0=<x<1
//const index = Math.floor(Math.random()*arr.length);
const index=Math.floor(Math.random() * 800); // 随机整数
arr[index] // 随机的取一个
删除某个元素
数组中有 800 个元素,删除第 701 个
arr.splice(700, 1); // splice参数一表示开始位置 参数二表示个数 后面的参数序列是用来替换的内容,如果没有就只做删除处理
filter, slice + concat;
字符串方法
字符串和数字相加
相加的时候会做隐式转换,数字转换为字符串,最终的结果是字符串拼接;
字符串如何转变为数字
let str = "123";
console.log(Number(str));.
console.log(str \* 1);深拷贝和浅拷贝
浅拷贝:只拷贝第一级实现方式:...扩展运算符 Object.asign
浅拷贝:基本数据类型拷贝值,复杂数据类型,拷贝地址
深拷贝:逐层拷贝对象的每一级
实现方式:`JSON.parse(JSON.stringify(obj))`或者使用插件 lodash
深拷贝:拷贝的是值,相互之间没有影响 地址指向堆
深浅拷贝是因为引用数据类型数据存储的问题,引用数据类型会把指针存放在栈内存中,真正的值存放在堆内存中,浅拷贝只是拷贝了地址,而深拷贝则是完全拷贝,并且拷贝后的对象和原对象没有任何关联。
栈内存中放地址,堆内存中放值
- 使用递归实现深拷贝。
- 使用 lodash 插件中的 deepclone 来实现深拷贝。
- 使用 JSON.stringfy()和 JSON.parse()来实现。
let obj = {name:'zhangsan',age:18}
let newObj = JSON.parse(JSON.stringfy(obj))
原型和原型链
每个构造函数都有一个原型,每个原型对象又有一个construtor属性指向构造函数,每个实例都有__proto__指向原型对象,原型对象上的属性方法能被实例访问。
在JS中,用 __proto__ 属性来表示一个对象的原型链。当查找一个对象的属性时,JS会向上遍历原型链,直到找到给定名称的属性为止。
JavaScript prototype(原型对象)
所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法。
所有 JavaScript 中的对象都是位于 原型链顶端的 Object 的实例。
每个对象都有一个`__proto__`属性,并且指向它的`prototype`原型对象
每个构造函数都有一个`prototype`原型对象
`prototype`原型对象里的`constructor`指向构造函数本身
原型链的终点 Object.prototype.__proto__ === null //true
原型和原型链的详细介绍:http://t.csdn.cn/zQZMc
什么是原型链?
当对象查找一个属性的时候,如果没有在自身找到,那么就会查找自身的原型,如果原型还没有找到,那么会继续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找停止。这种通过 通过原型链接的逐级向上的查找链被称为原型链
什么是原型继承?
一个对象可以使用另外一个对象的属性或者方法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样根据原型链的规则,如果查找一个对象属性且在自身不存在时,就会查找另外一个对象,相当于一个对象可以使用另外一个对象的属性和方法了。
原型/构造函数/实例
原型(prototype): 一个简单的对象,用于实现对象的属性继承。可以简单的理解成对象的爹。每个JavaScript对象中都包含一个–proto-- (非标准)的属性指向它爹(该对象的原型),可obj.–proto–进行访问。
构造函数: 本质上是一个普通的函数,只是可以通过new来 新建一个对象的函数。
实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数。
new 操作符调用构造函数的步骤
首先创建了一个新的空对象
设置原型,将对象的原型设置为函数的prototype对象。
让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
new 一个函数发生了什么
构造调用:
创造一个全新的对象
这个对象会被执行 [[Prototype]] 连接,将这个新对象的 [[Prototype]] 链接到这个构造函数.prototype 所指向的对象
这个新对象会绑定到函数调用的 this
如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象
new 一个构造函数,如果函数返回 return {} 、 return null , return 1 , return true 会发生什么情况?
如果函数返回一个对象,那么new 这个函数调用返回这个函数的返回对象,否则返回 new 创建的新对象
闭包
闭包就可以在全局函数里面操作另一个作用域的局部变量!
闭包是一个函数,即使在父函数返回之后,它也可以访问其父作用域中的变量。闭包可用于维护状态,或创建私有变量和方法。
在函数外部可以访问函数内部的变量。
使用场景:一时半会想不到了-------(必须答时 1.函数作为参数传递。2.函数作为返回值)
function foo(){
var local = 1
function bar(){
local++ //可以访问这个函数词法作用域中的变量
return local
}
return bar //一个函数被当作值返回
}
var func = foo() //外层函数执行完毕时销毁
func() //这个通道可以访问这个函数词法作用域中的变量,即函数所需要的数据结构保存了下来,而且无法直接访问,必须通过返回的函数。
- 虽然可以保护私有变量,但用多了可能造成内存泄露。
闭包的好处:减少全局变量的定义,减少全局空间的污染;形成命名空间;
闭包的坏处:容易造成内存泄漏:一块内存空间既不能被销毁,也不能被访问,通常出现 IE 低版本;
闭包优化:由于闭包会一直占用内存空间,直到页面销毁,我们可以主动将已使用的闭包销毁,
将闭包函数赋值为null 可以销毁闭包;
闭包 this 执行问题:this指向window对象(因为匿名函数的执行具有全局性,所以其this对象指向window);
遇到内存泄漏的问题
当不断向堆中存储数据,而不进行清理,这就是内存泄漏;
当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露;
哪些操作会造成内存泄露
1、全局变量引起的内存泄露
2、闭包引起的内存泄露:慎用闭包
3、dom清空或删除时,事件未清除导致的内存泄漏
4、循环引用带来的内存泄露
内存泄漏一般是指系统进程不再用到的内存,没有及时释放,造成内存资源浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
答案2
造成内存泄漏的原因有:
全局变量。在局部作用域中,函数执行完毕后,变量就没有存在的必要了,垃圾回收机制很快的做出判断并回收;但是对于全局变量,很难判断什么时候不用这些变量,无法正常回收。
解决办法:
① 尽量少使用全局变量;
② 使用严格模式,在 js 文件头部或者函数的顶部加上use strict。
闭包引起的内存泄露。闭包可以读取函数内部的变量,然后让这些变量始终保存在内存中,如果在使用结束后没有将局部变量清除,就可能导致内存泄露。
解决办法:将事件处理函数定义在外部,解除闭包。
被遗忘的定时器。定时器setInterval或者setTimeout不再需要使用时,且没有被清除,导致定时器的回调函数及其内部依赖的变量都不能被回收,就会造成内存泄漏。
解决办法:当不需要定时器的时候,调用clearInterval或者clearTimeout手动清除。
事件监听。垃圾回收机制不好判断事件是否需要被解除,导致callback不能被释放,此时需要手动解除绑定。
解决办法:及时使用removeEventListener移除事件监听。
元素引用没有清理。
解决办法:移除元素后,手动设置元素的引用为null。
console。传递给console.log的对象是不能被垃圾回收,可能会存在内存泄漏。
解决办法:清除不必要的console。
js 的执行机制?微任务和宏任务
1. js 是单线程的
所谓单线程就是一次只能执行一段代码,在执行的时候如果遇到比较耗时的操作,默认就会停下来等待这个操作完成之后继续走。这种情况下,就会出现页面卡在那里不动。为了解决这个问题 js 一般把比较耗时的操作做异步处理;
2. js 中的异步处理
js 中存在一个异步队列,所有比较耗时的操作都会被丢在这个异步队列中。当主线程空闲(同步代码)之后会执行异步队列中的代码,这个就是 js 中的 eventloop(事件循环);
宏任务,是运行环境提供的异步操作,例如:setTimeout;
微任务,是 js 语言自身的异步操作,例如:Promise;
在一个宏任务周期中会执行当前周期中的所有微任务,当所有的微任务都执行完成之后会进入下一个宏任务周期;
JS 中的继承
1、原型链继承
核心:将父类的实例作为子类的原型
2、构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
3、实例继承
核心:为父类实例添加新特性,作为子类实例返回
4、组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
5、寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
ESlass 继承,constructor()和 super()关键字
super()方法用来调用父类的构造函数;
constructor 是原型对象上的一个属性,默认指向这个原型的构造函数;
Map和Set
- Map
- Map 的键可以是任意值。包括对象{ {a:1}:xxx }
- .set() var myMap = new Map(); myMap.set(0, "zero");
- .get()
- .delet()
- Set
- Set 的键名是唯一的,不能重复,undefined 和 NaN 也不重复
- 用来去重
var mySet = new Set([1, 2, 3, 4, 4]);
[...mySet]; // [1, 2, 3, 4]
Js中Map和Object有什么区别
- Map 是 es6 新增的一种数据结构,继承自 Object,并对 Object 做了一些拓展。
- Map 的键可以是任意的数据类型,Object 不行。
- Map 是可迭代(iterable)的,可以使用 for...of 遍历,而 Object 不行。
- Map 的长度可以通过 size 直接拿到,Object 不行。
- Map 使用拓展运算符是可以直接展开的,Object 不行。
js 中的数据类型
一般数据类型:string number boolean null undefined symbol bigint
`BigInt`数据类型的目的是比`Number`数据类型支持的范围更大的整数值。
复杂数据类型:对象、数组、function
- 值为一个地址,地址指向真正的数据
js 中如何准确判断一个数据的数据类型?
typeof 如果是基本数据类型,使用typeof来判断,但是typeof不能用来判断引用数据类型的数据
不能判断Null、Array等
instanceof 不能检测Null和undefined
contructor
Object.prototype.toString.call( )
typeof 区分原理
typeof原理: 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。
typeof null 为"object", 原因是因为 不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位都为0的话会被判断为Object类型,null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object"。
类型判断
1.typeof
用 typeof 检测**null**返回是 object。在 JavaScript 中 null 表示 "什么都没有"。null 是一个只有一个值的特殊类型。表示一个空对象引用。
typeof 一个**没有值的变量**会返回 **undefined**。
null 和 undefined 的值相等,但类型不等:
typeof "John"; // string
typeof 3.14; // number
typeof false; // boolean
typeof [1, 2, 3, 4]; // object 在JavaScript中,数组是一种特殊的对象类型。 因此 typeof [1,2,3,4] 返回 object。
typeof { name: "John" }; // object
typeof undefined; // undefined
typeof null; // object
null === undefined; // false
null == undefined; // true
2.instanceof
- obj instanceof Object ,可以左边放你要判断的内容,右边放类型来进行 JS 类型判断
- 需要知道是什么类型才能正确判断
[1, 2] instanceof
Array(
// true
function () {}
) instanceof
Function(
// true
{ a: 1 }
) instanceof
Object(
// true
new Date()
) instanceof
Date; // true
3.object.prototype.toString.call
Object.prototype.toString.call(999); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(Symbol()); // "[object Symbol]"
Object.prototype.toString.call(42n); // "[object BigInt]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(true); // "[object Boolean]
Object.prototype.toString.call({ a: 1 }); // "[object Object]"
Object.prototype.toString.call([1, 2]); // "[object Array]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(function () {}); // "[object Function]"
类型转换
toString(); // 转化为字符串,不可以转null和underfined
String(); // 转换为字符串,
Number(); // 转换为数字,字符串中有一个不是数值的字符,返回NaN
parseInt(); // 转换为数字,第一个字符不是数字或者符号就返回NaN
Boolean(); // 转换为布尔值
函数声明和函数赋值语句的差别:
赋值式函数需要给变量赋值,以 var、let;声明式是不需要给变量赋值,以 function 开头;
对象的常用方法
1.Object.assign(target,source1,source2,...)
- 该方法主要用于对象的合并,将源对象 source 的所有可枚举属性合并到目标对象 target 上,此方法只拷贝源对象的自身属性,不拷贝继承的属性。
- Object.assign 方法实行的是浅拷贝,而不是深拷贝,当只有一层时为深拷贝
- 同名属性会替换
2.Object.create()
- Object.create()方法接受两个参数:Object.create(obj , propertiesObject) ;
- propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与 Object.defineProperties()的第二个参数一样)。注意:该参数对象不能是 undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。
- 没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法
3. hasOwnProperty()
- 判断对象自身属性中是否具有指定的属性。obj.hasOwnProperty('name')
4.Object.is()
- 判断两个值是否相同。
Object.is('foo', 'foo')// true
Object.is(window, window)// true
Object.is('foo', 'bar'); // false
Object.is([], []); // false
var test = { a: 1 };
Object.is(test, test); // true
Object.is(null, null); // true
// 特例
Object.is(0, -0); // false
Object.is(-0, -0); // true
Object.is(NaN, 0 / 0); )// true
5.Object.keys(obj)
- 返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)
let arr = ["a", "b", "c"];
Object.keys(arr); //["0", "1", "2"]
let obj = { age: 20, sex: "男" };
Object.keys(obj); //["age", "sex"]
6.Object.values()
- 方法返回一个给定对象自己的所有可枚举属性值的数组,值的顺序与使用 for...in 循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。
var obj = { 10: "a", 1: "b", 2: "c" };
Object.values(obj); // ['b', 'c', 'a']
var obj1 = { 0: "a", 1: "b", 2: "c" };
Object.values(obj1); // ['a', 'b', 'c']
7.entries
- 分割对象,将对象分割为数组
const obj = { foo: "bar", baz: 42 };
Object.entries(obj); // [ ['foo', 'bar'], ['baz', 42] ]
// array like object
const obj = { 0: "a", 1: "b", 2: "c" };
Object.entries(obj); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ]
// string
Object.entries("abc"); // [['0', 'a'], ['1', 'b'], ['2', 'c']]
Object.entries(100); // []
判断两对象是否相等
1、通过JSON.stringify(obj)来判断两个对象转后的字符串是否相等
2、getOwnPropertyNames方法返回一个由指定对象的所有自身属性的属性名组成的数组。先进行长度的比较,然后进行遍历
3.Object.is(a,b)
// 手写:
function diff(obj1,obj2){
var o1 = obj1 instanceof Object;
var o2 = obj2 instanceof Object;
// 判断是不是对象
if (!o1 || !o2) {
return obj1 === obj2;
}
//Object.keys() 返回一个由对象的自身可枚举属性(key值)组成的数组,
//例如:数组返回下表:let arr = ["a", "b", "c"];console.log(Object.keys(arr))->0,1,2;
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false;
}
for (var o in obj1) {
var t1 = obj1[o] instanceof Object;
var t2 = obj2[o] instanceof Object;
if (t1 && t2) {
return diff(obj1[o], obj2[o]);
} else if (obj1[o] !== obj2[o]) {
return false;
}
}
return true;
}
两个对象如果属性名和属性值都一样,他俩是否相等?为什么?
// 他俩不相等,因为对象的存储位置不一样,所以永远不相等
function compareTwoObj(a, b) {
return JSON.stringify(a) == JSON.stringify(b);
}
获取对象中的所有属性名
Object.keys(obj); // 数组,所有属性名组成的数组
for (let k in obj) {
}
json 字符串如何转换为 js 对象
js对象转字符串
JSON.stringify
// json字符串转对象
JSON.parse
如何删除对象中的一个属性
var obj = { a: 1, b: 2, c: 3 };
delete obj.c; // 删除一个属性
var strJson = `{"a": "123", "1-a": "456"}`;
面向对象
面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成类
面向对象的三大基本特征:封装、继承、多态
封装就是把一些通用的代码或者功能封装在一个文件中
继承就是子类可以继承父类的属性和方法,实现代码复用
多态就是一个 function 接收的参数不一样,返回不同的结果
//多态演示
// 构造函数
function Behavior () {}
Behavior.prototype.sound = function (sound) {
return sound
}
function Man () {}
Object.setPrototypeOf(Man.prototype, Behavior.prototype)
function Woman () {}
Object.setPrototypeOf(Woman.prototype, Behavior.prototype)
let man = new Man()
console.log(man.sound('哈哈哈')) // 哈哈哈
let woman = new Woman()
console.log(woman.sound('嘻嘻嘻')) // 嘻嘻嘻
对象常用的操作
- 获取所有的属性名,Object.keys、for in
- 获取所有的属性值,Object.values
- 删除一个属性
- 动态获取属性,可以使用数组下标的方式
js 中的常用事件绑定方法
1. 在DOM元素中直接绑定
2. 在JavaScript代码中绑定
3. 绑定事件监听函数
事件传播流程
三个阶段:捕获、事件源、冒泡
捕获是从 html 标签开始,逐层往内进行传递,到触发事件的标签为止
事件源是在当前触发事件的标签上进行传递
冒泡是从当前触发事件的标签开始逐层往外进行传递到 html 节点结束
addEventLister 的第三个参数,是一个 bool 值。默认是 false,表示在冒泡阶段触发
event 对象中的 target 和 currentTarget 有什么区别?
target 触发事件的标签
current Target 绑定事件的标签
addEventListener,第三个参数是一个 bool 值,默认是 false 表示在冒泡阶段触发事件
事件监听、事件委托
- 事件监听
addEventListener,第三个参数是一个 bool 值,默认是 false 表示在冒泡阶段触发事件
addEventListener(事件类型,处理函数,布尔值) 布尔值 I.false 事件冒泡 默认 II.true 事件捕获
使用事件监听器的好处:
使用事件监听器方式添加事件,可以给同一个 DOM 对象添加多个相同的事件名,对应事件处理函数会依次执行
普通事件添加方式,如果添加多个相同的事件名,后面的会覆盖前面,只会执行最后一个
on类型的事件在事件冒泡阶段触发
target(触发事件的标签)/currentTarget(绑定事件的标签)
**DOM标准事件流的触发的先后顺序为:先捕获再冒泡**
**阻止冒泡**:stopPropagation || cancelBubble
#### 事件默认行为
a标签的跳转,input框的输入,Submit按钮的提交,右键菜单
**阻止默认事件**:returnValue=false || preventDeafault
- 事件委托
- 利用事件冒泡的原理,将本应该添加某个 DOM 对象上的事件委托给他的父级
- 让新增的 DOM 具有相同的事件,减少事件添加的次数,提高执行效率
- 使用场景:大量标签绑定同一事件,动态渲染的标签
事件委托是什么
事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。
运用场景:
(1)ajax 局部刷新区域;
(2)绑定层级比较低的时候,不在 body 上绑定;
(3)定次数较少的时候,把多个事件绑定合并到一次事件委托中,由这个事件委托的回调,来进行分发。
事件 onxx 和 addEventListener 有什么区别
onClick 表示添加一个点击事件,属于属性赋值。属性多次赋值会出现覆盖,只有最后一次的赋值是有效果的
addEventlistener 事件委托,可以添加多次,每一次添加都会执行,它可以接收三个参数('事件名字', 回调函数, bool)
bool 默认是 false 表示在冒泡阶段触发
事件拖拽
01-在允许拖拽的节点元素上,使用 on 来监听 mousedown(按下鼠标按钮)事件,鼠标按下后,克隆当前节点
02-监听 mousemove(鼠标移动)事件,修改克隆出来的节点的坐标,实现节点跟随鼠标的效果
03-监听 mouseup(放开鼠标按钮)事件,将原节点克隆到鼠标放下位置的容器里,删除原节点,拖拽完成。
事件是如何实现的?
基于发布订阅模式,就是在浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发的时候才会执行。
比如点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。
在 Web 端,我们常见的就是 DOM 事件:
DOM0 级事件,直接在 html 元素上绑定 on-event,比如 onclick,取消的话,dom.onclick = null,同一个事件只能有一个处理程序,后面的会覆盖前面的。
DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件可以有多个事件处理程序,按顺序执行,捕获事件和冒泡事件
DOM3级事件,增加了事件类型,比如 UI 事件,焦点事件,鼠标事件
DOM/BOM
BOM 是 browser object model 的缩写,简称浏览器对象模型。是用来获取或设置浏览器的属性、行为,例如:新建窗口、获取屏幕分辨率、浏览器版本号等。 比如 alert();弹出一个窗口,这属于 BOM,核心对象是 window。
DOM 是 Document ,简称文档对象模型。是用来获取或设置文档中标签的属性,例如获取或者设置 input 表单的 value 值。document.getElementById("").value; 这属于 DOM,核心对象是 标签。
说出几个常用的 BOM 对象和方法
alert('提示信息')
confirm("确认信息")
prompt("弹出输入框")
open("url 地址",“black 或 self”,“新窗口的大小”)
close() 关闭当前的网页
常见的 dom 元素选择方式有哪些
getElementById
getElementsByClassName
getElementsByTagName
querySelector
querySelectorAll
dataset 数据集
是为标签添加的属性 data-开头的这些,在 js 中可以直接通过使用 dom 对象的 dataset 属性获取
设计模型
工厂模式、单例模式、原型模式,组合模式,发布-订阅模式
jQuery 优点和缺点
优点:出色的浏览器兼容性;出色的DOM操作的封装,可以进行快速的DOM元素操作;支持链式操作,支持插件
缺点:不能向后兼容,一个页面使用多个插件容易冲突,对动画和特效的支持差;
axios 和 ajax
Ajax 核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱
axios支持 Promise API,提供了一些并发请求的接口,自动转换JSON数据,体积也较小;
说说Ajax原理?
Ajax的原理简单来说实在用户和服务器之间加了一个中间层(AJAX引擎),由XMLHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
Ajax的过程只涉及JavaScript、XMLHttpRequest和DOM。XMLHttpRequest是Ajax的核心机制
Ajax的优点:
通过异步模式提升了用户体验
优化了浏览器和服务器之间的传输,减少了不必要的数据往返,减少了带宽占用
Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载
Ajax可以实现局部刷新
Ajax的缺点:
安全问题,Ajax暴露了与服务器的交互细节
对搜索引擎的支持比较弱
Ajax的请求过程:
// 1、创建连接
var xhr = null;
xhr = new XMLHttpRequest();
// 2、连接服务器
xhr.open('get', url, true);
// 3、发送请求
xhr.send(null);
// 4、接受请求
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
success(xhr.responseText);
} else {
// false
fail && fail(xhr.status);
}
}
}
什么是 mvc
mvc 是一种开发模式
model,用来存储数据
view,用来展示数据
Null 和 undefined 的区别
undefined和null的区别:.
● undefined 表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。. null 则表示"什么都没有",即"空值"。.
● Javascript将未赋值的变量默认值设为 undefined ;Javascript从来不会将变量设为 null 。. 它是用来让程序员表明某个用var声明的变量时没有值的; controller [kənˈtrəʊlə(r)] ,用来控制数据的展示方式
Require 和 import
答案2
null 是一个表示”无”的对象,转为数值时为 0;undefined 是一个表示”无”的原始值,转为数值时为 NaN。
当声明的变量还未被初始化时,变量的默认值为 undefined。
null 用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。
undefined 表示”缺少值”,就是此处应该有一个值,但是还没有定义。
require和import的区别
1.import在代码编译时被加载,所以必须放在文件开头,require在代码运行时被加载,所以require理论上可以运用在代码的任何地方,所以import性能更好。
2.import引入的对象被修改时,源对象也会被修改,相当于浅拷贝,require引入的对象被修改时,源对象不会被修改,官网称值拷贝,我们可以理解为深拷贝。
3.import有利于tree-shaking(移除JavaScript上下文中未引用的代码),require对tree-shaking不友好。 4.import会触发代码分割(把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件),require不会触发。
5.import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法,require 是 AMD规范引入方式。
目前所有的引擎都还没有实现import,import最终都会被转码为require,在webpack打包中,import和require都会变为_webpack_require_。
基本类型引用类型区别
基本类型
基本类型的访问是按值访问的,就是说我们可以操作保存在变量中的实际的值。基本类型有以下几个特点:
基本类型的值是不可变得:
2.基本类型的比较是值的比较:
3.基本类型的变量是存放在栈区的(栈区指内存里的栈内存)
引用类型
引用类型可以拥有属性和方法,属性又可以包含基本类型和引用类型。引用类型的有以下一些特性:
1.引用类型的值是可变的
2.引用类型的值是同时保存在栈内存和堆内存中的对象
3.引用类型的比较是引用的比较
js的基本数据类型和引用类型
值类型(基本类型):
字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
引用数据类型:
对象(Object)、数组(Array)、函数(Function)。
说说js缓存
好处:
(1).当页面渲染的数据过多时,为了减轻对内存的占用,对初次接收且会用到的数据进行本地缓存,是有着大好处的.
(2).受网速等各种因素的影响,当渲染数据过多时,若存在频繁的切换页面,用户体验效果不佳。
本地存储
storage来对数据进行存储(sessionStorage、localStorage)
sessionStorage
临时的会话存储,只要当前的会话窗口未关闭,存储的信息就不会丢失,即便刷新了页面或者在编辑器中更改了代码,存储的会话信息也不会丢失。
localStorage
不主动去清除,会一直将数据存储在客户端的储存方式,即使关闭了浏览器,下次打开的时候仍然可以看到之前存储的未主动清除的数据(即便是
杀毒软件或者浏览器自带的清除功能,也不能将localStorage存储的数据清除掉)
storage存储的数据只能是字符串类型,其他类型的数据需做类型转换
storage直接属于顶层对象window.
cookie
cookie属于较老且最常见用的最多的技术了,cookie的优点很多,用起来也比较方便
cookie缺陷:4kb,cookie本地存储的数据会被发送到服务器
cookie和storage的区别
1.cookie兼容所有的浏览器(本地cookie谷歌不支持),storage不支持IE6~8;
2.二者对存储的内容均有大小限制,前者同源情况写一般不能存储4kb的内容,后者同源一般能存储只能存储5MB的数据
3.cookie有过期时间,localStorage是永久存储(如果你不手动去删除的话)
4.一些浏览器处于安全的角度可能会禁用cookie,但无法禁用localStorage
谈谈Cookie的弊端?
(1)Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。
(2)安全性问题。如果cookie被 人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
(3)有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
0.1 + 0.2 === 0.3 嘛?为什么?
JavaScirpt 使用 Number 类型来表示数字(整数或浮点数),遵循 IEEE 754 标准,通过 64 位来表示一个数字(1 + 11 + 52)
1 符号位,0 表示正数,1 表示负数 s
11 指数位(e)
52 尾数,小数部分(即有效数字)
最大安全数字:Number.MAX_SAFE_INTEGER = Math.pow(2, 53) - 1,转换成整数就是 16 位,所以 0.1 === 0.1,是因为通过 toPrecision(16) 去有效位之后,两者是相等的。
在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,然后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度丢失。
所以总结:精度丢失可能出现在进制转换和对阶运算中
JS 整数是怎么表示的?
通过 Number 类型来表示,遵循 IEEE754 标准,通过 64 位来表示一个数字,(1 + 11 + 52),最大安全数字是 Math.pow(2, 53) - 1,对于 16 位十进制。(符号位 + 指数位 + 小数部分有效位)
Number() 的存储空间是多大?如果后台发送了一个超过最大自己的数字怎么办
Math.pow(2, 53) ,53 为有效数字,会发生截断,等于 JS 能支持的最大数字。
写代码:实现函数能够深度克隆基本类型
浅克隆:
function shallowClone(obj) {
let cloneObj = {};
for (let i in obj) {
cloneObj[i] = obj[i];
}
return cloneObj;
}
深克隆:
考虑基础类型
引用类型
RegExp、Date、函数 不是 JSON 安全的
会丢失 constructor,所有的构造函数都指向 Object
破解循环引用
function deepCopy(obj) {
if (typeof obj === 'object') {
var result = obj.constructor === Array ? [] : {};
for (var i in obj) {
result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
}
} else {
var result = obj;
}
return result;
}
NaN 是什么,用 typeof 会输出什么?
Not a Number,表示非数字,typeof NaN === 'number'
手写题:Promise 原理
class MyPromise {
constructor(fn) {
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
this.state = 'PENDING';
this.value = '';
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(value) {
if (this.state === 'PENDING') {
this.state = 'RESOLVED';
this.value = value;
this.resolvedCallbacks.map(cb => cb(value));
}
}
reject(value) {
if (this.state === 'PENDING') {
this.state = 'REJECTED';
this.value = value;
this.rejectedCallbacks.map(cb => cb(value));
}
}
then(onFulfilled, onRejected) {
if (this.state === 'PENDING') {
this.resolvedCallbacks.push(onFulfilled);
this.rejectedCallbacks.push(onRejected);
}
if (this.state === 'RESOLVED') {
onFulfilled(this.value);
}
if (this.state === 'REJECTED') {
onRejected(this.value);
}
}
}
js脚本加载问题,async、defer问题
如果依赖其他脚本和 DOM 结果,使用 defer
如果与 DOM 和其他脚本依赖不强时,使用 async
<script src=’xxx’ ’xxx’/>外部js文件先加载还是onload先执行,为什么?
onload 是所以加载完成之后执行的
手写题:数组去重
Array.from(new Set([1, 1, 2, 2]))
事件循环机制 (Event Loop)
事件循环机制从整体上告诉了我们 JavaScript 代码的执行顺序 Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
先执行宏任务队列,然后执行微任务队列,然后开始下一轮事件循环,继续先执行宏任务队列,再执行微任务队列。
宏任务:script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
微任务:process.nextTick()/Promise
上诉的 setTimeout 和 setInterval 等都是任务源,真正进入任务队列的是他们分发的任务。
优先级
setTimeout = setInterval 一个队列
setTimeout > setImmediate
process.nextTick > Promise
for (const macroTask of macroTaskQueue) {
handleMacroTask();
for (const microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
知道 ES6 的 Class 嘛?Static 关键字有了解嘛
为这个类的函数对象直接添加方法,而不是加在这个函数对象的原型对象上
如果一个构造函数,bind了一个对象,用这个构造函数创建出的实例会继承这个对象的属性吗?为什么?
不会继承,因为根据 this 绑定四大规则,new 绑定的优先级高于 bind 显示绑定,通过 new 进行构造函数调用时,会创建一个新对象,这个新对象会代替 bind 的对象绑定,作为此函数的 this,并且在此函数没有返回对象的情况下,返回这个新建的对象
setTimeout(fn, 0)多久才执行,Event Loop
setTimeout 按照顺序放到队列里面,然后等待函数调用栈清空之后才开始执行,而这些操作进入队列的顺序,则由设定的延迟时间来决定
什么是promise和async-await?
Promises 是一种在 JavaScript 中启用异步编程的方法。一般来说,Promise 意味着程序调用函数时期它返回调用程序可以在进一步计算中使用的结果。
Async-await 也有助于异步编程。它是Generator的语法糖。Async-await 语法简单,很容易在单个函数中维护大量异步调用。此外, async-wait 可以防止回调地狱。
const myPromise = new Promise((resolve, reject) => {
// condition
});
图片懒加载和预加载
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
JS的new执行时做了什么
在内存中创建一个新的空对象;
让this指向这个空对象;
执行构造函数里面的代码,给这个新对象添加属性和方法;
返回这个新对象(所以构造函数里面不需要return)。
JS中对this的理解
普通函数中,this 指向全局对象 window;
定时器中,this 指向调用对象 window;
构造函数中,this 指向当前实例化的对象;
事件处理函数中,this 指向事件触发对象。
在 js 中一般理解就是谁调用这个 this 就指向谁。
改变this指向
call、apply、bind 三者均为改变 this 指向的方法。
call(无数个参数),第一个参数:改变 this 指向;第二个参数:实参;使用之后会自动执行该函数。
apply(两个参数),第一个参数:改变 this 指向;第二个参数:数组(里面为实参);使用时候会自动执行函数。
bind(无数个参数),第一个参数:改变 this 指向;第二个参数之后:实参;返回值为一个新的函数;使用的时候需要手动调用下返回 的新函数(不会自动执行)。
this 的理解?
概念:
this是 JS 的一个关键字,它是函数运行时,自动生成的一个内部对象,只能在函数内部使用,随着函数使用场合的不同,this的值会发生变化,但有一个总的原则:this指的是调用函数的那个对象。
this的指向:
① 作为普通函数执行时,this指向window,但在严格模式下this指向undefined。
② 函数作为对象里的方法被调用时,this指向该对象.
③ 当用new运算符调用构造函数时,this指向返回的这个对象。
④ 箭头函数的this绑定看的是this所在函数定义在哪个对象下,就绑定哪个对象。如果存在嵌套,则this绑定到最近的一层对象上。
⑤ call()、apply()、bind()是函数的三个方法,都可以显示的指定调用函数的this指向。
获取窗口元素的高度的区别
clientHeight:表示的是可视区域的高度,不包含 border 和滚动条。
offsetHeight:表示可视区域的高度,包含了 border 和滚动条。
scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
clientTop:表示边框 border 的厚度,在未指定的情况下一般为 0。
scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的父坐(css定位的元素或 body 元素)距离顶端的高度。
typeof和instanceof的区别
在 js 中,判断一个基本数据的变量的类型会常用 typeof。使用 typeof 在判断引用(复杂)数据类型的时候,无论引用的是什么类型的对象,都会输出'object',此时需要用 instanceof 来检测某个对象是不是另一个对象的实例
JS 中 == 和 === 区别是什么?
1、对于string,number等基础类型,==和===有区别
1)不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等。
2)同类型比较,直接进行“值”比较,两者结果一样。
2、对于Array,Object等高级类型,==和===没有区别
进行“指针地址”比较。
3、基础类型与高级类型,==和===有区别
1)对于==,将高级转化为基础类型,进行“值”比较。
2)因为类型不同,===结果为false。
什么是"use strict";?使用它的好处和坏处分别是什么?
在代码中出现表达式-"use strict"; 意味着代码按照严格模式解析,这种模式使得Javascript在更严格的条件下运行。
好处:
消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的Javascript做好铺垫。
坏处:
同样的代码,在"严格模式"中,可能会有不一样的运行结果;
一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。
什么是跨域?有什么方法解决跨域带来的问题?
跨域需要针对浏览器的同源策略来理解,同源策略指的是请求必须是同一个端口,同一个协议,同一个域名,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
受浏览器同源策略的影响,不是同源的脚本不能操作其他源下面的对象。想要操作另一个源下的对象是就需要跨域。
常用解决方案:
跨域资源共享(CORS)
nginx代理跨域
nodejs中间件代理跨域
jsonp跨域
解释什么是Json
(1)JSON 是一种轻量级的数据交换格式。
(2)JSON 独立于语言和平台,JSON 解析器和 JSON 库支持许多不同的编程语言。
(3)JSON的语法表示三种类型值,简单值(字符串,数值,布尔值,null),数组,对象
ajax请求的时候get 和post方式的区别
最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。
防抖和节流的区别,以及手写实现?
防抖:多次触发事件,事件处理函数只执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发,准备执行事件函数前,会等待一定的时间,在这个等待时间内,如果没有再次被触发,那么就执行,如果又触发了,那就本次作废,重置等待时间,直到最终能执行。
主要应用场景:搜索框搜索输入,用户最后一次输入完,再发送请求;手机号、邮箱验证输入检测
节流:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
主要应用场景:高频点击、表单重复提交等。
/*** 防抖函数 n 秒后再执行该事件,若在 n 秒内被重复触发,则重新计时
* @param func 要被防抖的函数
* @param wait 规定的时间
*/
function debounce(func, wait) {
let timeout;
return function () {
let context = this; // 保存this指向
let args = arguments; // 拿到event对象
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args)
}, wait)
}
}
/*** 节流函数 n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
* @param fn 要被节流的函数
* @param wait 规定的时间
*/
function throttled(fn, wait) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, wait);
}
}
}Ajax
Array.from() 和 Array.of() 的使用及区别?
Array.from():将伪数组对象或可遍历对象转换为真数组。
接受三个参数:input、map、context。
input:待转换的伪数组对象或可遍历对象;
map:类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组;context:绑定map中用到的 this。
Array.of():将一系列值转换成数组,会创建一个包含所有传入参数的数组,而不管参数的数量与类型,解决了new Array()行为不统一的问题。
列举Java和JavaScript之间的区别?
Java是一门十分完整、成熟的编程语言。相比之下,JavaScript是一个可以被引入HTML页面的编程语言。这两种语言并不完全相互依赖,而是针对不同的意图而设计的。 Java是一种面向对象编程(OOPS)或结构化编程语言,类似的如C ++或C,而JavaScript是客户端脚本语言,它被称为非结构化编程。
ViewState和SessionState有什么区别?
“ViewState”特定于会话中的页面。
“SessionState”特定于可在Web应用程序中的所有页面上访问的用户特定数据。
说说JS延时加载的方式有哪些?
defer 和 async、动态创建DOM的方式(用的最多)、按需异步载入js
说说attribute 和 property 的区别是什么?
attribute 是dom元素在文档中作为html标签拥有的属性
property 是dom元素在js中作为对象用户的属性
对于html的标准属性来说,attribute 和 property 是同步的,是会自动更新的
但是对于自定义的属性来说,他们是不同步的
说说 window.onload 和 $(document).ready 的区别
window.onload()方法是必须等到页面包括图片的所有元素加载完毕后才能执行
$(document).ready() 是 DOM 结构绘制完毕后执行,不必等到加载完毕
做一个Dialog组件,说说你的设计思路?它应该有什么功能?
该组件需要提供 hook 指定渲染位置,默认渲染在body下面
然后该组件可以指定外层样式,如宽度等
组件外层还需要一层mask来遮住底层内容,点击mask可以执行传进来的onCancel函数关闭Dialog
另外组件是可控的,需要传入visible来表示是否可见
然后Dialog可以能要自定义头部和底部,默认有头部和底部,底部有一个确认按钮和取消按钮,按钮文字可以传入,按钮可以传入是否显示。确认按钮会执行外部传进来的onOk事件,取消按钮会执行外部传进来的onCancel事件
当组件的visible为true的时候,设置body的overflow为hidden,隐藏body的滚动条,反之显示滚动条
组件高度可能大于页面高度,组件内部需要滚动条
只有当组件的visible有变化且为true时,才重新渲染组件内的所有内容
JS的输出与输入
JS输出:
alert(“提示内容”);弹出警告框
document.write(“提示内容”) ; 向文档流中打印输出内容
console.log(“提示内容”);向控制台输出日志信息
- - 提示内容可以是字符串,数字或变量,表达式等等
JS输入:
prompt(提示信息,默认值)
用户点击确定按钮,得到输入的值,
用户点击取消按钮,得到Null
confirm (确认对话框)
可以用变量接收它的结果,用户点击确定返回true,用户点击取消返回false.
表单中的输入框
return、break和continue的区别
return
在函数体中遇到return语句,则结束函数执行(函数体未执行完部分不再执行),
将表达式的值返回到函数调用处。
使用return最多只能返回一个值!
break
break主要用在循环语句或者switch语句中,用来退出整个语句块。
break跳出最里层的循环,并且继续执行该循环下面的语句。
break当用于循环结构时,表示退出其所在的整个循环结构,当前次循环未完成任务及未完成循环次数将不再执行!
continue
continue适用于任何循环控制结构中。作用是让程序立即跳转到下一次循环的迭代。
在for循环中,continue语句使程序立即跳转到更新语句。
在while或者do。。。while循环中,程序立即跳转到布尔表达式的判断语句。
continue只能用于循环结构,表示结束当前次循环,还会判断条件进行下一次循环。
小总结
break; 可用作于switch和循环
continue; 只可用作于循环
return表达式; 只可用作于函数