前端面试题——JaveScript

目录

1、作用域

2、全局变量

3、局部变量

4、全局变量和局部变量的区别

5、作用域链是什么,你是如何理解的

6、预解析是什么?

7、带var和不带var的区别

8、JS内存空间

9、深浅拷贝

10、闭包

11、防抖

12、节流

13、构造函数和普通函数的区别

14、事件流

15、this指向

16、改变this指向

17、ES5

18、继承

19、ES6的新特性

20、ES6 Class 类

21、Promise

22、原型链

23、async/await

24、cookie和localStorage以及sessionStorage的区别

25、跨域解决方案

26、前端性能优化方案

27、JS常见的兼容性问题


1、作用域

作用域就是它定义了变量的可访问范围,控制变量的可见性和生命周期。

  • 局部作用域:只能在函数内部访问它们
  • 全局作用域:网页的所有脚本和函数都能够访问它

2、全局变量

函数之外声明的变量

  • 全局变量的作用域是全局的:网页的所有脚本和函数都能够访问它;
  • 在全局作用域下声明的变量;
  • 特殊情况下,在函数内部不声明,但赋值的变量;

3、局部变量

在 JavaScript 函数中声明的变量,会成为函数的局部变量。

  • 局部变量的作用域是局部的:只能在函数内部访问它们;
  • 在函数内部声明的变量;
  • 函数的形参是局部变量

4、全局变量和局部变量的区别

全局变量:只有浏览器关闭时才会被销毁,因此比较占内存;

局部变量:只在函数内部使用,当其所在的代码被执行时,会被初始化;当代码运行结束后,就会被销毁,因此更节约内存空间;

5、作用域链是什么,你是如何理解的

作用域链的作用主要用于查找标识符, 一般情况下,当作用域需要查询变量的时候会在当前作用域中查找该值,如果没有在当前作用域中查找到该值,就会向上级作用域中查找,直到查到全局作用域,这个查询过程形成的链条就叫作用域链。

6、预解析是什么?

Js引擎运行js分为两步:预解析和代码执行

        预解析:js引擎会把js里面所有的var还有function提升到当前作用域的最前面

        代码执行:按照代码书写顺序从上到下执行

预解析分为变量预解析(变量提升)和函数预解析(函数提升)

        变量提升:把所有的变量声明提升到当前的作用域最前面,不提升赋值

        函数提升:把所有的函数声明提升到当前的作用域最前面,不提升函数

案例1:

var num = 10;
fun();

function fun () {
    console.log(num);  //undefined
    var num = 20;
}

案例2:

var num = 10;
function fun () {
    console.log(num);  //undefined
    var num = 20;
    console.log(num);  //20
}
fun();

案例3:

var a = 18;
fun1();

function fun1 () {
    var b = 9;
    console.log(a);  //undefined
    console.log(b);  //9

    var a = “123”;
}

案例4:

fun1();
console.log(a);  //Uncaught ReferenceError: a is not defined
console.log(b);  //9
console.log(c);  //9

function fun1 () {
    var a = b = c = 9;  //等同于var a=9;b=9;c=9 此时b c为全局变量
    console.log(a);  //9
    console.log(b);  //9
    console.log(c);  //9
}

7、带var和不带var的区别

在全局作用域下声明一个变量,也相当于给window全局对象设置了一个属性,变量的值就是属性值(局部作用域中声明的局部变量和window没关系)

console.log(a);
//=>undefined

console.log(window.a) ;
//=>undefinedconsole.log('a'in window); 
//=>TRUE 在变量提升阶段,在全局作用城中声明了一个交量A,
//=>此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,
//=>默认值UNDEFINED in:检测某个属性是否隶属于这个对象var a= 12;
//=>全局交量值修改,WIN的属性值也跟着修改console.log(a);
//=>全局交量A 12console.log(window.a);
//=>WINDOW的一个属性名A 12

a=13;
console.log(window.a) ;
//=>13

window.a = 14;
console.log(a);//=>14
//=>全局交量和WIN中的属性存在“映射机制”


//=>不加VAR的本质是WIN的属性
// console.log(a);//=>Uncaught ReferenceError: a is notdefined
console.log(window.a) ;//=>undefined

console.log('a' in window) ;
//=>false
// a = 12;//=>window.a=12

console.log(a);
//=>12 =>window的属性

console.log(windowa);//=>12

8、JS内存空间

