js面试题

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的本体。
  • 实现拷贝的方法:
  1. 浅拷贝:Object.assign(target,…sources);扩展运算符{…obj};cancat;arr.slice(begin,end)
  2. 深拷贝:JSON.parse(JSON.stringify(obj));封装递归;第三方库,比如JQuery的$.extend({},obj);
  • JSON.parse(JSON.stringify(obj))处理的缺点?
  1. 拷贝的对象的值中如果有函数、undefined、symbol这几张类型,经过JJSON.stringify(obj)序列化后的字符串中的这个键值对会消失
  2. 拷贝Date引用类型会变成字符串;
  3. 拷贝RegExp引用类型会变成空对象;
  4. 无法拷贝不可枚举的属性以及对象的原型链
  5. 无法拷贝循环引用的对象,即对象成环(obj[key] = obj)

3.如何判断空对象,如何区分数据类型

  • 如何判断空对象?
  1. 使用Object.keys(obj).length
  2. 使用Object.getOwnPropertyNames(obj).length
  3. 使用JSON.stringify(obj)==="{}"
  4. 使用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.常用操作字符串方法有哪些?是否更改自身?

  1. concat():连接两个字符串,返回一个新字符串,不改变原字符串
  2. charAt():返回指定位置的字符,不改变原字符串
  3. indexOf():返回指定字符在字符串中首次出现的位置,不改变原字符串
  4. includes():判断字符串中是否包含该字符,不改变原字符串
  5. match():在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
  6. repeat():字符串重复指定的次数,不改变原字符串
  7. replace():在字符串中用一个值替换另一个值,不改变原字符串
  8. replaceAll():在字符串中用一个值替换另一些值,不改变原字符串
  9. search():检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。返回与指定查找的字符串或者正则表达式相匹配的 String 对象起始位置。
  10. slice(start,end):从原字符串中取出子字符串并返回,不改变原字符串
  11. split():将字符串分割成数组,不改变原字符串
let str = "Hello";
let s = str.split("e");
console.log(str); //Hello
console.log(s); //[ 'H', 'llo' ]
  1. substring(from,to):提取字符串中介于两个指定下标之间的字符,不改变原字符串 \
  2. toLowerCase():将字符串中的所有字符转换为小写,不改变原字符串 \
  3. toUpperCase():将字符串中的所有字符转换为大写,不改变原字符串 \
  4. trim():从字符串的开头和结尾删除空白字符,不改变原字符串

15.break/continue/return的使用场景

break:跳出整个循环
continue:跳过当前循环,进入下一层循环
return:结束函数

16.let,const,var区别

  1. var:存在变量提升,可以先去声明再去使用,一个变量可多次声明,后面的声明会覆盖前面的声明
  2. const:const声明一个只读的变量,声明后,值就不能改变(引用类型地址不变即可值可改变)
  3. let:不存在变量提升,let声明变量前,该变量不能使用

17.事件冒泡与事件捕获原理

事件捕获:触及的事件从文档根节点(Document 对象)流向目标节点,途中会经过目标节点的各个节点,并在这些节点上触发捕获事件,直至到达事件的目标节点。是由外到内层

事件冒泡:与事件捕获相反,事件会从目标节点流向文档根节点,途中会经过目标节点的各个父级节点,并在这些节点上触发捕获事件,直至到达文档的根节点。由内到外

18.父div和子div都绑定了click事件,点击子div触发事件,这个事件的回调顺序

该原理同上,默认是事件冒泡,先触发子元素再往它的上级触发

19.阻止冒泡的方式及作用

使用 e.stopPropagation()来阻止事件冒泡。作用是阻止我们触发它上级的事件

20.内存泄漏

内存泄漏指的是我们用动态存储分配的函数来开辟空间,但是在使用完了没有释放,结果就一直占据该内存单元,直到程序结束。简而言之就是用完了还没回收这就是内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值