前端面试题

508 阅读9分钟

1.事件流是什么?

当我们在网页上进行某些类型的交互时,也会触发事件,比如在某些内容上的点击、鼠标经过某个特定元素或按下键盘上的某些按键。当一个节点产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程称为DOM事件流

事件流描述的就是从页面中接收事件的顺序。而早期的IE和Netscape提出了完全相反的事件流概念,IE事件流是事件冒泡,而Netscape的事件流就是事件捕获

image.png

DOM2级事件规定的事件流包括三个阶段:

(1)事件捕获阶段 (2)处于目标阶段 (3)事件冒泡阶段

其中定义了两个方法:

addEventListener() ---添加事件侦听器

removeEventListener() ---删除事件侦听器

函数均有3个参数, 第一个参数是要处理的事件名   第二个参数是作为事件处理程序的函数   第三个参数是一个boolean值,默认false表示使用冒泡机制,true表示捕获机制。

<div id="btn">点击</div>

<script>
var btn=document.getElementById("btn");
btn.addEventListener("click",hello,false);
btn.addEventListener("click",helloagain,false);
function hello(){
    console.log("hello");
}
function helloagain(){
    console.log("hello again");
}
</script>

在btn2上加上stopPropagation函数,阻止程序冒泡。

2.js闭包是什么?

能够读取其他函数内部变量的函数

在一个函数内部可以访问另一个函数的变量

总结:

  • 形成: 函数中嵌套函数
  • 作用: 函数内部调用外部变量、构造函数的私有属性、延长变量生命周期
  • 优点: 希望一个变量长期存在内存中、模块化代码避免全局变量的污染、封装对象的私有方法和私有属性
  • 缺点: 无法回收闭包中引用的变量,容易造成内存泄漏

使用场景:

  1. return一个函数
  2. 函数作为参数
  3. 自执行函数
  4. ajax请求的成功回调
  5. setTime的延时回调
  6. 函数节流、防抖

3.什么是事件委托

如果有多个DOM节点需要监听事件的情况下,给每个DOM绑定监听函数,会极大的影响页面的性能,事件委托利用冒泡的原理来进行优化,。

将绑定事件委托到li的父级元素,即ul

var ul_dom = document.getElementsByTagName('ul')
ul_dom[0].addEventListener('click', function(ev){  
    console.log(ev.target.innerHTML)
})

4.js面向对象是什么

面向对象是一种思想

特点:

  1. 封装:将一些方法封装起来,留出一个接口供外部使用
  2. 继承:为了代码复用,父类的方法和属性子类也可以继承
  3. 多态:把想做什么和谁去做分开,也就是不同对象操作产生不同结果 this: 当前的方法属于谁就指向谁

5.js原型链是什么

原型:

给其他对象提供共享属性的对象,简称原型(prototype)

原型是定义一些公用的属性和方法,利用原型创建出来的新对象实例会共享原型的所有属性和方法

JavaScript的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是自身的原型JavaScript的函数对象,除了原型 [proto] 之外,还有 prototype 属性,当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [proto]

proto: 隐式原型 js标准里对象的原型无法直接通过属性名访问,单数多数浏览器实现了_proto_属性来访问对象的原型 constructor: 构造器 创建一个函数时会为他增加一个prototype属性,指向原型对象,原型对象自动获得一个名为constructor的属性,值回与之相关联的构造函数

作用:共享属性

优点:

  • 节约空间
  • 灵活度高 缺点:
  • 难理解
  • 易被覆盖和修改

原型链:

如果要访问对象中并不存在的属性,就会查找对象内部prototype,在查找属性时会对他进行层层遍历,这个关联关系实际上定义了一条原型链。

一个对象A调用方法的时候,会先从自身找有没有这个方法,如果没有就找自己的原型,
看有没有该方法,没有就继续找对象A的父类B的原型,就这样一级一级往上找,称之
为原型链。
    

原型链最终会指向Object.prototype,原型链的终点是Object.prototype.proto,也就是null

  • 继承中的原型链示意图: image.png

6.cookie sessionStorage localStorage的区别

  1. cookie 一条数据大小不能超过4KB,最多存储不能超过20条,如果没有设置过期时间,在浏览器关闭后就会消失
  2. sessionStorage 是会话存储,一条大小不能超过5M,数量没有限制,仅在当前网页会话下有效,关闭页面或浏览器后就会被清除
  3. localStorage 本地存储,一条大小不能超过5M,数量没有限制,除非主动删除,否则数据不会消失。

7.深拷贝、浅拷贝。广度优先级和深度优先级

深拷贝(Deep Copy),浅拷贝(Shallow Copy)

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

  • 基本数据类型的特点:直接存储在栈(stack)中的数据
  • 引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

image.png 浅拷贝是按位拷贝对象,它会创建一个新的对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

image.png

深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。 (2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。 (3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。 (4) 深拷贝相比于浅拷贝速度较慢并且花销较大。

image.png

深度优先(一条道走到黑)

广度优先(一层一层走)

深度优先搜素算法: 不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢。

广度优先搜索算法: 保留全部结点,占用空间大; 无回溯操作(即无入栈、出栈操作),运行速度快。

通常 深度优先搜索法不全部保留结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库中存储的结点数就是深度值,因此它占用空间较少。

所以,当搜索树的结点较多,用其它方法易产生内存溢出时,深度优先搜索不失为一种有效的求解方法。  

广度优先搜索算法,一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题。

但广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些

8.大数相加,溢出问题

<script>
        var str1 = "11111111111111111111117";
        var str2 = "22222222222222222222222229";

        var len1 = str1.length;
        var len2 = str2.length
        var maxLeng = Math.max(len1, len2);
        console.log(len1, len2, maxLeng);

        var tmp = 0;//进位
        var sum = 0;
        var myStr = "";
        for (var i = 0; i < maxLeng; i++) {
            // charAt() 返回固定索引位置得值
            sum = Number(str1.charAt(len1 -i- 1)) + Number(str2.charAt(len2 -i- 1)) + tmp;
            if (sum > 9) {
                sum = sum % 10;
                tmp = 1;
            } else {
                tmp = 0;
            }
            myStr = sum + myStr;
        }
        console.log(myStr);//22233333333333333333333346
    </script>

9. this指向问题

1:this永远指向一个对象;

2:this的指向完全取决于函数调用的位置;

10. call、apply、bind的区别

相同点:都可以更改this的指向

1. call

Function.call(obj,[param1[,param2[,…[,paramN]]]])
  • 调用 call 的对象,必须是个函数 Function。
  • call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
  • 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。 2. apply

调用者必须是函数 Function,并且只接收两个参数。

Function.apply(obj[,argArray])
  • 第一个参数,是一个对象。Function的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
  • 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。

3. bind

Function.bind(thisArg[, arg1[, arg2[, ...]]])
  • bind 方法的返回值是函数,并且需要稍后调用,才会执行。

总结:

  1. call,bind后面的第一个参数是指向的对象,第二个参数是往对象里边传的值。
  2. apply后面的第一个参数是指向的对象,第二个参数是数组,数组里边是往对象传的值。
  3. call和apply更改this指向会自动调用,bind需要手动调用