JS内存空间分为栈,堆,池,队列,其中栈存放变量,基本类型数据与指向复杂类型数据的引用指针;堆存放复杂类型数据;池又称为常量池,用于存放常量;而队列在任务队列也会使用。

        栈内存:栈数据结构具备FILO(first in last out)先进后出的特性,较为经典的就是乒乓球盒结构,先放进去的乒乓球只能最后取出来。它用于存放js代码在执行过程中创建的所有上下文和基本类型数据

        堆内存:堆数据结构是一种无序的树状结构,同时它还满足key-value键值对的存储方式;一般用于存储引用类型的数据,需要注意的是由于引用类型的数据一般可以拓展,数据大小可变,所以存放在堆内存中;但对引用类型数据的引用地址是固定的,所以地址指向还是会存放在栈内存中。

        队列:队列具有FIFO(First In First Out)先进先出的特性,与栈内存不同的是,栈内存只存在一个出口用于数据进栈出栈;而队列有一个入口与一个出口,理解队列一个较为实际的例子就像我们排队取餐,先排队的永远能先取到餐。在js中使用队列较为突出的就是js执行机制中的event loop事件循环

        堆内存释放:将所有引用这个堆内存的对象或者函数都赋值为null(空指针),如果堆内存没有任何东西被占用,那么浏览器会在空闲的时候把它销毁(垃圾回收)

        栈内存释放

                全局作用域:只有页面关闭的时候全局作用域才会销毁;

                私有作用域:一般情况下函数执行时会形成一个新的私有作用域,当私有作用域中代码执行完成时,我们当前的作用域会主动的释放和销毁。特殊情况,当前私有作用域中部分内存被作用域以外的东西占用了,那么当前这个作用域就不能销毁了

9、深浅拷贝

浅拷贝:

浅拷贝即只复制对象的引用,所以副本最终也是指向父对象在堆内存中的对象,无论是    副本还是父对象修改这个对象,副本或者父对象都会因此发生同样的改变

深拷贝:

深拷贝则是直接复制父对象在堆内存中的对象,最终在堆内存中生成一个独立的,与父    对象无关的新对象。深拷贝的对象虽然与父对象无关,但是却与父对象一致。当深拷贝    完成之后,如果对父对象进行了改变,不会影响到深拷贝的副本,反之亦然

10、闭包

能够访问其他函数内部变量的函数;

作用:保护私有变量不受外界干扰;形成不销毁的栈内存,把一些值保存下来,方便后面调用;

        在所在作用域外被调用;

        函数嵌套函数;

        参数和变量不会被垃圾回收机制回收;

优点:

        延长了变量的声明周期 => 一个不会被销毁的函数执行空间(始终占用内存);

        可以在函数外部通过闭包函数访问函数内部的变量 => 一个不会被销毁的函数执行空间(始终占用内存)

缺点:

        占用内存空间,大量使用闭包会造成内存泄露;=>解决:释放内存 使用场景:定时器、回调、函数防抖

function fn(num) {
    var a = num
    return function (n) {
        a += n
        console.log(a)
    }
}

var f = fn(10)

f(20)    // 30 调用闭包函数
fn(30)(40)  // 70 调用原始函数
fn(40)(50)  // 90 调用原始函数
f(60)    // 90 调用闭包函数

11、防抖

防抖就是让代码在最后一次点击的时候去执行一次

防抖,顾名思义,防止抖动,以免把一次事件误认为多次,敲键盘就是一个每天都会接触到的防抖操作。

应用的场景:

        登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖

       调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖

        文本编辑器实时保存,当无任何更改操作一秒后进行保存

12、节流

在指定时间内只触发一次函数

节流,顾名思义,控制水的流量。控制事件发生的频率,如控制为1s发生一次,甚至1分钟发生一次。与服务端(server)及网关(gateway)控制的限流 (Rate Limit) 类似。

应用场景:

        scroll 事件,每隔一秒计算一次位置信息等 

        浏览器播放事件,每个一秒计算一次进度信息等 

        input 框实时搜索并发送请求展示下拉列表,没隔一秒发送一次请求 (也可做防抖)

13、构造函数和普通函数的区别

1)构造函数也是一个普通函数,创建方式和普通函数一样,但构造函数习惯上首字母大写;

2)构造函数和普通函数的区别在于:调用方式不一样。作用也不一样(构造函数用来新建实例对象);

3)调用方式不一样

        普通函数的调用方式:直接调用 person();

        构造函数的调用方式:需要使用new关键字来调用 new Person();

4)构造函数的函数名与类名相同:Person( ) 这个构造函数,Person 既是函数名,也是这个对象的类名‘;

5)内部用this 来构造属性和方法

6)构造函数的执行流程

        a)立刻在堆内存中创建一个新的对象

        b)将新建的对象设置为函数中的this

        c)逐个执行函数中的代码

        d)将新建的对象作为返回值

7)普通函数例子:因为没有返回值,所以为undefined

8)构造函数例子:构造函数会马上创建一个新对象,并将该新对象作为返回值返回

9)用instanceof 可以检查一个对象是否是一个类的实例,是则返回true;所有对象都是Object对象的后代,所以任何对象和Object做instanceof都会返回true

14、事件流

DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即DOM事件流。

