1.闭包:
- 概念:JS中内层函数可以访问外层函数的变量,外层函数无法操作内部函数的变量的特性。
- 优点:
1.可以通过闭包访问函数作用域内的局部变量
2.使函数中的变量被保存在内存中不被释放 - 缺点:
1.由于闭包会使得函数中的变量都被保存在内存中,内存消耗大
2.闭包会在父函数外部,改变父函数内部变量的值 - 内存泄漏解决方法:
无法自动销户,就及时手动回收,使用后将函数的引用置为null
var tmp = 100
function foo(x){
var tmp = 3;
return function(y){
console.log(x + y + ++tmp);
};
}
var fee = foo(2);
fee(10); //16
fee(10); //17
fee(10); //18
function createInc(startValue){
return function(step){
startValue += step;
return startValue;
}
}
var inc = createInc(5);
console.log(inc(1));//6
console.log(inc(2));//8
var inc2 = createInc(5);
console.log(inc2(2));//7
console.log(inc(1));//9
inc = createInc(5);
console.log(inc(1));//6
2.深浅拷贝
- 浅拷贝:浅拷贝就是直接复制,相当于把一个对象中的所有内容,复制一份给另一个对象,对于基本类型复制的是具体的值的副本,对于引用类型复制的是指针。
- 深拷贝:深拷贝还是复制,对于基本类型复制的是具体的值的副本,对于引用类型会找到对象中具体的属性或者方法,并且开辟新的相应的空间,一个一个的复制到另一个对象中,在这个过程中需要使用递归
- 如何区分深浅拷贝:
假设B复制了A,当修改A时,看B是否也跟着修改了,如果B跟着修改了,说明是浅拷贝,反之则说明是深拷贝。浅拷贝中B复制的是A的引用,深拷贝中,B复制的是A的本体。 - 实现拷贝的方法:
- 浅拷贝:Object.assign(target,…sources);扩展运算符{…obj};cancat;arr.slice(begin,end)
- 深拷贝:JSON.parse(JSON.stringify(obj));封装递归;第三方库,比如JQuery的$.extend({},obj);
- JSON.parse(JSON.stringify(obj))处理的缺点?
- 拷贝的对象的值中如果有函数、undefined、symbol这几张类型,经过JJSON.stringify(obj)序列化后的字符串中的这个键值对会消失
- 拷贝Date引用类型会变成字符串;
- 拷贝RegExp引用类型会变成空对象;
- 无法拷贝不可枚举的属性以及对象的原型链
- 无法拷贝循环引用的对象,即对象成环(obj[key] = obj)
3.如何判断空对象,如何区分数据类型
- 如何判断空对象?
- 使用Object.keys(obj).length
- 使用Object.getOwnPropertyNames(obj).length
- 使用JSON.stringify(obj)==="{}"
- 使用for in
const obj1 = {};
function isEmptyObj(obj) {
for(let key in obj) {
return false;
}
return true;
}
alert(isEmptyObj(obj1)); //true
- 如何区分数据类型?
Object.prototype.toString.call()
typeof
instanceof
constructor:null和undefined是无效的对象,因此是不会有constructor存在的
4.如何改变this指向?区别?
- 1.使用call、apply: call和apply都是可以改变this指向的问题,call方法中传递参数要求一个一个传递参数。但是apply方法要求传递参数是一个数组形式。
let a = {
name: 'sunq',
fn:function(action){
console.log(this.name + ' love ' + action);
}
}
let b = {name:'sunLi'}
// 正常的this指向
a.fn('basketball'); // sunq love basketball
a.fn.apply(b,['football']); // sunLi love football
a.fn.call(b,'football'); // sunLi love football
- 2.使用bind:会返回一个新的函数。如果需要传参,需要再调用该参数并传参
a.fn.bind(b)('piano'); // sunLi love piano
5.沙箱隔离怎么做
iframe标签
<iframe src="URL"></iframe>
6.浏览器存储的区别
- 1.localStorage:永久保存,以键值对保存
- 2.sessionStorage:会话保存,以键值对保存,会话结束就会清空
- 3.cookie:会话保存,以键值对保存,会话结束就会清空,但是cookie可以设置过期时间
document.cookie = "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT; path=/" // 设置cookie
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT" // 删除cookie
- 4.indexedDB:将数据集作为个体对象存储,数据形式使用的是JSON。存储容量大
7.常用的数组方式有哪些
改变原数组:push、pop、shift、unshift、sort、splice、reverse
不改变原属组:concat、join、map、forEach、filter、slice
slice和splice的区别?
slice切片的意思,根据传入的起始和终止下标,获取该范围数组。
splice可根据传入参数个数不同实现删除、插入操作,直接操作原数组。第1个参数为起始下标,第2个为删除个数,第3个为要增加的数据。
数组如何滤重?
let arr = [1,2,3,4,5,5,5,5,4,9,9,'西柚','西柚',"草莓","草莓",undefined,undefined,null,null,NaN,NaN,{},{},0,0]
console.log([...new Set(arr)])
8.Dom事件流的顺序?什么是事件委托
当页面上的一个元素被点击时,先从document向下一层层捕获到该元素。然后再向上冒泡,一层层触发。
事件委托是将事件写在父级元素上,e.target是事件捕获时那个最小的元素,即选中的元素。所以可以根据e.target操作选中的元素。这样不需要给每个子元素绑定事件,代码更加简约。
9.对原型链的认识
通过对象__proto__属性指向函数的原型对象(函数.prototype)一层一层往上找,直到找到Object的原型对象(Object.prototype)为止,层层继承的链接结构叫做原型链(通过proto属性形成原型的链式结构,专业术语叫做原型链)
10.防抖/节流的区别?
防抖每次触发时都会取消之前的延时调用。节流每次触发事件时都会判断是否等待执行的延时函数。
区别:防抖和节流本质上是不一样的。防抖是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段事件执行。函数防抖一定连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
应用场景:防抖:实时搜索的input,一直输入就不发送
let input = document.querySelector("input");
let time = null;//time用来控制事件的触发
input.addEventListener('input',function(){
//防抖语句,把以前的定时删除,只执行最后一次
if(time !== null){
clearTimeout(time);
}
time = setTimeout(() => {
console.log(this.value);//业务实现语句,这里的this指向的是input
},500)
})
节流:在指定的时间内多次触发无效
//节流
function throttle(fn, time) {//连续触发事件 规定的时间
let flag = false;
return function () {
//使用标识判断是否在规定的时间内重复触发了函数,没有就触发,有就不触发
if (!flag) {//不为假时 执行以下
fn();//触发事件
flag = true;//为真
setTimeout(() => {//超时调用(在规定的时间内只执行一次)
flag = false;
}, time);
}
}
}
mybtn.onclick = throttle(btn, 3000);//单击事件 节流(btn,3s时间)
11.ES5/ES6如何实现继承
ES5:
1.原型链继承:直接让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承。
2.借用构造函数继承(伪造函数,经典继承):在子类的构造函数中执行父类的构造函数,并为其绑定子类的this。让父类的构造函数把成员的属性和方法都挂在子类的this上,这样既避免了共用一个原型实例,又能像父类构造函数传参。
继承不到父类上的属性和方法
3.组合式继承:将原型链继承和构造函数继承结合,通过原型链继承父类的属性和方法,再通过构造函数继承父类的属性和方法。
function fat(name) {
this.name = name;
}
fat.prototype.getName = function() {
return this.name;
}
// 构造函数继承
function Child(name) {
fat.call(this, name)
}
// 原型链继承
Child.prototype = new fat();
Child.prototype.constructor = Child;
const child1 = new Child("aqing");
const child2 = new Child("aq");
child1.name = 'wzg';
console.log(child1.name); // wzg
console.log(child2.name); // aq
console.log(child1.getName()); //wzg
4.寄生式组合继承:将指向父类实例 变成转向父类原型,减少一次构造函数的执行。
存在问题:如果对子类原型操作就会对父类原型产生影响。为了解决问题,给父类原型做一个浅拷贝
function fat(name) {
this.name = name;
}
fat.prototype.getName = function() {
return this.name;
}
// 构造函数继承
function Child(name) {
fat.call(this, name);
}
// 原型链继承
Child.prototype = fat.prototype // 将指向父类实例转成转向父类原型
Child.prototype.constructor = Child;
const child1 = new Child("aqing");
const child2 = new Child("aqing");
child1.name = 'wzg';
console.log(child1.name); // wzg
console.log(child2.name); // aqing
console.log(child1.getName()); // wzg
ES6继承extends
// 父类
class fat {
constructor(props) {
this.name = props || '';
}
getName () {
return this.name
}
}
// 继承
class Child extends fat {
constructor(props, attrs) { // props继承父类的属性,attrs自己私有的属性
super(props); // 相当于获取父类的this指向
this.rename = props.rename || ''
this.attrs = attrs
}
// 子类自己的方法
getFatname () {
return this.name
}
getAttrs () {
return this.attrs
}
}
// 通过new实例化一个构造函数
const child1 = new Child({
name: 'wzg',
rename: 'aqing'
}, 'wuzhiguang')
child1.getName() // wzg
child1.getRename() // aqing
child1.getAttrs() // wuzhiguang
12.什么是作用域和执行上下文
1.执行上下文:可以理解为当前代码的运行环境,作用是用来保存当前代码运行时所需要的数据,在全局环境,函数环境中会创建执行上下文 /
2.作用域:作用域是在运行代码时代码中的某些特定部分中变量,函数和对象的可访问性。作用域决定了代码区块中变量和其他资源的可见性。/
3.块级作用域:可通过新增命令let和const声明,所声明变量在指定块的作用域外无法被访问,不能在同一作用域内重复声明一个已有标识符,如果在嵌套的作用域内使用let声明一个同名的新变量,则不会抛出错误
13.垃圾回收机制
1.标记清除:当变量进入上下文时,会对其添加上存在于上下文的标记。当变量退出上下文时,对退出上下文的变量添加上退出上下文的标记
2.引用计数:当变量被赋值或者被访问时,会将变量的引用计数加1,当变量不再被使用时,会将变量的引用计数减1,当引用计数为0时,会将变量回收
14.常用操作字符串方法有哪些?是否更改自身?
- concat():连接两个字符串,返回一个新字符串,不改变原字符串
- charAt():返回指定位置的字符,不改变原字符串
- indexOf():返回指定字符在字符串中首次出现的位置,不改变原字符串
- includes():判断字符串中是否包含该字符,不改变原字符串
- match():在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
- repeat():字符串重复指定的次数,不改变原字符串
- replace():在字符串中用一个值替换另一个值,不改变原字符串
- replaceAll():在字符串中用一个值替换另一些值,不改变原字符串
- search():检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。返回与指定查找的字符串或者正则表达式相匹配的 String 对象起始位置。
- slice(start,end):从原字符串中取出子字符串并返回,不改变原字符串
- split():将字符串分割成数组,不改变原字符串
let str = "Hello";
let s = str.split("e");
console.log(str); //Hello
console.log(s); //[ 'H', 'llo' ]
- substring(from,to):提取字符串中介于两个指定下标之间的字符,不改变原字符串 \
- toLowerCase():将字符串中的所有字符转换为小写,不改变原字符串 \
- toUpperCase():将字符串中的所有字符转换为大写,不改变原字符串 \
- trim():从字符串的开头和结尾删除空白字符,不改变原字符串
15.break/continue/return的使用场景
break:跳出整个循环
continue:跳过当前循环,进入下一层循环
return:结束函数
16.let,const,var区别
- var:存在变量提升,可以先去声明再去使用,一个变量可多次声明,后面的声明会覆盖前面的声明
- const:const声明一个只读的变量,声明后,值就不能改变(引用类型地址不变即可值可改变)
- let:不存在变量提升,let声明变量前,该变量不能使用
17.事件冒泡与事件捕获原理
事件捕获:触及的事件从文档根节点(Document 对象)流向目标节点,途中会经过目标节点的各个节点,并在这些节点上触发捕获事件,直至到达事件的目标节点。是由外到内层
事件冒泡:与事件捕获相反,事件会从目标节点流向文档根节点,途中会经过目标节点的各个父级节点,并在这些节点上触发捕获事件,直至到达文档的根节点。由内到外
18.父div和子div都绑定了click事件,点击子div触发事件,这个事件的回调顺序
该原理同上,默认是事件冒泡,先触发子元素再往它的上级触发
19.阻止冒泡的方式及作用
使用 e.stopPropagation()来阻止事件冒泡。作用是阻止我们触发它上级的事件
20.内存泄漏
内存泄漏指的是我们用动态存储分配的函数来开辟空间,但是在使用完了没有释放,结果就一直占据该内存单元,直到程序结束。简而言之就是用完了还没回收这就是内存泄漏。