2023前端JS面试题
1、事件委托与批量添加事件监听的优缺点:
- 每一个事件监听的注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗会非常大,而实际上,因为每个事件处理函数都是独立的不同函数,所以这些函数本身会占用一定的内存
- 当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销,当有动态元素节点需要添加到DOM树上时,使用事件委托可以让新插入到DOM树中的元素自动具有事件监听
2、JS执行机制:
- 先执行执行栈中的同步任务
- 异步同步(回调函数)放入任务队列中
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行
3、e.target与this的区别:
- this是事件绑定的元素(绑定这个事件处理函数的元素)
- e.target是事件触发的元素
4、创建对象的方法:
-
使用字面量创建对象
const obj = { name: "tom", age: 18, study: function () { console.log("进行代码编辑"); } }2、利用new Object();创建对象
const ming = new Object() ming.name = "小明" ming.age = 17 ming.running = function () { console.log("五公里越野进行中~"); }3、工厂函数创建对象
function creatObject(name, age, sleep, eat) { // 创建对象 const cat = new Object() // 对象的参数和方法需要自定义 cat.name = name cat.age = age cat.sleep = sleep cat.eat = eat // 于是会想到利用函数来进行参数的接收 // 那么就把整个过程封装到一个函数中 // 发现虽然不同的对象创建好了,但是外部无法获取 // 所以需要使用return关键字将创建好的实例对象传递出去 return cat } const jafy = creatObject("加菲", "两年半",sleep,eat) function sleep() { console.log("呼噜呼噜~"); } function eat() { console.log("美味的千层饼啊,吧唧吧唧~"); } console.log(jafy);4、使用构造函数创建对象
function Cat(name,age,sleep,eat) { this.name=name this.age=age this.sleep=sleep this.eat=eat } const haly=new Cat("加菲", "两年半",sleep,eat) console.log(haly);
5、new在调用函数执行时会做四件事:
- new会使函数体自动在内存中创建一个新的空对象
- new会让函数的上下文(this)指向这个新的空对象
- 执行构造函数(也就是会执行函数体内的语句,目的是为了给这个新对象添加属性和方法)
- new会使函数自动返回上下文对象,即使函数没有使用return语句也会返回这个新对象
6、构造函数和对象的区别:
- 构造函数,如person(),抽取了对象的公共部分,封装到了函数里面,它泛指某一大类(class)
- 创建对象,如new Person(),特指某一个,通过new关键字创建对象的过程我们也称为对象实列化
7、认识上下文:
- 实列化完成以后,构造函数中的this其实指代的就是我们的实列化对象
- 函数中的this具体指代什么必须通过调用函数时的“前言后语”来判断
- 函数的上下文由其调用的方式决定
- 同一个函数,用不同的形式调用它,则函数的上下文不同
8、上下文规则
-
对象直接打点调用它的方法函数时,函数的上下文就是这个调用函数的对象
-
直接调用函数并使用圆括号执行,则函数的上下文是window对象
-
数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
-
IIFE中的函数,上下文是window对象
-
定时器、延时器调用函数,上下文是window对象
-
事件处理函数的上下文是绑定事件的DOM元素
总结:
-
以函数形式进行调用时(直接使用函数),this永远都是执行window的
-
以方法的形式进行调用时(对象.方法),this指向调用它的那个对象
-
通过apply、call调用的函数,this指向指定的那个对象,通过bind绑定的函数,this指向其绑定的对象
-
9、函数定义的三种方式
- apply()方法会调用一个函数,参数一是一个指定的this值,参数2是一个数组形式或类数组对象形式的参数。 apply(thisArg,[argArr])
- bind()方法会创建一个新的函数,我们一般称其为绑定函数,新函数与被调用的函数(绑定函数的目标函数)具有相同的函数体。 当目标函数在被调用时this值会被绑定到bind()方法的参数1上,需要注意的是,这个参数不能被覆写。绑定函数被调用时,bind()方法也接受预设的参数并提供给原函数。 一个绑定函数也可以使用new操作符来创建对象。这个行为就像是把原函数当做一个构造函数来使用,提供的this值会被忽略掉,同时,调用时的参数会被提供给模拟函数 。 fun.bind(thisArg,arg1,arg2,arg3…) thisArg:当绑定函数被调用时,此参数会作为原函数运行时的this执行,当使用new操作符调用绑定函数的时候,此参数无效 arg1…:当绑定函数被调用时,这些参数会被放在实参之前传给被绑定的方法它有一个返回值,会返回指定的this值和初始化参数改造的原函数的拷贝。
- call()方法调用一个函数,它会具有一个指定的this值和分别提供的参数或参数列表。 fun.call(thisArg,arg1,arg2,arg3…) thisArg:在fun函数运行时指定的this值 arg1…:指定的参数列表 此方法的作用和apply()方法类似,但是有一定的区别:call()方法接受的是多个参数的列表,而apply()方法接受的是一个包含多个参数的数组
10、apply、bind、call的区别:
call()和apply()特性一样
-
它们都是用来调用函数的,而且都是立即调用当前函数,但是可以在调用函数的同时,通过参数1指定函数内部this的指向
-
call在进行调用的时候,参数必须使用参数列表的形式进行传参,也就是说要使用arg1,arg2,arg3….这种形式
-
apply在进行调用的时候,参数必须是一个数组,在执行时会将数组内部的元素和形参进行一一对应并进行传递
-
如果参数1指定为null或undefined,那么函数内部的this指向为window bind
-
可以用来指定函数内部的this执行,然后会生成一个改变了this执行的新函数
-
bind与apply、call之间的最大区别是bind不会调用函数,而是绑定一个函数来改变它的this指向
-
、bind可以进行传参,但是传参的形式与以上两种不同,
(1)在进行bind的同时,会以参数列表的形式进行传参
(2)在调用时,会以参数列表的形式进行传参,具体以哪种形式进行传参,bind会将其两种形式进行合并进行参数传递
11、什么是封装?写出实现封装的3个方法
简单来说就是将相关联的代码组合到一起,以便于代码的复用与维护,同时防止内部的数据和方法被外界随意篡改,只能使用特定的方式进行调用,如我们最简单的封装就是函数,此外还有命名空间、构造函数。
封装的严格说法,利用抽象的数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。
12、简述原型、原型链与继承
每个函数都具有一个prototype属性,但只有构造函数的prototype 属性有作用,它的意思就是原型,因为它是一个对象类型的数据,所以又常被称为原型对象,常用来添加公共属性或方法以减小使用构造函数的内存开销。
原型对象具有一个constructor属性,指向原型对象所在的构造函数。
所有对象都都具有一个__proto__属性,通过构造函数实例化出的对象通过__proto__属性可以直接访问其构造函数的原型,从而调用原型的成员。
我们可以将公共的属性和方法封装进构造函数A中,使用构造函数A实例化出的对象a替换构造函数B的原型对象,当使用构造函数B实例化一个对象b时,实例化对象b可以调用对象a中所有的属性与方法, 此时我们就可以说对象b继承了对象a。
因为每一个构造函数都有原型对象,在实例化对象调用属性或方法时,会先从自身查找,如果自身不存在会通过其__proto__属性去它的构造函数的原型上进行查找,若仍然查找不到的话,会继续调用其构造函数的原型的__proto__属性向上找……由此形成的链式调用关系被称为原型链,原型链是一种调用规则。如果找到原型链的重点还未找到调用的属性或方法时,返回undefined。
原型链的终点是null,因为所有函数的__proto__属性都指向Function.prototype,而原型又是一个对象,所以Function.prototype的__proto__属性指向Object.prototype,而Object.prototype的__proto__属性为空。
我们所用的除Math对象外的所有内置对象都是构造函数,所以它们同时拥有prototype属性和__proto__属性,Math对象只是一个对象。
13、 什么是闭包?简述闭包的主要应用及使用闭包的缺点
闭包是函数本身和该函数声明时所处的作用域中所有变量的状态,简单来说就是函数包函数,内部函数使用外部函数中定义的变量。
因为闭包一定会产生封装,封装时会产生作用域隔离,所以使用闭包可以帮助解决变量污染问题,这也是其主要的应用方向。
由于生成闭包后,其内部函数可能随时会被调用,当其被调用时使用操作的变量根据作用域链会调用其外部函数中定义的变量,就需要将这些变量永久存放在内存中以备随时调用,这些变量所占用的内容在其函数被使用完毕销毁时也不会得到释放,所以闭包存在内存泄漏问题。
内部函数根据作用域链会调用外部函数中定义的变量,所以闭包具有记忆性。。
外部函数funA由于封装的原因使得函数外部无法直接调用其内部的变量,所以funA中的变量相当于其私有的,只有在它内部能被访问,此时若将funA内部的函数funB作为返回值进行调用,就能对funA中的变量进行直接操作,就相当于在任意的作用域中操作funA中的局部变量,所以闭包还具有模拟私有变量的特性。
14、 简述JavaScript中的this指向
非严格模式下,以函数形式进行调用时(直接使用函数),this永远指向window,严格模式下指向undefined。
以方法的形式进行调用时(对象.方法),this指向调用它的那个对象。
通过apply、call调用的函数,this指向指定的那个对象,通过bind绑定的函数,this指向其绑定的对象。
15、简述静态成员与实例成员的区别,在类中如何创建静态成员
静态成员只能被类或构造函数口调用,实例成员只能被实例化对象调用。
在类中没有被static修饰的成员为实例成员,被static修饰的成员为静态成员;在构造函数中直接在函数体中定义的成员为静态成员,添加在this上的成员为实例成员。
16、什么是箭头函数?简述箭头函数与普通函数的区别
使用( )=>{ }这样的形式创建出来的函数的为箭头函数,它比普通函数的语句更加的简短,与普通函数之间最大的区别是其内部不能绑定this,它内部的this其实是这个箭头函数所处的作用域的this,所以不受调用者影响,而普通函数中的this与其自身相绑定,所以会受调用者影响。
17、 简述for、for...in、forEach( )方法、for...of的主要作用及它们之间的区别
for循环适用于遍历普通数组;for…in常用来遍历对象,虽然可以用来遍历数组但是不推荐,此外它自身还存在一些缺陷,如在遍历是会遍历对象自身和其继承的可枚举属性;forEach( )方法可用来遍历数组、类数组对象、Set、Map等;for…of方法修复了for…in中的缺陷,可用于遍历所有可迭代类型的对象,但是不能遍历普通对象,因为对象不可迭代。