DOM事件流分为三个阶段,分别为:

        捕获阶段:事件从Document节点自上而下向目标节点传播的阶段;

        目标阶段:真正的目标节点正在处理事件的阶段;

        冒泡阶段:事件从目标节点自上而下向Document节点传播的阶段

事件冒泡会从当前触发的事件目标一级一级往上传递,依次触发,直到document为止。

阻止冒泡:

// 原生js
if ( e && e.preventDefault ) {
e.stopPropagation();//非IE浏览器
} else { 
window.event.cancelBubble = true;
} //IE浏览器

// vue.js 
<div @click.stop="doSomething($event)">vue取消事件冒泡</div>

阻止默认事件

// 原生js
if ( e && e.preventDefault ) {
    e.preventDefault()//非IE浏览器
} else { 
    window.event.returnValue = false; 
} //IE浏览器

// vue.js
<div @click.prevent="doSomething($event)">vue阻止默认事件</div>

事件捕获会从document开始触发,一级一级往下传递,依次触发,直到真正事件目标为止。

事件委托:利用事件冒泡,把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。

15、this指向

一般情况下,this的最终指向的是那个调用它的对象。

全局作用域下或者普通函数中this指向全局对象window;

方法调用中调用者被this指向; 

构造函数中this指向构造函数的实例; 

箭头函数的this与声明的上下文相同;

16、改变this指向

call、apply、bind

相同点:

        都能改变this指向,第一个传递的参数都是this指向的对象

        三者都采用的后续传参的形式

不同点:

        call的传参是单个传递的,apply后续传参的是数组形式,bind没有规定,传递值和数组都可以

        call和apply函数的执行是直接执行的,而bind函数是返回一个函数,然后调用才会执行

17、ES5

1)JSON对象

        JSON.stringify(obj/arr): js对象(数组)转换为json对象(数组)

        JSON.parse(json): json对象(数组)转换为js对象(数组)

2)Array扩展

        Array.prototype.indexOf(value) : 得到值在数组中的第一个下标         Array.prototype.lastIndexOf(value) : 得到值在数组中的最后一个下标         Array.prototype.forEach(function(item, index){}) : 遍历数组         Array.prototype.map(function(item, index){}) : 遍历数组返回一个新的数组,返回加工之后的值

        Array.prototype.filter(function(item, index){}) : 遍历过滤出一个新的子数组, 返回条件为    true的值

3)Object扩展

        Object.create(prototype,[descriptors]):以指定对象为原型创建新的对象

        Object.defineProperties(object,descriptors):为指定对象定义扩展多个属性

4)函数的扩展

        Function.prototype.bind(obj):将函数内的this绑定为obj,并将函数返回

18、继承

ES5继承,通过构造函数+原型对象模拟继承,称之为组合继承。

借用构造函数继承父类型属性

核心原理:通过call()把父类型的this指向子类型的this。

function Father (uname, age){
    this.uname = uname;  // this=>Father实例对象
    this.age= age;
}

function Son (uname, age){
    Father.call(this,uname, age)   // this=>Son实例对象
}

利用原型对象继承方法

Son.prototype = new Father()

// 利用对象的形式改了原型对象,需要利用constructor指回原来的

Son.prototype.constructor = Son

19、ES6的新特性

可以使用let和const定义一个变量,都是块级作用域

模板字符串(倒引号)

解构赋值

For of循环,但是他不能循环对象

展开运算符(...)他可以展开数组和对象的多个元素

箭头函数

用class定义一个类

定义一个函数function

引入import

新增了this的方法call(),apply()

新增了数组的方法forEach(),map(),filter(),reduce()

Symbol用来做对象的key值

20、ES6 Class 类

Constructor方法:constructor方法是类的默认方法,创建类的实例化对象时被调用。

Extends:通过 extends 实现类的继承。

Super:子类 constructor 方法中必须有 super ,且必须出现在 this 之前;调用父类构造函数,只能出现在子类的构造函数;调用父类方法,,super 作为对象,在普通方法中,指向父类的原型对象,在静态方法中,指向父类。

class Father (){
    constructor (x, y){
        this.x = x;  
        this.y= y;
    }

    say(){
        return '我是爸爸'
    }

    sum(){
        console.log(this.x + this.y)
    }
}

class Son extends Father (){

    constructor (x, y){
        super(x, y)  //调用父类构造函数
        this.x = x;  
        this.y= y;
    }

    say(){
        return super.say()+'的儿子'  //调用父类普通方法
    }

}

21、Promise

1)Promise就是异步操作的同步代码,是ES6新增的一个构造函数。构造函数内部的代码是立即执行的。

2)Promise是解决异步以及回调地狱的问题(回调函数:回调函数就是一个被作为参数传递的函数)(嵌套太多导致代码维护成本增加,代码可读性降低的情况)

