2024年最新「2024」前端高频面试题之JS篇(二),小白勿进

JavaScript 和 ES6

在这个过程你会发现,有很多 JS 知识点你并不能更好的理解为什么这么设计,以及这样设计的好处是什么,这就逼着让你去学习这单个知识点的来龙去脉,去哪学?第一,书籍,我知道你不喜欢看,我最近通过刷大厂面试题整理了一份前端核心知识笔记,比较书籍更精简,一句废话都没有,这份笔记也让我通过跳槽从8k涨成20k。

JavaScript部分截图

如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

val *= 2; // => 全局的val = 1 * 2 = 2
      // 如果把上一句代码:val *= 2改为this.val *= 2,这时候的this就是json
    },
  };
  json.dbl();
  alert(json.val + val); // 10 + 2 = 12
})();
// 作用域:栈内存、执行上下文,这仨是一个玩意。var json 等于个对象,首先对象是堆内存,所以json是堆不叫栈,作用域只能是栈

// => “12”



var num = 10;
var obj = { num: 20 };
obj.fn = (function (num) {
  this.num = num * 3;
  num++;
  return function (n) {
    this.num += n;
    num++;
    console.log(num);
  };
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);
// 22 23 65 30


##### 5,作用域链面试题



var n = 1;
function fn() {
  var n = 2;
  function f() {
    n–;
    console.log(n);
  }
  f();
  return f;
}
var x = fn();
x();
console.log(n);
// 1 0 1


![](https://img-blog.csdnimg.cn/direct/9faad180ea5346398d29d8d932b7bff5.png)


#### 8,undefined、null的区别


##### 1,undefined


Undefined是全局对象的一个属性,也就是说它是全局作用域的一个变量


* 1,一个声明了却没有被赋值的变量是undefined
* 2,调用函数的时候,应该提供的参数没提供,该参数也是undefined
* 3,对象没有赋值的属性,该属性的值也是undefined
* 4,函数没有返回值的时候,默认值也是undefined


Undefined这个变量从根本上就没有定义,隐藏式空值


##### 2,null


null不是全局对象的属性。null表示空对象,指变量未指向任何对象。null常用在预期的值是一个对象,但是又没有关联的对象的地方使用


Null这个值虽然定义了,但它并未指向任何内存中的对象,声明式空值


##### 3,undefined 和 null的区别


Null和undefined都表示空/没有。主要区别是undefined表示尚未初始化的变量的值,null表示该变量有意缺少对象指向


* Undefined:意料之外(不是我们能决定的)

 

let num; // => 创建一个变量没有赋值,默认值是undefined
num = 12

* null: 意料之中(一般都是开始不知道值,我们手动先设置为null,后期再给予赋值操作)



let num = null  // => let num = 0;一般最好用null作为初始的空值,因为0不是空值,它在栈内存中有自己的存储空间(占了位置)
num = 12


当检查值是否为null或者undefined的时候,要注意相等(==)和全等(===)运算符的区别,前者会执行类型转换:



typeof null  // “object”
typeof undefined  // “undeinfed”
null == undefined  // true
null === undefined // false
null == null  // true
null === null  // true
!null  // true
Number.isNaN(1 + null)  // false
Number.isNaN(1 + undefined)  // true


#### 9,this


this是函数执行的主体(不是上下文),意思是谁把函数执行的,那么执行主体就是谁


this是谁 和函数在哪创建的或者在哪执行的都没有必然的联系


找this是谁的规律:


##### 1,给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的this是当前操作的元素本身



document.body.onclick = function (){
  // 此时这个方法中的this是document.body
}


##### 2,方法执行,看方法前面是否有点,有点, 点前面是谁就是谁,没有点,this是window(严格模式下是undefined)。自执行函数中的this是window和undefined



var name = “xiaowang”;
function fn() {
  console.log(this.name);
}
var obj = {
  name: “Hello world”,
  fn: fn,
};
obj.fn(); // Hello world   => this: obj
fn(); // xiaowang   => this: window(非严格模式下,严格模式下是undefined)    window.fn()把window.省略了

(function () {
 // 自执行函数中的this是window和undefined
})()


##### 3,在构造函数模式执行中,函数体内的this是当前类的实例



function Fn() {
 // this: f这个实例
 this.name = ‘xxx’
}
let f = new Fn()


##### 4,箭头函数中没有自己的this,它里面的this是继承函数所处上下文中的this(所以真实项目中,一旦涉及this问题,箭头函数慎用)



window.name = ‘WINDOW’
let obj = {
  name: ‘OBJ’
}
let fn = n => {
  console.log(this.name)
}
fn(10)  // => this: WINDOW
fn.call(obj, 10) // => this: WINDOW


##### 5,案例



1,ary.proto.proto.hasOwnProperty()
// hasOwnProperty()方法中的this: ary.proto.proto   点前面的

2,let obj = {
  fn: (function (n) {
    // 把自执行函数执行的返回结果赋值给fn
    // this: window
  })(10),
};

let obj = {
  fn: (function (n) {
    return function () {
      // => fn 等价于这个返回的小函数
      // this: obj
    };
  })(10),
};
obj.fn();

function fn() {
  console.log(this);   // this: window
}
document.body.onclick = function () {
 // this: document.body
  fn();
};



function Fn() {
  // this: f1
  this.x = 100;
  this.y = 200;
  this.say = function () {
    // this: 这个this是谁,不知道,因为方法此时还没有执行
    console.log(this.x);
  };
}
Fn.prototype.say = function () {
  console.log(this.y);
};
Fn.prototype.eat = function () {
  console.log(this.x + this.y);
};
let f1 = new Fn;



function Fn() {
  // this: f1
  this.x = 100;
  this.y = 200;
  this.say = function () {
    console.log(this.x);
  };
}
Fn.prototype.say = function () {
  console.log(this.y);
};
Fn.prototype.eat = function () {
  console.log(this.x + this.y);
};
Fn.prototype.write = function () {
  this.z = 1000;
};
let f1 = new Fn();
f1.say(); // this: f1 => console.log(this.x) => console.log(f1.x) => 100
f1.eat(); // 私有里面没有eat方法,找原型链上的eat方法,this: f1 => console.log(this.x + this.y) => console.log(f1.x + f1.y) => 300
f1.proto.say(); //this: f1.proto => console.log(this.y) => console.log(f1.proto.y) => undefined
Fn.prototype.eat(); //this: Fn.prototype => console.log(this.x + this.y) => console.log(Fn.prototype.x + Fn.prototype.y) => undefined + undefined => NaN
f1.write(); //this: f1 => this.z = 1000 => f1.z = 1000 => 给f1设置了一个私有的属性z=1000
Fn.prototype.write(); //this: Fn.prototype => this.z = 1000 => Fn.prototype.z = 1000 => 给原型上设置一个属性z=1000,这个属性是实例的公有属性
/**
 * 面向对象中有关私有/公有方法中的this问题
 * 1,方法执行,看前面是否有点,有点,点前面是谁this就是谁
 * 2,把方法中的this替换
 * 3,再基于原型链查找方式确定结果即可
 * **/



var fullName = “xiaowang”;
var obj = {
  fullName: “javascript”,
  prop: {
    getFullName: function () {
      return this.fullName;
    },
  },
};
console.log(obj.prop.getFullName()); // 方法前面有点,点前面是谁就是谁 => obj.prop,prop方法里return this.fullName => prop里没有fullName 所以返回undefined
var test = obj.prop.getFullName;
console.log(test()); // test这个方法前面没点,所以是window => window.fullName = > xiaowang



var name = “window”;
var Tom = {
  name: “Tom”,
  show: function () {
    console.log(this.name);
  },
  wait: function () {
    // this: Tom  所以this.show => Tom.show
    var fun = this.show;
    fun(); // fun()的this => window, 执行show方法 => console.log(this.name) => console.log(window.name) => window
  },
};
Tom.wait();
// => window



window.val = 1;
var json = {
  val: 10,
  dbl: function () {
    this.val *= 2; // => 10 *= 2 = 20
  },
};
json.dbl();
// this: json => json.val *= 2 = 20

var dbl = json.dbl;
dbl();
// this: window => window.val *= 2 => 2

json.dbl.call(window);
// 通过call可以把方法中的this改成window
// this: window (基于call方法改的)  => window.val *= 2 => 2 * 2 => 4

alert(window.val + json.val);
// 4 + 20 = 24
// => “24”


#### 10,hasOwnProperty是什么?


作用:检测某个实例是否在某个对象上


我们学过的in:检测某个属性是否在某个对象上


##### 1,hasOwnProperty和in的区别



console.log(Array.prototype.hasOwnProperty(‘push’)) // true
console.log([] in Array) // false


##### 2,怎么判断是不是自己的私有属性



// 检测某个属性是否为对象的公有属性 hasPubProperty
Object.prototype.hasPubProperty = function (property) {
  if (![“string”, “number”, “boolean”].includes(typeof property))
    return false;
  return property in this && !this.hasOwnProperty(property);
};
console.log(Array.prototype.hasPubProperty(“hasOwnProperty”)); // => true
console.log(Array.prototype.hasPubProperty(“push”)); // => false


#### 11,科普什么是进程、内存、CPU、线程、JS的执行机制、同步、异步、事件循环


##### 1,进程


电脑每打开一个程序就是一个进程,或者浏览器中每打开一个页面也都是一个进程


任何一个程序,无论是用何种语言写的代码,当它在运行的时候,比如打开 QQ,在管理器会发现有一个进程在运行着,这就是我们经常说的一段程序执行的过程,每一个运行的程序都有一个进程,这些进程支撑着软件应用提供给我们的各种功能


###### 浏览器的工作方式


前端代码主要的运行环境就是浏览器,具体说就是浏览器内部的JS引擎(V8引擎)及渲染引擎。另一个是Node服务端开发的环境,其实还是从Chrome浏览器中抽取的V8引擎。


浏览器的请求是并发进行的,因为本质上它是给我们提供服务的,当页面加载的时候,需要的资源都是从服务端过来的一条条的请求,比如页面打开的时候有5个接口需要返回数据,还有图片、CSS资源、音视频资源等,假设这些合起来需要请求20个请求,实际上浏览器在处理这些请求的时候并不是一次性将20个请求一起发过去,而是有请求并发限制的,减少处理的时候浏览器本身的线程切换开销。比如Chrome,它实际上是6条并发进行,某一条请求完成后补充另外的请求进来,直到20条请求完毕。


浏览器的请求并发限制针对的是同一个域名下的资源,这样的话,我们就可以将静态资源和服务分离,分多域名存储,这样就可以简单的解决浏览器的并发瓶颈


比如以下小案例,分析一下浏览器的请求并发数:



                                                                                                      

以上代码罗列了20张图片资源,打开Network面板,选择All请求类型,可以看到如下界面:



![](https://img-blog.csdnimg.cn/img_convert/e7391dda6c1a18b2b533b460c358a54f.png)


图中可以清晰的看出并发数限制。这其实就像赛道接力,同时奔跑在赛道上的永远不超过跑道数(比如6),只有某一个赛道某一段跑完了,这个赛道才能释放出来,接着由下一个人跑。


###### 并发


并发是指某一个瞬间运行在同一处理机或者某个服务器集群上的服务或执行逻辑。比如,淘宝双十一当天0点刚过的那一瞬间,用户的每一次点击、支付都是一条服务,需要服务端去处理,可以想象一下那时候的人数和同时过来的请求数,这就是并发。


##### 2,内存


内存就是所谓的栈内存和堆内存


##### 3,CPU


CPU就是处理器


##### 4,线程


线程:一个进程里可以同时处理很多任务,每一个任务都是基于线程处理的这就是线程


比如一个QQ进程,既能拍照又能聊天还可以语音、听音乐等,在一个QQ里可以同时做诸如以上的很多事情,这就是线程


进程在开启的时候,系统会给它分配一点资源,如内存、CPU计算资源、网络带宽、磁盘读写等,而线程就是在进程的基础上,从进程那里再分配一点资源来运行。线程是进程的一个实体,它只占用了一点运行时的资源,但它同样共享着其所属进程所拥有的全部资源。相比于进程,线程可以更高效地利用 CPU 资源


例如,A与B在QQ上聊天,C突然来消息了,此时的A同时与B和C聊天,用户只需要进行窗口切换,然而程序需要处理的逻辑是不同的用户关联不同的信息。每个程序只有一个进程,要同时完成两件事,一个很简单的办法就是再开一个进程,实现真正意义上的同时执行。


进程与进程之间的切换是很耗时间的,这时候线程就来了,进程能做的事情线程都能做,而且只需要很少的运行资源。线程之间的切换也比进程快得多,大大地提高了系统的性能。


其实,开的线程实际上还是一个进程,那它是怎样做到接近并行的呢?实际上,线程是把 CPU 的工作时间分成几个片段,一个片段执行一个任务。由于它们之间切换效率极高,在用户看来就像是在同时工作,这类似于我们看电视、计算机等的情况。实际上电视和计算机的屏幕是以一种很快的频率在刷新,看起来就像是连贯的,这个过程利用的就是 CPU 的调度算法。


##### 5,JS 的执行机制


前面我们知道了大多数程序在并发完成某个任务的时候,实际上是开了一条线程在跑。以php举例,当有一个任务或者请求时,可以从线程池里取一条线程运行,当线程处理完请求或操作逻辑后重新放回线程池中。


这样的方式是现在大多数后台语言的处理方式,由于线程有自己独立的堆栈,会比较安全。这种方式也存在一个问题:线程并不能无限的新增,这个线程池里的线程实际上是有限的,当并发量非常高的时候,就会发现线程不够用。当线程不够用的时候,剩余的任务只能等线程释放出来再执行。尽管线程的运行速度非常快,切换速率也高,但是也抵挡不住庞大的并发量,因此通过优化可以极大的提高运行性能。


那么JS的运行机制是怎样的呢?


JS 实际上只有一个线程,它是怎样同时做不同的事情的呢?以Node为例,JS的处理可以看作是一位分配任务的领导,当收到一个任务或者请求的时候,JS引擎将该任务放入任务队列中,JS的同步主线程仅仅做了一个标识,用来接受及分配任务或请求。


这个过程不需要做任何的处理,类似于倾听和记录,这样做就可以非常高效的处理任务了。在该任务中,任务A在某个时间节点开始执行, 任务B在另一个时间节点开始执行,任务与任务之间不会互相等待、阻塞,任务其实几乎是同时进行的,这样大大的加快了程序的执行效率。JS虽然是单线程的,但是通过这种异步队列的方式分配和执行任务,可以大大的提高程序的执行性能。


任务队列和线程最大的不同是队列只要内存足够,可以一直堆叠任务。这样会造成很大的内存开销,但其实如金硬件成本越来越低,增加一点内存就能极大的提高用户体验


##### 6,同步


同步:任务是依次执行的,上一个任务没有完成,下一个任务不能进行处理,即一个一个执行。它是基于浏览器的单线程完成的


##### 7,异步


异步:上一个事情没有完成,下一个事情也可以继续完成,它是基于浏览器的多线程完成的


JS也是单线程的(大部分任务都是同步任务),但是也有异步任务:


* 1,定时器(设置定时器是同步的,多长时间之后那个方法这件事是异步的)
* 2,事件绑定
* 3,ajax数据请求项目中基本都是用异步处理
* 4,回调函数可以理解为异步
* 5,Promise/async/await就是用来处理异步编程的
* 6,nodejs中也提供其余的异步编程方式


##### 8,事件循环


因为浏览器是多线程的, 浏览器只分配一个线程用来自上而下执行我们的代码,所以JS中大部分任务都属于同步任务,但是肯定有异步任务,比如定时器...等,那么浏览器是怎么处理JS中的异步任务的呢,首先主线程在自上而下执行的时候,代码进栈,进栈后出栈,反反复复,当遇到异步任务的时候,会把当前这个异步任务放到等待任务队列(Event Queue)中存起来,主线程的代码继续加载执行,当把主线程所有代码都加载执行完了,也就是主线程空闲下来的时候,就到等待任务队列中查找到达时间的任务,拿到主线程所在内存中执行,当执行完,再去等待任务队列看看还有哪一个到时间了再拿到主线程来执行,反反复复,这么个过程就是**事件循环**


![](https://img-blog.csdnimg.cn/direct/947db25911cc4f33a5e2bb77ba2648d5.png)


![](https://img-blog.csdnimg.cn/direct/ea1c9b3d9f594095a1c39897d6c6dde0.png)


定时器设置一个等待时间,到达时间后不一定执行(如果当前主线程被占用着,所有任务都要等主线程空闲下来,才能被安排执行),因为JS是单线程的,一次只能干一件事:



let n = 0;
setTimeout(() => {
  n++;
  console.log(n);
}, 0);
console.time(“AA”);
for (let i = 0; i < 900000000; i++) {}
console.timeEnd(“AA”);
n += 2;
console.log(n);


以上代码输出结果为: AA: 741.64208984375 ms 2 3


主线程被死循环牵绊住,不会再执行Event Queue里的代码:



let n = 0;
setTimeout(() => {
  n++;
  console.log(n); // => 没有执行,因为主线程被死循环牵绊住了
}, 0);
n += 2;
console.log(n); // => 2
while (1 === 1) {}


Event Queue中,谁先到时间先执行谁:



let n = 0;
setTimeout(() => {
  n++;
  console.log(n);
}, 500);
setTimeout(() => {
  n += 2;
  console.log(n);
}, 50);
for (let i = 0; i < 90000000; i++) {}
setTimeout(() => {
  n += 3;
  console.log(n);
}, 20);
console.log(n);


![](https://img-blog.csdnimg.cn/direct/c68a9bb35a2c4b53822cee7e6d63952a.png)


#### 12,事件及浏览器常用的事件行为


##### 1,事件是什么


事件是元素天生自带的默认行为,无论我们是否给其绑定了方法,当我们操作的时候,也会把对应的事件触发


##### 2,事件绑定是什么


事件绑定是给元素的某个行为绑定一个方法,目的是当前行为触发的时候,可以做一些事情


##### 3,常见的事件行为


###### 1,鼠标事件



click 点击 (移动端click被识别为单击)
dbclick 双击
mousedown 鼠标按下
mouseup 鼠标抬起
mousemove 鼠标移动
mouseover 鼠标滑过
mouseout 鼠标滑出
mouseenter 鼠标进入
mouseleave 鼠标离开
mousewhell 鼠标滚轮滚动


###### 2,键盘事件



keydown 按下某个键
keyup 抬起某个键
keypress 除shift/fn/capsLock键外,其他键按住(连续触发)


###### 3,移动端手指事件



touchstart 手指按下
touchmove 手指移动
touchend 手指松开
touchcancel 操作取消(一般应用在非正常状态下操作结束)


###### 4,表单元素常用事件



focus  获取焦点
blur 失去焦点
change 内容改变


###### 5,音视频常用事件



canplay 可以播放(资源没有加载完,播放中可能会卡顿)
canplaythrough 可以播放(资源已经加载完,播放中不会卡顿)
play 开始播放
playing 播放中
pause 暂停播放


###### 6,其他常用事件



load 加载完
unload 资源卸载
beforeunload 当前页面关闭之前
error 资源加载失败
scroll 滚动事件
readystatechange ajax请求状态改变事件
contextmenu 鼠标右键触发


##### 4,DOM0和DOM2事件绑定的区别


* DOM0:

 

// 元素.on事件行为 = function(){}
eg: box.onclick = function () {}

* DOM2:

 

// 元素.addEventListner(事件行为, function(){}, true/false)
eg: box.addEventListner(‘click’, function(){}, true)



DOM0事件绑定的原理:给元素的私有属性赋值,当事件触发,浏览器会帮我们把赋的值执行,但是这样也导致“只能给当前元素某一个事件行为绑定一个方法:



let lufei = document.querySelector(“.lufei”);
lufei.onclick = function () {
  console.log(“哈哈哈”);
};
lufei.onclick = function () {
  console.log(“呵呵呵”);
};
// => 只执行最后一个,输出呵呵呵


DOM2事件绑定的原理:基于原型链查找机制,找到EventTarget.prototype上的方法并且执行,此方法执行,会把给当前元素某个事件行为绑定的所有方法,存放到浏览器默认的事件池中(绑定几个方法,会向事件池存储几个),当事件行为触发,会把事件池中存储的对应方法一次按照顺序执行“给当前元素某一个事件行为绑定多个不同方法:



let lufei = document.querySelector(“.lufei”);
lufei.addEventListener(
  “click”,
  function () {
    console.log(“哈哈哈”);
  },
  false
);
lufei.addEventListener(
  “click”,
  function () {
    console.log(“呵呵呵”);
  },
  false
);
// => 两个都会执行输出哈哈哈 呵呵呵


DOM2事件绑定的时候,我们一般采用实名函数,目的:这样可以基于实名函数去移除事件绑定



function fn () {
  console.log(‘hahaha’)
  // => 移除事件绑定:从事件池中移除,所以需要指定好事件类型、方法等信息
  lufei.removeEventListener(‘click’, fn, false)
}
lufei.addEventListener(‘click’, fn, false)



![](https://img-blog.csdnimg.cn/img_convert/39ccd666abe90c4ebe94b7329ada2cf1.png)



![](https://img-blog.csdnimg.cn/img_convert/635951428b45b9f98d1cd6a0955d0b9c.png)


##### 5,事件对象


给元素的事件行为绑定方法,当事件行为触发方法会被执行,不仅被执行,而且还会把当前操作的相关信息传递给这个函数 => 事件对象


如果是鼠标操作,获取的是 mouseEvent 类的实例 =》 鼠标事件对象



鼠标事件对象:mouseEvent.prototype -> UIEvent.prototype -> Event.prototype -> Object.prototype


如果是键盘操作,获取的是keyboardEvent类的实例 => 键盘事件对象


##### 6,阻止事件的默认行为


阻止a标签的默认行为



// => 方式1
11111  // => 给href加javascript:;

// => 方式2: 点击a标签,先触发click行为,然后再去执行href的跳转
11111
link.onclick = function () {
  return false; // => 返回一个false,相当于结束后面即将执行的步骤
};

// => 方式3:点击a标签,先触发click行为,然后再去执行href的跳转
11111
link.onclick = function (ev) {
  ev.preventDefault();
};


##### 7,事件的传播机制


冒泡传播:触发当前元素的某一个事件行为,不仅它的这个行为被触发了,而且它所有的祖先元素(一直到window)相关的事件行为都会被依次触发(从内到外的顺序)


阻止冒泡传播:



box.onclick = function(ev){
 ev.stopPropagation()  // => 阻止冒泡传播
}


事件的传播机制:


捕获阶段:从最外层向最里层事件源依次进行查找(目的:是为冒泡阶段事先计算好传播的层级路径)


目标阶段:当前元素的相关事件行为触发


冒泡传播:触发当前元素的某一个事件行为,不仅它的这个行为被触发了,而且它所有的祖先元素(一直到window)相关的事件行为都会被依次触发(从内到外的顺序)



![](https://img-blog.csdnimg.cn/img_convert/eb3b008e1e99ac70b41a3bd34f76203c.png)


##### 8,mouseover 和mouseenter的本质区别



![](https://img-blog.csdnimg.cn/img_convert/c3f07f1d31d95bb8da87aa416a5e0113.png)


#### 13,浏览器常用的事件-事件委托


##### 1,事件委托


###### 1,事件冒泡


事件开始时,由最具体的元素逐级向上传播,直到较为不具体的节点(文档)



![](https://img-blog.csdnimg.cn/img_convert/441179229b8ad46e7f165e1f43bd5907.png)


###### 2,事件捕获


不太具体的节点更早的接收到事件,最具体的节点最后接收到事件



![](https://img-blog.csdnimg.cn/img_convert/23fde0349d5032f1abbd6c74d0af8a46.png)


###### 3,事件流


事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段



![](https://img-blog.csdnimg.cn/img_convert/aa34613f118b21a42f546a54d0dafacb.png)


首先发生的是事件捕获阶段,为截获事件提供机会。然后是实际的目标接收到事件,最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应


###### 4,所谓的事件委托是什么?


是基于事件的冒泡传播机制完成的


###### 5,什么时候用事件委托,用它的好处?


如果一个容器中很多元素都要在触发某一事件的时候做一些事情,比如有100个元素:给每一个元素都单独进行事件绑定。


使用事件委托:我们只需要给当前容器的这个事件行为绑定方法,这样不论是触发后代中的哪一个元素的相关事件行为,由于冒泡传播机制,当前容器绑定的方法也都要被触发执行


###### 6,事件对象的ev.target事件源


想知道点击的是谁,只需要基于事件对象中的ev.target事件源获取即可


##### 2,事件委托的意义


###### 1,基于事件委托实现,整体性能要比一个个的绑定方法高出50%左右


###### 2,如果多元素触发,业务逻辑属于一体的,基于事件委托来处理,更加的好


###### 3,某些业务场景只能基于事件委托来处理


##### 3,案例


###### 1,购物车案例



// 方法一:

   购物车   
暂无购物车内容~
.box {   width: 100px;   height: 35px;   line-height: 35px;   border: 1px solid #AAA;   text-align: center;   margin: 20px auto;   box-sizing: border-box;   position: relative;   color: #AAA; } .detail {   width: 200px;   height: 100px;   position: absolute;   top: 33px;   right: -1px;   box-sizing: border-box;   border: 1px solid #AAA;   text-align: left;   color: #AAA;   display: none; } .box:hover .detail {   display: block; }


// 方法二:

  
     购物车   
  
暂无购物车内容~
.container {   box-sizing: border-box;   width: 200px;   margin: 20px auto; } .box {   width: 100px;   height: 35px;   line-height: 35px;   float: right;   border: 1px solid #AAA;   text-align: center;   box-sizing: border-box;   color: #AAA;   position: relative;   top: 1px; } .detail {   width: 200px;   height: 100px;   float: right;   box-sizing: border-box;   border: 1px solid #AAA;   text-align: left;   color: #AAA;   display: none; } let box = document.querySelector('.box'), detail = document.querySelector('.detail') document.onmouseover = function (ev) {   console.log(ev.target)   let target = ev.target   // if (target.tagName === 'span') {     // 如果事件源是span, 我们让其变成它的父元素   //  target = target.parentNode   // }   target.tagName === 'span' ? target = target.parentNode : null;   if (/^(box|detail)$/.test(target.className)) {     // target.className === 'box' || target.className === 'detail'     // 如果事件源是box和detail,让其显示     detail.style.display = 'block'     return   }   detail.style.display = 'none' }

###### 2,追加按钮点击并弹出相应的li的索引案例



// 方法1:
let  u l L i s t   =   ulList =  ulList = (‘.ulList’),
   b t n   =   btn =  btn = (‘.btn’),
  KaTeX parse error: Expected '}', got 'EOF' at end of input: … handle () {   list =  u l L i s t . c h i l d r e n ( ′ l i ′ )    ulList.children('li')    ulList.children(li)  list.each(function (index, item) {
    KaTeX parse error: Expected '}', got 'EOF' at end of input: …    alert(`我是第 {index + 1} 个li`)
    })
  })
}

handle()
let count = 5
KaTeX parse error: Expected '}', got 'EOF' at end of input: …tr += `<li>我是第 {count} 个li`
  }
  $ulList.append(str)
  handle()
})



// 方案2: 改为事件委托方式
let  u l L i s t   =   ulList =  ulList = (‘.ulList’),
   b t n   =   btn =  btn = (‘.btn’),
  count = 5

KaTeX parse error: Expected '}', got 'EOF' at end of input: …= ev.target;   target = KaTeX parse error: Expected '}', got 'EOF' at end of input: …    alert(`我是第 {$(target).index() + 1} 个li`)
  }
})

KaTeX parse error: Expected '}', got 'EOF' at end of input: …tr += `<li>我是第 {count} 个li`
  }
  $ulList.append(str)
})


#### 14,var、let、const的区别


【声明】:该题参考阮一峰大佬的[ECMAScript 6 入门]( )和自我的总结归纳,部分觉得阮老写的很好,没有修改的必要,即使修改也改不出花来,所以直接拿过来了


##### 1,区别总结


* **1,块级作用域方面**:
	+ var 定义的变量没有块的概念,可以跨块访问,不能跨函数访问
	+ let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问
	+ const 定义的变量,只能在块作用域里访问
* **2,是否存在变量提升**
	+ var 命令会发生变量提升现象,即变量可以在声明之前使用,值为undefined
	+ let和 const 命令不会发生变量提升
* **3,暂时性死区**
	+ 暂时性死区:在代码块内,使用`let`、`const`声明变量之前,该变量是不可用的
* **4,是否可以被重复声明**
	+ let、const 命令在相同作用域中, 不能重读声明
* **5,const**
	+ const声明一个只读的常量,一旦声明,常量的值不能改变
	+ 声明的基本数据类型不能被修改,声明的引用数据类型可以通过修改对象属性和方法,本质上,基本数据类型的数据值爆存在变量指向的那个内存地址,因此等同于常量。但是引用数据类型的数据,变量指向的内存地址保存的只是一个指向实际数据的指针,`const`只能保证这个指针式固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了


##### 2,区别详细介绍


###### 1,块级作用域


`ES5`中的作用域有:`全局作用域`、`函数作用域`,没有块级作用域的概念


`ES6`新增了`块级作用域`,由`{}`包括,`if`语句和`for`语句里面的`{}`都属于块级作用域



// 块级作用域
{
  var a = 100;
  let b = 200;
  const c = 300;
  console.log(a); // 100
  console.log(b); // 200
  console.log©; // 300
}
console.log(a); // 100
console.log(b); // b is not defined
console.log©; // c is not defined

// 函数作用域
(function () {
  var a = 100;
  let b = 200;
  const c = 300;
  console.log(a);
  console.log(b);
  console.log©;
})();
console.log(a);
console.log(b);
console.log©;


###### 2,变量提升



console.log(a); // undefined
var a = 1;

console.log(b); // 报错 ReferenceError
let b = 2;

console.log©; // 报错 ReferenceError
const c = 3;


###### 3,暂时性死区


在代码块内,使用`let`、`const`声明变量之前,该变量是不可用的。这在语法上成为暂时性死区简称TDZ



if (true) {
  // TDZ开始
  a = 10;
  console.log(a); // 报错 ReferenceError

let a; // TDZ结束
  console.log(a); // undefined

a = 20;
  console.log(a); // 20
}


ES6 规定暂时性死区和`let`、`const`语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了


**let能解决typeof检测时出现的暂时性死区问题,所以let比var更严谨**



总结
  • 对于框架原理只能说个大概,真的深入某一部分具体的代码和实现方式就只能写出一个框架,许多细节注意不到。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 算法方面还是很薄弱,好在面试官都很和蔼可亲,擅长发现人的美哈哈哈…(最好多刷一刷,不然影响你的工资和成功率???)

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

第一次跳槽十分忐忑不安,和没毕业的时候开始找工作是一样的感受,真的要相信自己,有条不紊的进行。如果有我能帮忙的地方欢迎随时找我,比如简历修改、内推、最起码,可以把烦心事说一说,人嘛都会有苦恼的~

祝大家都有美好的未来,拿下满意的 offer。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值