前端面经-js篇

文章目录

1、闭包,内存泄露

特性

  • 函数嵌套函数
  • 函数内部可以引用函数外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收

优点

  • 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突
  • 在内存中维持一个变量,可以做缓存(但使用多了同时也是一种缺点,消耗内存
  • 匿名执行函数可以减少内存消耗

缺点

  • 其中一点上面已经有体现了 ,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄露,解决方法是可以在使用完变量后 手动为他赋值为null;
  • 其次由于闭包涉及跨域访问,所以会导致性能有所流失,我们可以把跨域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的

2、作用域链,变量提升

在js中,作用域分为全局作用域和函数作用域,ES6新增块级作用域

作用域链

一般情况下,变量取值到创建这个变量的函数的作用域中取值。

但是如果在当前作用域中取不到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

作用域链这么形成

作用域和执行上下文:许多人误认为作用域和执行上下文的概念,误认为他们是相同的概念,事实并非如此。

我们知道js属于解释性语言,js的执行分为:解释和执行两个阶段 。这两个阶段所做的事情并不一样。

解释阶段

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是 this 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。

一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。

EC:函数的执行环境AO:(Actived Object)活动对象: 保存函数调用时的局部变量 其实AO对象就是函数作用域对象

变量提升

var 声明的变量会提升到当前作用域的最顶端
function 声明的函数也会提升到当前作用域的最顶端

3、let,var,const

  • let没有变量提升
  • let变量不能重复声明
  • ES6可以用let定义块级作用域变量

不同

  • var定义的变量,没有块的概念,可以跨块访问,不能跨函数访问。
  • let定义的变量,有块的概念,不能跨块访问,也不能跨函数访问。
  • const用来定义常量,有块的概念,不能跨块访问,而且不能修改。

什么是暂时性死区
如果区块(花括号)中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量,就会报错,所以在代码块内,使用let命令声明变量之前,该变量都是不可用的
这被称为暂时性死区

4、常用数组(reduce),字符串方法

数组

会改变原有数组

会改变原有数组,不会返回新的数组或值:
pop()—删除数组的最后一个元素并返回删除的元素。

push()—向数组的末尾添加一个或更多元素,并返回新的长度。

shift()—删除并返回数组的第一个元素。

unshift()—向数组的开头添加一个或更多元素,并返回新的长度。

reverse()—反转数组的元素顺序。

sort()—对数组的元素进行排序。

splice()—用于插入、删除或替换数组的元素。

不会改变数组,并会返回新的数组

concat()—连接两个或更多的数组,并返回结果。

every()—检测数组元素的每个元素是否都符合条件。

some()—检测数组元素中是否有元素符合指定条件。

filter()—检测数组元素,并返回符合条件所有元素的数组。

indexOf()—搜索数组中的元素,并返回它所在的位置。

join()—把数组的所有元素放入一个字符串。

toString()—把数组转换为字符串,并返回结果。
lastIndexOf()—返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。

map()—通过指定函数处理数组的每个元素,并返回处理后的数组。

slice()—选取数组的的一部分,并返回一个新数组。

valueOf()—返回数组对象的原始值。

其他

reduce 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

reducer 函数接收4个参数:
Accumulator (acc) (累计器)
Current Value (cur) (当前值)
Current Index (idx) (当前索引)
Source Array (src) (源数组)
您的 reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

字符串

split、slice、substr、indexOf、charAt、trim、toLocalUpperCase、toLocaleLowerCase

5、map和foreach区别

在这里插入图片描述
可以通过抛出异常来强制终止迭代,但这种方法会导致代码难以维护:

6、js事件循环机制,宏任务微任务,async/await,读代码运行顺序

js事件循环机制推文

js异步任务安装准确的划分,应该将任务分为:

宏任务:setTimeout、setInterval( 按照指定的周期(以毫秒计)来调用函数或计算表达式。)
微任务:列如promise.then方法。注意new Promise()的时候是同步,立即执行。

所以针对这种机制,js的事件循环机制应该是这样的

注意:现在有三个队列:同步队列(也称执行栈)、宏任务队列、微任务队列

  • 遇到同步代码,依次推入同步队列并执行
  • 当遇到setTimeout、setInterval,会被推到宏任务队列
  • 如果遇到.then,会被当做微任务,被推入微任务队列
  • 同步队列执行完毕,然后去微任务队列取任务,直到微任务队列清空。然后去检查宏任务队列,去宏队列取任务,并且每一个宏任务执行完毕都会去微任务队列跑一遍,看看有没有新的微任务,有的话先把微任务清空,这样依次循环。
setTimeout(function(){
   
	console.log(1)
},0)

new Promise(function(resolve,reject){
   
	console.log(2)
	resolve()
}).then((res)=>{
   
	console.log(3)
})
console.log(4)
//执行结果是 2 4 3 1

async

当我们在函数前使用async的时候,使得该函数返回的是一个Promise对象

async function test(){
   
	return 1 //async的函数会帮我们隐式的使用Promise。resolve(1)
}
//等价于下面的代码
function test(){
   
	return new Promise(function(resolve,reject){
   
		resolve(1)
	})
}

可见async只是帮助我们返回一个Promise而已

await

await表示等待,是右侧【表达式】的结果,这个表达式的结果结算可以是Promise对象的值或者一个函数的值。并且只能在带有async的内部使用
使用await时,会从右往左执行,当遇到await时候,会阻塞函数内部处于他后面的代码。去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余代码,并且当await执行完毕之后,会先处理微任务队列的代码。

	async function async1() {
   
            console.log( 'async1 start' )
            await async2()
            console.log( 'async1 end' )
        }
        async function async2() {
   
            console.log( 'async2' )
        }
        console.log( 'script start' )
        setTimeout( function () {
   
            console.log( 'setTimeout' )
        }, 0 )
        async1();
        new Promise( function ( resolve ) {
   
            console.log( 'promise1' )
            resolve();
        } ).then( function () {
   
            console.log( 'promise2' )
        } )
        console.log( 'script end' )

在这里插入图片描述

7、原型与原型链

原型:每一个对象都与另一个对象相关联,那个关联的对象就称为原型
原型链:每一个对象,都有一个原型对象与之关联,这个原型对象它也是一个普通对象,这个普通对象也有自己的原型对象,这样层层递进,就形成了一个链条,这个链条就是原型链。通过原型链可以实现JS的继承,把父类的原型对象赋值给子类的原型,这样子类实例就可以访问父类原型上的方法了
原型链的终点是null

8、对象继承方法

36个手写问题

  • 原型链继承
function Animal() {
   
    this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
   
    return this.colors
}
function Dog() {
   }
Dog.prototype =  new Animal()

let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors)  // ['black', 'white', 'brown']

原型链继承存在的问题:

问题1:原型中包含的引用类型属性将被所有实例共享;
问题2:子类在实例化的时候不能给父类构造函数传参;

  • 构造继承
function Animal(name) {
   
    this.name = name
    this.getName = function() {
   
        return this.name
    }
}
function Dog(name) {
   
    Animal.call(this, name)
}
Dog.prototype =  new Animal()

借用构造函数实现继承解决了原型链继承的 2 个问题:引用类型共享问题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类实例都会创建一遍方法。

  • class实现继承
class Animal {
   
    constructor(name) {
   
        this.name = name
    } 
    getName() {
   
        return this.name
    }
}
class Dog extends Animal {
   
    constructor(name, age) {
   
        super(name)
        this.age = age
    }
}
  • 组合继承

组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

function Animal(name) {
   
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
   
    return this.name
}
function Dog(name, age) {
   
    Animal.call(this, name)
    this.age = age
}
Dog.prototype =  new Animal()
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2) 
// { name: "哈赤", colors: ["black", "white"], age: 1 }

缺点:两次调用父类构造函数,数据冗余

寄生式组合继承

function Animal(name) {
   
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值