3)promise的基本使⽤

        a)通过new promise创建⼀个promise对象,⾥⾯有⼀个参数,参数是⼀个回调函数,回调函数中 有2个参数,resolve,reject :

        b)完成了(resolve)当异步执⾏成功的时候调⽤的⽅法

        c)拒绝了(reject)当异步失败的时候调⽤的⽅法

4)resolved是一个函数,直接调用会把本次的状态改为成功,rejected也是一个函数直接调用会讲本次promise的状态改为失败,成功后执行.then里面的函数,失败后执行.catch里面的函数;每次return出来的都是一个新的promise。原因也是因为状态不可变。

5)优点:Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。

6)缺点:无法取消 Promise,错误需要通过回调函数捕获。

7)promise过程:

        a)生成Promise实例

        b)执行一系列同步操作

        c)使用resolve函数将异步操作的结果传递出去, reject函数传递异步操作的错误

        用then方法分别指定Resolve状态和Reject状态的函数,then方法返回一个新的Promise    实例,因此可以采用链式写法

22、原型链

实例对象上都会有一个隐式原型属性(__proto__), 它指向的就是原型对象, 而原型对象也有__proto__属性指向它的原型对象

作用:原型链用于查找对象的属性

为什么__proto__指向的是原型对象

        构造函数对象上有显式原型属性(prototype), 它指向的就是原型对象    

        实例对象的__proto__属性被赋值为构造函数的prototype属性值

23、async/await

作用:

        简化pormise的使用(不用再使用then()来指定成功或失败的回调函数)    

        以同步编码的方式实现异步流程(没有回调函数) 

哪里使用await:   

        在返回promise对象的表达式左侧, 为了直接得到异步返回的结果, 而不是promsie对象

哪里使用async:

        使用了await的函数定义左侧

24、cookie和localStorage以及sessionStorage的区别

1)存储大小:cookie数据大小不能超过4k,sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

2)有效时间:localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除;cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

3)数据与服务器之间的交互方式,cookie的数据会自动的传递到服务器,服务器端也可以写cookie到客户端;sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存

25、跨域解决方案

一个域下的文档或脚本试图请求另一个域下的资源

同源策略:协议、域名、端口相同

a、通过jsonp跨域

b、cors跨域资源共享

26、前端性能优化方案

1)在js中尽量减少闭包的使用(闭包不会释放栈内存)

        a、循环进行事件绑定时,尽可能使用自定义属性,而不用创建闭包来存储信息。

        b、在最外层形成一个闭包,把一些后期需要的公共信息进行存储,而不是每一个方法都    创建一个闭包(例如单例模式)。

        c、尽可能手动释放掉不需要的内存。

2)进行js和css文件的合并,减少http请求的次数,尽可能将文件进行压缩,减少请求资源的大小。

         a、webpack这种自动化构建工具,可以帮我们实现代码的合并和压缩(工程化开发)

        b、在移动端开发过程中,如果代码不是很多,直接将css和js写html中。

3)尽量使用字体图标和SVG图标,来代替传统的PNG等格式的图片(字体图标等是矢量图)

4)减少对DOM的操作(主要是减少DOM的重绘和回流(重排))

        a)关于重排的分离读写(浏览器会将连续dom操作一起缓存起来一起操作)

        b)使用文档碎片或者字符串做数据绑定(DOM的动态创建)

5)js避免“嵌套循环”(会额外增加很多次循环次数)和“死循环”(浏览器会死机)

6)采用图片“懒加载”,加快第一次加载的速度,实际并没有减少请求次数 步骤:开始加载页面是,所有的真实图片都不去发送请求,而是给一张占位的背景图,    当页面加载完后,并且图片出现在可是区域再去做图片加载。

7)利用浏览器和服务器端的缓存技术(304缓存),把一些不经常变更的资源进行缓存,例如js和css文件。目的就是减少请求大小。

8)尽可能使用事件委托来处理绑定的操作,减少DOM的频繁操作。

27、JS常见的兼容性问题

滚动条:

document.documentElement.scrollTop||document.body.scrollTop

获取样式兼容

function getStyle(dom, styleName){
    return dom.currentStyle?
    dom.currentStyle[styleName] :getComputedStyle(dom)[styleName];
}

网页可视区域兼容

window.innerHeight || document.documentElement.clientHeight

window.innerWidth || document.documentElement.clientWidth

事件对象兼容

e  = e || window.event

阻止事件冒泡兼容

event.stopPropagation? event.stopPropagation():event.cancelBubble=true

阻止默认行为兼容

evt.preventDefault?evt.preventDefault():evt.returnValue=false

事件监听兼容

        标准浏览器的写法addEventListener()和IE的写法attachEvent()

事件目标对象兼容

        var src = event.target || event.srcElement

以上如有错误,敬请指正。

猜你喜欢

转载自blog.csdn.net/m0_65894854/article/details/130866461