1.js当中有哪些数据类型
5个基础:字符串,布尔,数值,null,undefined,1个复杂:Object
在es6下新增一种叫symbol的数据类型,而且初始化不需要new操作符
防止属性名的冲突,这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖
1.2 如何对这些数据类型进行判断
- 1.对于普通的数据类型
typeof,他的返回值有六种,null和object返回都是object,而其余的都是对应的字符串,包括function。
1.2.1 typeof 通常会跟一个(),这个括号可不可以省略
typeof是一个一元运算符,所以是可以不跟括号的,括号的作用只是为了看起来方便,而且一元运算符有很高的优先级,即使做多个typeof判断也没有问题
1.2.2 既然null和object返回都是object,那么如何对对象进行数据类型判断
1.数组数据类型判断
+1.constructor
每一个对象实例都可以通过 constrcutor 对象访问它的构造函数
console.log([].constrcutor)
//f Array(){[native code]}
- 2.isArray()
Array.isArray([1, 2, 3]);
// true
3.特殊情况
一般情况上面两种方法没有问题,当页面存在两个iframe,而且判断是在两个iframe之间进行判断的时候,那么这两种方法就很有可能出现问题,因为两个页面就会有两个window对象,而constrcutor 判断出的构造函数是在两个不同的window对象上的,所以这个时候使用这两个方法判断的时候就会出现问题。4.判断一个对象是否为数组的正确方法
Object.prototype.toString().call(变量) -->得到类型的字符串
2.数组有哪些方法
join()
push()和pop()
shift() 和 unshift()
sort()
reverse()
concat()
arrayObject.slice(start,end)
arrayObject.splice(index,howmany,item1,.....,itemX)
splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。
indexOf()和 lastIndexOf()
forEach()
map(func) :通过指定函数处理数组的每个元素,并返回处理后的数组。
var numbers = [4, 9, 16, 25];
function myFunction() {
console.log(numbers.map(Math.sqrt));
}
输出结果为:
2,3,4,5
filter():创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
every():用于检测数组所有元素是否都符合指定条件(通过函数提供)
some(func) :用于检测数组中的元素是否满足指定条件(函数提供)
var ages = [3, 10, 18, 20];
function checkAdult(age) {
return age >= 18;
}
function myFunction() {
console.log(ages.some(checkAdult));
}
reduce()和 reduceRight()
reduce()
方法接收一个回调函数作为累加器,数组中的每个值(从左到右)开始合并,最终为一个值。
function callbackfn(preValue,curValue,index,array){}
var arr = [0,1,2,3,4];
arr.reduce(function (preValue,curValue,index,array) {
return preValue + curValue;
}); // 10
可以使用reduce()
实现数组求和
2.1 对于这些数组的方法有哪些对原数组是有影响的?
push()和pop(),shift() 和 unshift(),sort(),reverse(),splice()
其它的不会影响,要么返回新数组要么返回其他数据类型,数字或者布尔类型
2.2 数组如何去重
- 1.创建空数组,使用循环push的方式,一个一个把元素push进去,push之前用indexof判断一下push的值在新数组里有没有,没有就push进去,有就判断下一个
- 2.直接在原先的数组上进行去重,用splice方法,在原数组上依次进行对比,如果对比的元素在他前面或者后面存在的话,就把当前这个值删掉,并且数组长度减一
var arr = [1,3,4,5,6,5,6,6,6,4];
for(var i = 0 ; i < arr.length ; i++){
for(var j = i+1 ; j < arr.length ; j++){
if(arr[i] == arr[j]){
arr.splice(j,1);
j--;
}
}
}
- 3.利用对象的属性不能重复的特性进行去重
Array.prototype.distinct = function (){
var arr = this,
i,
obj = {},
result = [],
len = arr.length;
for(i = 0; i< arr.length; i++){
if(!obj[arr[i]]){ //如果能查找到,证明数组元素重复了
obj[arr[i]] = 1;
result.push(arr[i]);
}
}
return result;
};
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
var b = a.distinct();
console.log(b.toString()); //1,2,3,4,5,6,56
遍历 数组的元素,将元素作为对象的属性并赋值,每一次遍历的时候判断对象是否有该属性,如果没有再给对象添加属性
- 4.先对原数组进行排序,然后循环,如果相邻的两个元素相同,那就删掉一个元素
- 5.一行代码实现数组去重
//ES6中新增了Set数据结构,类似于数组,但是 它的成员都是唯一的 ,其构造函数可以接受一个数组作为参数,如:
let array = [1, 1, 1, 1, 2, 3, 4, 4, 5, 3];
let set = new Set(array);
arr=[...new_set];)
console.log(arr);
// =>[1, 2, 3, 4, 5]
2.3 伪数组有哪些?如何把一个伪数组转化为真数组
2.3.1 伪数组有哪些?
- 1.dom选择器选出来的节点列表
- 2.函数中的arguments是对象,函数参数不确定的时候通常会调用arguments对象
- 3.jquery选择器选出的也是伪数组,实际上他也是一个jquery对象
2.3.2 如何把一个伪数组转化为真数组
- 1.创建数组,然后把伪数组里的值依次push进去
- 2.Array.prototype.slice().call(伪数组)或者[].prototype.slice().call
3.字符串
3.1 字符串有哪些方法
- 1.str.charAt(index); 返回子字符串,index为字符串下标,index取值范围[0,str.length-1]
- 2.str.charCodeAt(index); 返回子字符串的unicode编码,index取值范围同上
- 3.String.fromCharCode(num1,num2,...,numN); 根据unicode编码返回字符串
- 4.indexOf(),lastIndexOf()
- 5.slice(),replace()
- 6.match(): 返回所有查找的关键字内容的数组
- 7.split()
- 8.substring()(不接受负值参数),substr()
- 9.toLowerCase(),toUpperCase()
- 10.str.concat("thank you")); //连接字符串
- 11.str.trim()
3.2 slice,substring(),substr()有什么区别
- 1.相同点:
这三个方法都是返回被操作字符串的一个子字符串,
就是返回一个新的字符串。
1、第一个参数是指定字符串的开始位置
2、第二次参数(在指定的情况下)表示字符串到哪里结束
3、如果没有第二个参数,则将字符串结束的未位作为结束位置 - 2.不同点:
1、slice()和substring()第二次参数指定的是字符串最后一个字符后面的位置;
substr()第二个参数指定返回的字符串个数;
2、slice() 会将所有的负数与字符串的长度相加
substr() 会将第一个负参数与字符串长度相加,第二个负参数转化为 0
substring() 将所有的负参数转化为 0
4.数值类型
4.1 如何判断是不是NaN
isNaN()函数,它是判断一个值能否被 Number() 合法地转化成数字。
但是对于一些没法转化成数字的值,它都有可能告诉你这是数值。
所以最好的办法是全等和自身进行判断,因为在js当中,只有NaN不全等自身
或者es6的Object.is和===一样
5.布尔类型
5.1 在js当中那些值会转化成布尔类型为false
六种:undefined,null,0/-0,NaN,false,‘’
5.2 如何快速的将一个值转为布尔类型
!!+值
5.3 new Boolean(false)->true
5.3.1.new操作符的执行过程
+1.创建一个空对象
- 2.修改this指针,将this指针指向创建出来的这个对象
- 3.运行构造函数里面的所有代码
- 4.将创建出来的对象作为返回值进行返回
使用new操作符的时候如果没有参数的时候,括号可以省略
5.3.2 对于构造函数来讲,里面的返回值应该是什么样的
返回值分成这样几个问题:
- 1.new操作符本来就会返回一个对象然后再将它返回
- 2.如果在构造函数里写了一个返回值
- 1.如果返回的不是一个对象,会被忽略
- 2.返回的是一个对象,会把设置的对象进行返回(单例模式)
6.js当中继承是如何实现的
对象a,对象b,让b继承于a
1.原型链继承:
子类的原型对象指向父类的实例
- 1.优点
1简单:B.prototype=new A
2父类原型原型对象中增加的属性和方法在子类中都能访问的到 - 2.缺点
1为子类增加方法,必须在B.prototype=new A之后
2属性和方法都是共享的。
3无法实现多继承。
4无法传参。
2.构造函数式继承
原型链继承虽然简单,但是无法传参是最大弱点(多继承在开发中使用并不多)。如果想让子类拥有父类的方法和属性,最简单的方式就是将父类的构造函数拷贝到子类中一份。或者说在子类的构造函数中运行一下父类的构造函数。
1.核心
用call和apply在子类的构造函数中运行父类的构造函数,一般使用A.apply(this,arguments)、2.优点
1可以实现多继承(call或apply多个父类)
2解决的共享问题
3可以传参。3.缺点
1实例只是子类的实例,不是父类的实例。无法被instanceof和isPropertyOf识别。
2只能继承构造函数内的属性和方法,不能继承原型对象上的属性和方法。
3方法都是在构造函数中运行的,无法复用。
instanceof的用法
- 1.instanceof 就是判断一个实例是否属于某种类型
// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo)//true
- 2.instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型
// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();//JavaScript 原型继承
var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true
isPrototypeOf的用法
isPrototypeOf()方法测试一个对象是否存在另一个对象的原型链上
var o={};
function Person(){};
var p1 =new Person();//继承自原来的原型,但是现在已经无法访问
Person.prototype=o;
var p2 =new Person();//继承自o
console.log(o.isPrototypeOf(p1));//false o是不是p1的原型
console.log(o.isPrototypeof(p2));//true o是不是p2的原型
3.组合式继承
同时使用原型继承和借用构造函数继承两种方式。在子类的构造函数内使用call或apply调用父类,并且子类的prototype指向某一个父类的实例。
1.优点
结合了原型继承和借用构造函数继承两种方式的优点,可以传参,可以多继承,可以使用父类构造函数的方法和属性,也可以使用父类原型对象上的方法和属性。同时也可以被instanceof和isPropertyOf识别2.缺点
1父类被调用了两次,第一次是在使用call或apply的时候,第二次是在将子类的原型对象指向父类的实例的时候。(效率降低)
2父类构造函数内所设置的属性或方法在子类的实例和原型对象中(上图中的红色部分)个存在了一份,由于同名覆盖的原因,使用时并不受影响。但是会占用额外的内存空间。(浪费空间)
4.原型式继承(记住上面三个)
创建一个构造函数B
B.prototype = a;
创建b实例对象时
b = new B 这样b对象就继承了a
直接让一个对象继承于另一个对象
5.寄生式继承???
将原型式继承用函数封装起来
6.组合寄生式继承(完美继承)
先通过原型链继承创建一个对象,比如说这个对象叫object,object继承于a,让object的constructor指向b,然后让b的prototype指向刚刚创建的对象object
这些继承方式的优缺点和应用场景
- 4.原型式继承,寄生式继承
不是实现两个构造函数的继承而是实现两个对象的继承, - 5.组合寄生式继承(完美继承)
很完美就是写起来麻烦
6.1 什么是继承
继承是指一个对象(子类)直接使用另一对象(父类)的属性和方法。在子类中也可以重写父类的方法,覆盖父类中同名的方法。继承的优点是提高了代码的效率,避免了代码重写。
6.2 谈一谈你对原型链的理解
我认为原型链是一种关系,一种实例对象和原型对象之间的关系,这个关系是通过原型(proto)来联系的。
6.3 谈谈构造函数,原型对象和实例对象之间的关系
+1.构造函数的原型属性prototype指向了(该构造函数的)原型对象,在原型对象里有共有的方法,所有构造函数声明的实例都可以共享这个方法。
- 2.原型对象Foo.prototype
Foo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。 - 3.实例
假设f1和f2是Foo这个对象的两个实例,这两个对象也有属性proto,指向构造函数的原型对象,可以访问原型对象的所有方法。 - 4.构造函数可以实例化对象
- 5.构造函数Foo也是对象,它的proto属性指向Function.prototype
- 6.Function.prototype也是对象,他的proto属性指向Object.prototype,最后Object.prototype的proto属性指向null
总结:由对象实例起步,由proto属性不断向上指向对应构造函数的原型对象,直至Object.prototype的proto属性指向null,形成原型链
7.script标签上的async和defer属性的区别
- 1.defer是延迟执行,它会同时并行地去加载多个js,然后按照顺序从上到下执行
- 2.async是异步加载,它和defer一样会并行地去加载多个js,但是它是下载完之后就立刻执行,并不是按书写顺序去执行
面试怎么答 - 1.在js中javascript代码的加载和执行会阻塞页面的html渲染
- 2.正常的js文件的下载都是同步的依次去下载,但是写了async和defer,都会以
异步的方式去加载外部的js文件 - 3.async是在外部js加载完之后,如果浏览器空闲,并且load事件触发的时候,会在load事件触发之前去执行
- 4.defer是在js加载完成之后,等到整个文档解析完成之后,才会去执行。他们俩个的执行时间是不一样的。如果对一个script标签使用了defer属性,即使把script标签放在head里面,执行的效果也类似于你把它放在body后面,但是由于他是异步加载的,可能会节省一些时间,但是他执行的时间是没办法确定的。具体的用法要根据项目的需求
8.promise
8.1 promise实现的方式
创建的时候,new Promise(resolve,reject),成功和失败的回调,
使用的时候调用then和catch
8.2 如果页面上有多个promise,如何等到所有promise执行完成之后再执行后面的内容
在promise中有一个特殊的方法all,还有一个race,他们有什么区别
使用方式上都一样,区别是
1.all会把数组当中所有promise执行完之后,才会把所有的决议结果以数组的方式传入到回调里面(是按顺序执行的)
2.而race,会把数组当中第一个产生的决议值传给回调
Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那一个结果,不管结果本身是成功状态还是失败状态。
8.3 promise中的then和catch的返回值是什么
返回新的promise对象,所以可做链式操作
8.4 如果在promise的then中显式的返回一个非promise对象,这个时候如何执行
返回值还是一个promise对象,但是后面的then会立即执行不会等前面的
8.5 在promise当中异常捕获是如何实现的,这种异常捕获和try/catch有什么优势
- 对于try/catch来讲,尝试后面的代码能否运行,一种异常的捕获机制。
先调用try里面的方法,如果里面报错了,再把错误传到catch里面进行处理。
不能捕获异步下的异常,比如要发送一个ajax请求,只能在ajax的回调里使用,或者整体包裹一个try/catch
- 对于promise来讲,它里面存在两种状态,resolved和rejected,只要是发生了异常或者错误,promise会进入rejected决议,进入catch里面进行处理。
9.es6新增了哪些东西,你在项目中用了哪些
9.1 import、export模块化
export var a = 'jspang';//temp.js
//export default var a='jspang';
//import str from './temp';default可以在导入的时候自定义变量名
import {a} from './temp.js';//index.js
console.log(a);
9.2 解构赋值
数组,字符串,对象也可
letl [a,b,c]=[1,2,3];
9.3 let,const
9.4 class和extends
class Coder{
name(val){
console.log(val);
return val;
}
constructor(a,b){
this.a=a;
this.b=b;
}
add(){
return this.a+this.b;
}
}
let jspang=new Coder(1,2);
console.log(jspang.add());
class htmler extends Coder{//类的继承
}
let pang=new htmler;
pang.name('技术胖');
9.5 内置函数的拓展
1.函数的拓展
箭头函数,传参的默认值,rest参数(...)
箭头函数和普通函数区别
- 箭头函数没有prototype,所以箭头函数本身没有this
- 箭头函数的this指向在定义的时候继承自外层第一个普通函数的this
- 箭头函数没有arguments,普通函数有
- 使用new调用箭头函数会报错
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
yield是ES6的新关键字,使生成器函数执行暂停
2.字符串的拓展
模板字符串
3.数组的拓展
from of数组的遍历,拓展运算符...
4.对象的拓展
- 1.属性的简洁表达式(ES6中允许使用变量来作为属性和方法,书写更简便)
对于函数返回值返回对象非常的方便 - 2.解构赋值
新增变量或者import一些变量 - 3.is, keys,values,assign,entries
https://www.cnblogs.com/marvintang1001/p/11809665.html
is:严格相等 Object.is(+0, -0) // false
keys:返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
values:返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。第一个参数是目标对象,后面的参数都是源对象。注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
entries:方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。只输出属性名非 Symbol 值的属性。
5.class 类
6.promise对象
6.1 promise原理
https://juejin.im/post/5a0965c9f265da430e4ea8a0#heading-2
promise对象是一个构造函数,通过new关键字来生成实例,promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,他们是JavaScript引擎提供的两个函数。
resolve函数在异步操作由pending状态(执行进行中)变为resolved(成功状态)时触发,传递操作成功后的结果;
reject函数在异步操作从pending状态(执行进行中)变为rejected(失败状态)时触发,传递操作失败后的结果。promsie状态一旦修改就不能再变。
Promise.prototype上有一个then方法,处理状态改变的代码写在then()方法里,它的作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
promise采用链式调用,then()为 Promise 注册回调函数,参数为上一个任务的返回结果,所以链式调用里then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收。
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,也就是异步操作发生错误时的回调函数,另外,then()方法里的回调函数发生错误也会被catch()捕获。
Promise.prototype.finally()
finally()方法是在ES2018引入标准的,该方法表示promise无论什么状态,在执行完then()或者catch()方法后,最后都会执行finally()方法。
6.2 基本用法
const promise = new Promise((resolve, reject) => {
// 异步操作的代码
if (success){
resolve(value);
} else {
reject(error);
}
});
6.3 简单实现一个promise
function myPromise(executor) {
let self=this;
self.status='pending';
self.value=undefined;
self.error=undefined;
function resolve(value) {
if(self.status==='pending'){
self.value=value
self.status="resolved"
}
}
function reject(reason) {
if(self.status==='pending'){
self.error=errot
self.status=status
}
}
try{
executor(resolve,reject)
}
catch (e) {
reject(e)
}
}
6.4 promise解决异步的思路
- 1.解决异步回调嵌套的问题,比如说有多个ajax,他们之前的请求存在依赖关系,也就是说,一个请求必须使用另一个请求返回的结果才能做这次请求,那么就需要将这个请求嵌套在另一个请求的回调函数里面才能获取到另一个请求的返回结果。这样的话如果有多个请求之间有相互依赖的关系,那就说他需要在多个ajax的回调函数里面嵌套ajax操作,这样就形成了回调地狱。这在团队合作中,这样的项目对于后期的维护十分不利。
- 2.思路:将异步请求封装成一个对象,将执行成功与失败分别作为这个对象的方法,执行成功的时候将结果放在这个对象的then方法中处理后续的逻辑,失败了就调用这个对象的catch方法。这样调用方法的执行方式就能避免回调函数的嵌套。如果有多个依赖的异步操作,promise还可以将多个对象的then方法变成链式操作,从而避免回调函数的嵌套。
- 3.promise的使用场景,主要是用于保证多个异步请求都完成之后在进行后续的业务
9.6 在项目当中没法直接使用es6,需要用babel进行转换为es5
要知道babel是如何转换的,有能力自己将es6转换为es5.尤其是let,const的赋值,
箭头函数,解构和拓展运算符。
9.7 箭头函数和以前普通的函数有什么区别
- 1.箭头函数没有arguments对象
- 2.this指向也发生了转变
10.在js中事件是如何实现的
- 1.直接嵌入dom
在标签里直接加上click跟上函数 - 2.直接绑定
先获取到一个节点,然后节点.click= function,方便管理 - 3.事件监听
先获取到一个节点,addEventListener(解绑removeEventListener),监听事件,并向指定元素添加该事件
问题:浏览器兼容性的处理,对于ie来讲绑定事件用的是attachEvent,解绑用detachEvent
10.1 事件委托
10.2 事件流
完整的事件流是从window开始然后再回到window这么一个过程
分成三个阶段:1.捕获阶段 2.目标阶段 3.冒泡阶段
偏的问题:
- 1.对于同一个dom元素,它既在三个阶段都注册了事件,当这个事件被触发的时候,整体的事件执行顺序是怎么样的。
正常情况下事件是按照事件流的顺序执行。但是当事件处于目标阶段,事件的执行顺序决定于绑定事件的书写顺序。 - 2.stopPrepagation和preventDefault有什么区别
一个是阻止默认行为,一个阻止事件冒泡。在ie下要处理兼容问题,要阻止默认行为需要event.returnValue设置为 false,阻止事件冒泡,event.cancelBubble设置为true
10.3 target 和currentTarget的区别
target是事件的真正目标
currentTarget是事件处理程序注册的元素
11.网络请求
11.1 http状态码
五类:
- 1开头,信息响应表示接收到了请求可以进行后续的处理
- 2开头,请求成功
- 3开头,重定向响应,完成指定的响应
- 4开头,客户端错误
- 5开头,服务端错误
常见状态码:301,303,400,500,502,503,504
11.2 输入url敲回车,整个过程当中发生了什么
- 1.输入地址
- 2.浏览器查ip地址(dns解析,浏览器的系统缓存,路由缓存)
- 3.解析完成之后,浏览器向服务器发送一个http请求
- 4.发出去之后,服务器会进行一个重定向响应,比如你输入一个地址jd.com,浏览器会把你重定向到www.jd.com, 接下去浏览器会跟踪这个重定向响应,然后取得这个地址,服务器接收到这个请求以后,会对请求进一步处理,然后服务器返回一个http的响应,响应中包含了html的代码
- 5.浏览器接收了html代码以后,里面包含了很多东西,比如说图片,音频,js,css。
浏览器会默认按照它的一个渲染方式把整个页面渲染出来,这里面涉及了异步请求的发送,js的解析和执行
11.3 跨域
- 1.jsonp的跨域(JSON with padding(填充式JSON))
通过script标签实现,
问题:1.只能get请求 2.存在安全隐患 - 2.cors 需要服务端进行处理,如果前端需要带cookie的话前后端都要处理
如果使用的是原生js的话,需要把xhr的withCredentials设置为true,如果用的是jq,要把jquey里面的xhrfileds的withCredentials设置为true - 3.服务端的nginx反向代理
使用:配置nginx的时候配置一个代理服务器,然后把它做成跳板,比如说我要访问domain2,但是前端只能访问domain1,nginx做一个跳板,每次访问domain1的时候就自动指向domain2这个接口。
原理是什么?同源策略是浏览器的安全策略而不是http协议的一部分,如果在服务器端调用接口,因为不存在浏览器所以他走的只是http协议,所以不存在跨域的问题。
12.防抖节流
- 1.为什么要有函数节流和函数防抖
解决高频触发函数而带来的负荷问题 - 2.什么是..
函数节流就是当函数被高频触发时,每间隔固定时间会触发一次。简单理解为缩减函数执行频率,主要目的是稀释函数执行次数
函数防抖就是函数在固定的时间内被触发时,不会立即执行,而是每一次触发都会重新计算时间。在达到固定时间后,仅仅执行一次 - 3.从代码层面说一下什么是...
函数节流,通过闭包的方式标记一个变量为true,在闭包内返回的函数里进行判断,如果标记为true,那么就执行,并且把标记改为false,如果标记为false就直接return。在闭包返回的函数里面放一个settimeout,在settimeout里面执行我们要执行的函数,并且把标记变为true。
const throttle = (fn, delay = 500) =>
{ let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
},delay);
};
};
函数防抖,准备一个变量来接受定时器的返回值,把我们要执行的函数放在一个settimeout里面,如果在固定时间内再次触发了函数,那么就把之前定时器关闭,再执行现在这个定时器。这样一来,时间记录会随着函数的执行重新进行计算,最后只会执行一次
const debounce = (fn, delay) =>
{
let timer = null;
return (...args) =>
{ clearTimeout(timer);
timer = setTimeout(() =>
{ fn.apply(this, args); }, delay);
}; };
4.举例
1.滚动条滚动到一定程度的时候会出现回到顶部按钮,使用函数防抖,不需要随着滚动每次都去判断浏览器向上卷曲的高度,而是在滚动过程中不进行判断,当滚动停止的时候才进行一次判断,决定顶部按钮是否出现
2.轮播图左右切换按钮,不停的快速点击会造成鬼畜效果,可以使用函数节流,在一个图片效果的滚动时间内只触发一次滚动,只有当这张图片滚动完毕以后,再次点击切换按钮的时候,才能触发下一次图片的滚动
5.应用场景
函数防抖--延时执行
两个条件:
1,如果客户连续的操作会导致频繁的事件回调(可能引起页面卡顿).
2,客户只关心"最后一次"操作(也可以理解为停止连续操作后)所返回的结果.
例如:
输入搜索联想,用户在不断输入值时,用防抖来节约请求资源。
按钮点击:收藏,点赞,心标等
函数节流:
防抖是将多次执行变为最后一次执行,节流是将多次执行变为在规定时间内只执行一次.一般不会重置定时器.
两个条件:
1,用户连续频繁地触发事件
2,用户不再只关心"最后一次"操作后的结果反馈.而是在操作过程中持续的反馈.
例如:
鼠标不断点击触发,点击事件在规定时间内只触发一次(单位时间内只触发一次)
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
13.深浅拷贝
浅拷贝:
concat()
Object.assign()
-
slice()
var arr1 = [1,2,3,4]; var arr2 = [].concat(arr1) var arr3 = arr1.slice(0) var arr4 = [] arr4 = Object.assign(arr4,arr1))
拷贝之后数组各个值的指针还是指向相同的存储地址
手写
function shallowCopy(obj){
if(typeof obj!=='function'&& obj!==null){
let cloneObj=Array.isArray(obj)?[]:{}
for(let prop in obj){
cloneObj[prop]=obj[prop]
}
return cloneObj
}
else{
return obj
}
}
深拷贝:
JSON.stringfy(JSON.parse(obj))
1.时间对象=>字符串的形式
2.RegExp、Error => {}
3.function,undefined 丢失
4.NaN、Infinity和-Infinity会变成null
5.如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor
6.循环引用的情况也无法实现深拷贝
手写深拷贝
基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
map数据类型:它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
const m = new Map()
m.set('name', 'lily')m.get('name') // "lily"
m.has('name') // true
m.delete('name')//true
var deepClone=(obj,map=new WeakMap())=>{
if(map.get(obj)){
return obj
}
let newObj;
if(typeof obj==='object'&& obj!==null){
newObj=Array.isArray(obj)?[]:{};
for(let prop in obj){
if(obj.hasOwnProperty(prop)){
newObj[prop]=deepClone(obj[prop])
}
}
return newObj;
}
else {
return obj;
}
};
14.this
this绑定函数的执行上下文,谁调用它,它就指向谁。
分为默认绑定、显式绑定、隐式绑定、apply/call/bind绑定、new绑定和箭头函数绑定
默认绑定:严格模式下this指向undefined,非严格模式this指向window
14.1 call、apply、bind
call、apply、bind都可以改变this的指向,但是apply接收参数数组,call接收的是参数列表 bind接收的是参数列表,但是apply和call调用就执行,bind需要手动执行
箭头函数绑定:箭头函数的this是父作用域的this,不是调用时的this,其他方法的this是动态的,而箭头函数的this是静态的
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向person 对象
}, 1000);
}
-------------------------
function Person() {
var self = this;
this.age = 0;
setInterval(function growUp() {
// 回调引用的是`that`变量, 其值是预期的对象.
self.age++;
}, 1000);
}
优先级:箭头函数>new绑定>显示绑定/apply/bind/call>隐式绑定>默认绑定
14.2 箭头函数和普通函数区别
- 箭头函数没有prototype,所以箭头函数本身没有this
- 箭头函数的this指向在定义的时候继承自外层第一个普通函数的this
- 箭头函数没有arguments,普通函数有
- 使用new调用箭头函数会报错
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
15.浏览器事件循环
- 1.同步任务在主线程执行,在主线程外还有个任务队列用于存放异步任务
- 2.主线程的同步任务执行完毕,异步任务入栈,进入主线程执行
- 3.上述的两个步骤循环,形成eventloop事件循环 浏览器的事件循环又跟宏任务和微任务有关,两者都属于异步任务。
js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入任务队列,再执行微任务,将微任务放入任务队列,他俩进入的不是同一个任务队列。往外读取的时候先从微任务里拿这个回调函数,然后再从宏任务的任务队列上拿宏任务的回调函数
宏任务:
script
定时器 setTimeout setInterval setImmediate
微任务:
promise
process.nextTick()
MutationObserver
16.document.ready和window.onload区别
document.ready是dom树加载后执行,而window.onload是整个页面资源加载完后执行,所以document.ready比window.onload先执行
17.什么是闭包,应用场景是什么?
- 1.可以保留局部变量不被释放的代码块被称作闭包
- 2.怎么形成:1.在外层函数中返回一个内层函数 2.在内存函数中能访问到外层函数中的变量 3.一定要定义一个变量接受内存函数
- 3.使用 :定义一些有作用域局限的持久化变量,这些变量可以作为缓存或者计算的中间量
- 4.弊端:持久化变量不会被正常释放,会持续占用内存空间,很容易造成内存的浪费,一般需要额外手动去清理。
- 5.应用场景:封装变量,实现对数据的保护。
实现设计模式:单例模式,策略模式,观察者模式等
实现封装独立的作用域:轮播图的小圆点添加点击事件,解决循环变量i不正确的问题,防抖节流函数
18.localStorage、sessionStorage、cookie、session几种web数据存储方式对比
18.1 cookie 和 session
1.相同点
cookie 和 session 都是普遍用来跟踪浏览用户身份的会话方式。
2.区别
1.存放位置:cookie 数据存放在客户端,session 数据放在服务器端,session 主要是服务端用来处理数据的。
2.安全性:cookie 本身并不安全,考虑到安全应当使用 session。
3.服务器性能:session 会在一定时间内保存在服务器上。如果访问量比较大,会比较消耗服务器的性能。考虑到减轻服务器性能方面的开销,应当使用 cookie 。
4.数据大小限制:单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个域名最多保存 50 个 cookie。 将登陆信息等重要信息存放为 session、其他信息如果需要保留,可以放在 cookie 中。
3.cookie 的使用
cookie 可通过 document.cookie
获取全部 cookie。它是一段字符串,是键值对的形式。操作起来有些麻烦,可引入封装好的库进行使用,比如 js-cookie
Cookies.set("name", "value", { expires: 7 }); // 设置一个cookie,7天后失效
Cookies.get("name"); // => 'value'
Cookies.remove("name");
18.2 localStorage 和 sessionStorage
在 web 本地存储场景上,cookie 的使用受到种种限制,最关键的就是存储容量太小和数据无法持久化存储。
在 HTML 5 的标准下,出现了 localStorage 和 sessionStorage 供我们使用。
-
1.cookie、localStorage 以及 sessionStorage 的异同点
分类 生命周期 存储容量 存储位置 cookie 默认保存在内存中,随浏览器关闭失效(如果设置过期时间,在到过期时间后失效) 4KB 保存在客户端,每次请求时都会带上 localStorage 理论上永久有效的,除非主动清除。 4.98MB(不同浏览器情况不同,safari 2.49M) 保存在客户端,不与服务端交互。节省网络流量 sessionStorage 仅在当前网页会话下有效,关闭页面或浏览器后会被清除 4.98MB(部分浏览器没有限制) 同上 应用场景:localStorage 适合持久化缓存数据,比如页面的默认偏好配置等;sessionStorage 适合一次性临时数据保存。
-
使用方法
localStorage.setItem("name", "value"); localStorage.getItem("name"); // => 'value' localStorage.removeItem("name"); localStorage.clear(); // 删除所有数据 sessionStorage.setItem("name", "value"); sessionStorage.setItem("name"); sessionStorage.setItem("name"); sessionStorage.clear();
19.js中数组对象自定义排序
var data = [{ name: "zachary", age: 28 }, { name: "nicholas", age: 29 }];
function fun(name) {
return function (o1, o2) {
var value1 = o1[name];
var value2 = o2[name];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
data.sort(fun("age"));
console.log(data)
定义一个函数,参数为排序的属性名,这个函数返回一个比较函数,参数是两个对象,在比较函数中对两个对象的某一个属性值进行比较。使用函数的时候,把它放在sort方法里。