JavaScript
-
js中有几种数据类型,分别是什么?
- number,string,boolean,null,undefined,object
-
call()
、apply()
、bind()
的异同点。- 相同点:
- 三者的相同点:都是用来改变this的指向
- call() 和 apply() 的相同点:都是调用一个对象的一个方法,用另一个对象替换当前对象(功能相同)
- 例如:
- B.call(A, args1,args2);即A对象调用B对象的方法
- F.apply(G, arguments);即G对象应用F对象的方法
- 例如:
- call()和 bind() 的相同点:都是用来改变this的指向
- 不同点:
- call() 和 apply() 的不同点:参数书写方式不同
- call() 的第一个参数是 this 要指向的对象,后面传入的是参数列表,参数可以是任意类型,当第一个参数为 null、undefined 的时候,默认指向 window
- apply() 的第一个参数是 this 要指向的对象,第二个参数是数组
- call()和 bind() 的不同点:
- call() 改过 this 的指向后,会再执行函数
- bind()改过 this 后,不执行函数,会返回一个绑定新 this 的函数
- call() 和 apply() 的不同点:参数书写方式不同
- 相同点:
-
如何准确判断变量是什么类型?
- type of 判断
- typeof 检测基本类型是没有问题的,如字符串、数值、布尔和 undefined,但是如果检测对象或null的话都会返回'object';
- instanceof 判断
- 此方法后面一定是数据类型:console.log(data instanceof Array),通常我们并不是想知道某个值是对象,而是想知道它是什么类型的对象,如上:变量 data 是 Array 类型的对象?
- 所有引用类型值都是 object 的实例,所以在检测一个引用类型值和 Object 构造函数时,instanceof 操作符会一直返回 true。如用 instanceof 操作符检测基本类型值时,会一直返回false,原因很简单,因为基本类型不是对象
- constructor 判断
- console.log(data.constructor === Array)
- Object.prototype.toString.call(data)
- 此种方式可以准确的判断出数据类型,console.log(Object.prototype.toString.call(data))
- type of 判断
-
请解释一下事件冒泡机制,并说明如何阻止冒泡。
- 事件冒泡机制
- 在事件开始时,由最具体的元素(文档中最底层的那个节点)接受,然后逐级向上传播到最不具体的节点(文档)
- 比如说,在一个页面有一个div元素,加上一个点击事件,在点击事件触发的时候,它的传递过程就是由 div→body→html→document(老版本暂且不谈),它会沿着 DOM 树逐级的传递
- 阻止事件冒泡
- event.stopPropagation() // 不支持IE低版本
- cancelBubble = true
- 事件冒泡机制
-
new
一个对象的过程是什么?- 创建空对象
- var obj = {}
- 设置新对象的constructor属性为构造函数的名称,设置新对象的proto属性指向构造函数的prototype对象
- obj.proto = ClassA.prototype
- 使用新对象调用函数,函数中的this被指向新实例对象:
- ClassA.call(obj); //{}.构造函数()
- 将初始化完毕的新对象地址,保存到等号左边的变量中
- 注意:
- 若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象
- 若返回值是引用类型的值,则实际返回值为这个引用类型
- 注意:
- 创建空对象
-
this 的指向有哪几种情况,分别是什么?
- 作为函数调用,非严格模式下,this指向window,严格模式下,this指向undefined;
- 作为某对象的方法调用,this通常指向调用的对象。
- 使用apply、call、bind 可以绑定this的指向。
- 在构造函数中,this指向新创建的对象
- 箭头函数没有单独的this值,this在箭头函数创建时确定,它与声明所在的上下文相同
-
谈谈对作用域和作用域链的理解。
- 变量的作用域有两种:全局变量和局部变量
- 全局作用域:
- 最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的
- 局部作用域:
- 和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部
- 注意:
- 函数内部声明变量的时候,一定要使用var命令。如果不用的话,实际上声明了一个全局变量
- 全局作用域:
- 作用域链:
- 全局作用域和局部作用域中变量的访问权限,其实是由作用域链决定的
- 执行环境决定了变量的生命周期,以及哪部分代码可以访问其中变量
- 执行环境有全局执行环境(全局环境)和局部执行环境之分
- 每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链
- 函数的局部环境可以访问函数作用域中的变量和函数,也可以访问其父环境,乃至全局环境中的变量和环境
- 全局环境只能访问全局环境中定义的变量和函数,不能直接访问局部环境中的任何数据
- 变量的执行环境有助于确定应该合适释放内存
- 变量的作用域有两种:全局变量和局部变量
-
谈谈对原型和原型链的理解。
- 原型
- 原型就是 prototype 对象,用来表示类型之间的关系
- 原型的作用:
- 数据共享 节约内存内存空间
- 实现继承
- 注意:函数也是一个对象,对象不一定是函数(对象有proto属性,函数有prototype属性)
- 原型链
- 对象和对象之间是有联系的,通过prototype对象指向父类对象,在指向Object对象。而通过的方式就是proto连接,而proto最终指向null
- 图例:
- 原型
-
列举 js 中继承的几种方式。
- 原型链继承
- 核心: 将父类的实例作为子类的原型
- 构造继承
- 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
- 实例继承
- 核心:为父类实例添加新特性,作为子类实例返回
- 拷贝继承
- 组合继承
- 核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
- 寄生组合继承
- 核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
- 原型链继承
-
谈谈对闭包的理解,并说明其优缺点。
- 使用闭包主要是为了设计私有的方法和变量
- 在js中,函数即闭包,只有函数才会产生作用域的概念
- 闭包有三个特性:
- 函数嵌套函数
- 函数内部可以引用外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
- 优点:
- 可以避免全局变量的污染
- 缺点:
- 闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露
-
谈谈 js 中的异步(事件循环[Event Loop]机制)。
- 浏览器中的 Event Loop
- Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行
- JS调用栈
- JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空
- 同步任务和异步任务
- Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行
- [图片上传失败...(image-5b483b-1585842119691)]
- 任务队列 Task Queue,即队列,是一种先进先出的一种数据结构
- 浏览器中的 Event Loop
-
列举 js 中数组的常用方法并说明用途。
- push() (末尾添加元素)
- 作用:向数组的末尾添加n个元素
- 参数:参数时新增的元素,可以串多个
- 返回值:新数组的数组成员的个数
- 原数组发生变化
- pop() (删除最后一项)
- 作用:删除数组的最后一项
- 参数:不需要传参数
- 返回值:被删除的那一项
- 原数组发生改变
- unshift() (开始位置添加一项)
- 作用:向数组的开始位置添加n个元素
- 参数:参数是新增的元素,可以是多个
- 返回值:新数组的长度
- 原数组发生改变
- shift() (开始位置删除一项)
- 作用:删除数组的第一项
- 参数:不需要传参数
- 返回值:被删除的那一项
- 原数组发生改变
- splice() (删除、添加、修改元素)
- 作用:删除元素,并向数组添加新元素
- 参数:
- splice(m,n);从索引m开始,删除n项
- splice(m);从索引m开始删除到末尾
- splice(m,n,x,…);从索引m开始删除n项,并将x添加到原来位置;添加项可以是多个(如果删除项为0个,那么添加是在m元素前面)
- 返回值:是个数组,数组中是被删除的项
- 原数组发生变化
- indexOf()(从左向右查询)不兼容IE低版本浏览器:IE6,7,8
- 作用:检测数组中是否存在某个元素
- 参数:被查询的元素(m,n)从索引n开始,m第一次出现的索引位置
- 返回值:
- 返回数组中第一次匹配到的元素的索引
- 如果数组中没有匹配项返回-1
- 原数组不发生变化
- lastIndexOf()(从左到右查询)不兼容IE低版本浏览器:IE6,7,8
- 作用:检测数组中是否存在某个元素
- 参数:被检测的元素
- 返回值:
- 返回数组中最后一次匹配到的元素的索引
- 如果数组中没有匹配项返回-1
- 原数组不发生变化
- slice()(截取)
- 作用:按照起始和结束位置的索引截取数组
- 参数:
- 有两个参数slice(m,n):从索引m开始,截取到索引n;(包前不包后)
- 有一个参数slice(m):从索引m开始截取到末尾;
- 没有参数:数组的克隆;(slice(0)也是数组的克隆);
- 以上情况参数都支持负数,负数情况会默认被加上数组的长度,处理成正数
- 返回值:截取的数组
- 原数组不发生变化
- sort()(排序)
- 作用:对数组的元素进行排序
- 参数:
- 没有参数sort():只能排序数组成员项是相同位数的数字
- sort(function(a,b){return a-b}):从小到大排序
- sort(function(a,b){return b-a}):从大到小排序
- 返回值:排序之后的数组
- 原数组发生变化
- reverse()(倒序)
- 作用:使数组中元素倒序
- 参数:不需要参数
- 返回值:成员倒序的数组
- 原数组发生变化
- concat()(拼接数组)
- 作用:拼接两个或多个数组
- 参数:
- 不传参数:数组的克隆
- 传参数:将传入的参数拼接到数组中、可以传多个
- 返回值:拼接之后的新数组
- 原数组不发生变化
- join()(数组拼接成字符串)
- 作用:将数组的成员项通过制定字符拼接成字符串
- 参数:
- 不传参数:会默认按照逗号拼接
- 传参数:会按照参数字符拼接
- 返回值:拼接之后的字符串
- 原数组不发生变化
- map()(映射)
- 作用:方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值
- 参数:参数是一个回调函数;数组有几项,回调函数就执行多少次;在回调函数中处理数组中的每项
- 返回值:映射的新数组;
- 原数组不发生变化;
- forEach()(遍历数组)
- 作用:遍历数组;
- 参数:参数是一个回调函数;数组有几项,回调函数就执行多少次
- 返回值:没有返回值;undefined
- 原数组不发生变化
- ary.forEach()和ary.map()的区别
- ary.forEach()是和for循环一样,是代替for。ary.map()是修改数组其中的数据,并返回新的数据
- ary.forEach() 没有return ary.map() 有return
- map有映射,forEach没有映射。
- toString()(数组转字符串)
- 作用:数组转字符串
- 参数:不需要参数
- 返回值:一个字符串,由数组的每项组成,数组的每一项用逗号隔开
- 原数组不发生变化;
- find(查找)
- 从左到右依次进行查找,找到符合条件的那一项,直接返回,不再进行查找;如果找不到,那么返回undefined; 返回true,说明就找到了
- find会根据回调函数的返回值,判断是否要继续向右查找;
- filter(过滤)
- 过滤;原数组不发生改变;返回一个过滤后的新数组
- every
- 每一个都是true则返回true,如果有一个是false,那么直接返回false;只要找到false,直接结束,不再继续向下查找;返回值是布尔值
- some
- 返回一个布尔值;只要有一个符合条件就返回true;找到true,就不再向右进行查找;
- includes
- 返回一个布尔值;如果有就返回true;,没有就返回false;
- reduce
- 迭代数组的所有项,累加器,数组中的每个值(从左到右)合并,最终计算为一个值
- reduce回调函数后面可以传一个参数
- push() (末尾添加元素)
-
js 如何实现深拷贝与浅拷贝
- 浅拷贝和深拷贝都只针对于引用数据类型
- 浅拷贝
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
- 深拷贝
- 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
- 区别:
- 浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制
-
谈一谈对面向对象的认识。
- 面向对象是向现实世界模型的自然延伸,这是一种"万物皆对象"的编程思想
- 面向对象有三大特性,封装、继承和多态
- 封装
- 封装是为了使得代码的复用性更高
- 封装是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化
- 继承
- 继承是为了扩展了已存在的代码块,进一步提高了代码的复用性
- 继承是进一步将一类事物共有的属性和行为抽象成一个父类,而每一个子类是一个特殊的父类--有父类的行为和属性,也有自己特有的行为和属性
- 多态
- 多态是为了实现接口重用
- 多态的一大作用就是为了解耦--为了解除父子类继承的耦合度。如果说继承中父子类的关系式IS-A的关系,那么接口和实现类之之间的关系式HAS-A。简单来说,多态就是允许父类引用(或接口)指向子类(或实现类)对象。很多的设计模式都是基于面向对象的多态性设计的
- 封装
- 如果说封装和继承是面向对象的基础,那么多态则是面向对象最精髓的理论。掌握多态必先了解接口,只有充分理解接口才能更好的应用多态
-
js 的高阶函数有哪些?说明其用处。
- 高阶函数
- 一个函数接受一个或多个函数作为参数,或者可以返回一个参数
- 常见的高阶函数
- setInterval , setTimeout , sort ,一般用于函数回调
- map:应用于数组,对数组进行操作
- reduce 归纳的意思,作用是对数组的每个值求和积等操作
- filter 数组过滤
- 高阶函数
-
什么是防抖和节流?有什么区别?如何实现?
- 防抖
- 触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
- 思路:
- 每次触发事件时都取消之前的延时调用方法
- 代码:
function debounce(fn) { let timeout = null; // 创建一个标记用来存放定时器的返回值 return function () { clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉 timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数 fn.apply(this, arguments); }, 500); }; } function sayHi() { console.log('防抖成功'); } var inp = document.getElementById('inp'); inp.addEventListener('input', debounce(sayHi)); // 防抖
- 节流
- 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
- 思路:
- 每次触发事件时都判断当前是否有等待执行的延时函数
- 代码:
function throttle(fn) { let canRun = true; // 通过闭包保存一个标记 return function () { if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return canRun = false; // 立即设置为 false setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中 fn.apply(this, arguments); // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉 canRun = true; }, 500); }; } function sayHi(e) { console.log(e.target.innerWidth, e.target.innerHeight); } window.addEventListener('resize', throttle(sayHi));
- 防抖
-
编写一段代码:实现数组去重
- Set
var newArr = Array.from(new Set(arr));//不改变原数组
- 扩展运算符 […]
var resultarr = [...new Set(arr)]; //不改变原数组
- filter
let newArr = arr.filter((item,key,self) => self.indexOf(item) == key);
- Set
-
编写一段代码:实现数组扁平化
- 数组扁平化
- 是指将一个多维数组变为一维数组
[1, [2, 3, [4, 5]]] ------> [1, 2, 3, 4, 5]
- 思路:
- 遍历数组 arr,若 arr[i] 为数组则递归遍历,直至 arr[i] 不为数组然后与之前的结果 concat
- concat() 方法用于连接两个或多个数组
- 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本
- 遍历数组 arr,若 arr[i] 为数组则递归遍历,直至 arr[i] 不为数组然后与之前的结果 concat
- 是指将一个多维数组变为一维数组
- 实现方式
- reduce
- 遍历数组每一项,若值为数组则递归遍历,否则concat
function flatten(arr) { return arr.reduce((result, item)=> { return result.concat(Array.isArray(item) ? flatten(item) : item); }, []); }
- reduce 是数组的一种方法,它接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
- reduce包含两个参数:回调函数,传给total的初始值
- toString & split
- 调用数组的 toString 方法,将数组变为字符串然后再用 split 分割还原为数组
- 因为split分割后形成的数组的每一项值为字符串,所以需要用一个map方法遍历数组将其每一项转换为数值型
function flatten(arr) { return arr.toString().split(',').map(function(item) { return Number(item); }) }
- join & split
- 和上面的toString一样,join也可以将数组转换为字符串
function flatten(arr) { return arr.join(',').split(',').map(function(item) { return parseInt(item); }) }
- 递归
- 递归的遍历每一项,若为数组则继续遍历,否则concat
function flatten(arr) { var res = []; arr.map(item => { if(Array.isArray(item)) { res = res.concat(flatten(item)); } else { res.push(item); } }); return res; }
- 扩展运算符
- es6的扩展运算符能将二维数组变为一维
- 可以做一个遍历,若arr中含有数组则使用一次扩展运算符,直至没有为止
function flatten(arr) { while(arr.some(item=>Array.isArray(item))) { arr = [].concat(...arr); } return arr; }
- reduce
- 数组扁平化