面试总结 ES6新增+JS高频

497 阅读30分钟

es6部i分面试题

1、 ES6 新增特性

新增了块级作用域(let,const)

提供了定义类的语法糖(class)

新增了一种基本数据类型(Symbol)

新增了变量的解构赋值

函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数

数组新增了一些 API,如 isArray / from / of 方法;数组实例新增了entries(),keys() 和 values() 等方法

对象和数组新增了扩展运算符

ES6 新增了模块化(import/export)

ES6 新增了 Set 和 Map 数据结构

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例

ES6 新增了生成器(Generator)和遍历器(Iterator)

2、require与import的区别和使用(CommonJS规范和es6规范)

1、import是ES6中的语法标准也是用来加载模块文件的,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块,export可以有多个,export default只能有一个。

2、require 定义模块:module变量代表当前模块,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。

require与import的区别

1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;

2,require是运行时加载,import是编译时加载;

3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;

4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;

5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;

6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。

3、箭头函数

js中我们在调⽤函数的时候经常会遇到this作⽤域的问题,这个时候ES6给我们提箭头函数。

1、 箭头函数是匿名函数不能作为构造函数,不能使用new

2、 箭头函数不绑定arguments,取而代之用rest参数…解决,

3、 this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this

4、 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响.

5、 箭头函数没有prototype(原型),所以箭头函数本身没有this

6、 箭头函数不能当做Generator函数,不能使用yield关键字、

7、 写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁

8、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。

箭头函数和普通函数的区别

(1)箭头函数比普通函数更加简洁
如果没有参数,就直接写一个空括号即可
如果只有一个参数,可以省去参数括号
如果有多个参数,用逗号分割
如果函数体的返回值只有一句,可以省略大括号
如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常用的就是调用一个函数:
let fn = () => void doesNotReturn()

(2) 箭头函数没有自己的this
箭头函数不会创建自己的this,所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中的this的指向在它在定义时一家确定了,之后不会改变。

(3)箭头函数继承来的this指向永远不会改变

(4) call()、apply()、bind()等方法不能改变箭头函数中的this指向 

(5) 箭头函数不能作为构造函数使用

(6) 箭头函数没有自己的arguments

(7) 箭头函数没有prototype

(8) 箭头函数不能用作Generator函数,不能使用yeild关键字

4、简述 let const var 的区别 以及使用场景

var let 是用来声明变量的,而const是声明常量的 var

var

1.var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined ​
2、一个变量可多次声明,后面的声明会覆盖前面的声明 ​
3、在函数中使用var声明变量的时候,该变量是局部的作用域只在函数内部,而如果在函数外部使用 var,该变量是全局的

let

1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区

2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面

3、let不允许在相同作用域中重复声明,不同作用域有重复声明不会报错

const

1、const声明一个只读的常量,声明后,值就不能改变

2、let和const在同一作用域不允许重复声明变量const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种 引用类型,内存地址不能修改,可以修改里面的值。

3、let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错

4、能用const的情况下尽量使用const,大多数情况使用let,避免使用var。

const > let > var

const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生

5、map和forEach的区别

相同点

都是循环遍历数组中的每一项 forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个 匿名函数中的this都是指向window 只能遍历数组

注意:forEach对于空数组是不会调用回调函数的。

不同点

map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。) map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined)

6、promise的解释(重点)

www.jianshu.com/p/84ef1b48f…

1、Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱的问题,自己身上有all、reject、resolve、race 等方法,原型上有then、catch等方法。

2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列

3、promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败

4、Promise 对象状态改变:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了

5、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then 和 catch ,会被then的第二个参数 或 catch所捕获

promise 的 then 为什么可以支持链式调用 promise 的then会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用

promise基本规则:

1.首先Promise构造函数会立即执行,而Promise.then()内部的代码在当次事件循环的结尾立即执行(微任务)

2.promise的状态一旦由等待pending变为成功fulfilled或者失败rejected。那么当前promise被标记为完成,后面则不会再次改变该状态。

3. resolve函数和reject函数都将当前Promise状态改为完成,并将异步结果,或者错误结果当做参数返回。

4. Promise.resolve(value)

返回一个状态由给定 value 决定的 Promise 对象。如果该值是 thenable(即,带有 then 方法的对象),返回的 Promise 对象的最终状态由 then 方法执行决定;否则的话(该 value 为空,基本类型或者不带 then 方法的对象),返回的 Promise 对象状态为 fulfilled,并且将该 value 传递给对应的 then 方法。通常而言,如果你不知道一个值是否是 Promise 对象,使用 Promise.resolve(value) 来返回一个 Promise 对象,这样就能将该 value 以 Promise 对象形式使用。

5. Promise.all(iterable)和Promise.race(iterable)

简单理解,这2个函数,是将接收到的promise列表的结果返回,区别是,all是等待所有的promise都触发成功了,才会返回,而arce有一个成功了就会返回结果。其中任何一个promise执行失败了,都会直接返回失败的结果。

6. promise对象的构造函数只会调用一次,then方法和catch方法都能多次调用,但一旦有了确定的结果,再次调用就会直接返回结果。

6、async、await的原理

Async 和 await 是一种同步的写法,但还是异步的操作,两个必须配合一起使用

函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。

await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是promise则会等待promaise 返回结果,接普通函数直接进行链式调用.

await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法

如果await后面不是Promise对象, 就直接返回对应的值,只能在async函数中出现, 普通函数直接使用会报错

await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行

使用场景:

我在项目中: 需求:执行第一步,将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第 二步操作的接口调用,达到异步操作。

7、 generator 有了解过吗?(async和await的爸爸)

Generator 生成器 也是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同 function *(){}

Generator 函数是一个状态机,封装了多个内部状态,除了状态机,还是一个遍历器对象生成函数。

Generator 是分段执行的, yield (又得)可暂停,next方法可启动。每次返回的是yield后的表达式结果,这使得Generator函数非常适合将异步任务同步化

Generator 并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator`接口…)

Generator函数返回Iterator对象,因此我们还可以通过for...of进行遍历,原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for...of进行遍历了

promise、Generator、async/await进行比较:

promise和async/await是专门用于处理异步操作的 Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…) promise编写代码相比Generator、async更为复杂化,且可读性也稍差 Generator、async需要与promise对象搭配处理异步情况 async实质是Generator的语法糖,相当于会自动执行Generator函数 async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案

8、解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值

常见的几种方式有

1.默认值

2.交换变量

3.将剩余数组赋给一个变量

结构数组和对象字符串区别

对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定;

而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象.

我在项目中:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。

9、 for...in 迭代和 for...of 有什么区别

1、推荐在循环对象属性的时候,使用 for...in,在遍历数组的时候的时候使用for...of。

2、for in遍历的是数组的索引,而for of遍历的是数组元素值

3、for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用

4、for...in 便利顺序以数字为先,无法遍历 symbol 属性 可以遍历到公有中可枚举的

5、从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。

10、js构造函数的静态成员和实例成员

js的构造函数(类)上可以添加一些成员,可以在构造函数内部的this上添加,可以在构造函数本身上添加,通过这两种方式添加的成员,就分别称为实例成员和静态成员

实例成员:构造函数中this上添加的成员

静态成员:构造函数本身上添加的成员

实例成员,只能由实例化的对象来访问

静态成员,只能由构造函数本身来访问 实例化对象的proto指向构造函数的prototype属性指向的对象,实例化的对象可以访问到它后者身上的成员

构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?

新建了一个Object对象

修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数

为Object对象添加了一个proto属性,是其指向构造函数的prototype属性

将这个Object对象返回出去

11、set和map数据结构的区别,常用的属性和方法?

image.png

set数据的特点是数据是唯一的

size: 返回Set实例的成员总数。 add(value):添加某个值,返回 Set 结构本身。 delete(value):删除某个值。 clear():清除所有成员,没有返回值。

Set的不重复性

传入的数组中有重复项,会自动去重 const set2 = new Set([1, 2, '123', 3, 3, '123'])

Set的不重复性中,要注意引用数据类型和NaN 两个对象都是不用的指针,所以没法去重 const set1 = new Set([1, {name: '孙志豪'}, 2, {name: '孙志豪'}])

如果是两个对象是同一指针,则能去重 const obj = {name: '我们一样'} const set2 = new Set([1, obj, 2, obj])

NaN !== NaN,NaN是自身不等于自身的,但是在Set中他还是会被去重 const set = new Set([1, NaN, 1, NaN])

map数据结构

Map对比object最大的好处就是,key不受类型限制

定义map const map1 = new Map()

新增键值对 使用 set(key, value) map1.set(true, 1)

判断map是否含有某个key 使用 has(key) console.log(map1.has('哈哈'))

获取map中某个key对应的value console.log(map1.get(true))

删除map中某个键值对 使用 delete(key) map1.delete('哈哈')

定义map,也可传入键值对数组集合 const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']]) console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }

12、proxy 的理解(代理器)

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

13、Es6中新的数据类型symbol

symbol 是es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL 值是由 SYMBOL函数生成,也就是说现在我们定义对象的属性名字可以是原有的字符串 也可以是 symbol 类型的,symbol 可以保证不与其他属性名冲突,减少了bug的产生,

如果那 symbol 对比的话 就是会返回 false

symbol 他是一个原始类型的值就,不可以使用 new 关键字,symbol不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型

symbol 不能用来四则运算,否则会报错,只能用显示的方式转为字符串

symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆

14、iterator == iteration (遍历器的概念)

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

15、Object.assign(招银问了)

Object.assign可以实现对象的合并。它的语法是这样的: Object.assign(target, ...sources)

Object.assign会将source里面的可枚举属性复制到target。如果和target的已有属性重名,则会覆盖。同时后续的source会覆盖前面的source的同名属性。

Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题

Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。

那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。

要将一个类数组对象转换为一个真正的数组,必须具备以下条件:

1、该类数组对象必须具有 length 属性,用于指定数组的长度。如果没有 length 属性,那么转换后的数组是一个空数组。

2、该类数组对象的属性名必须为数值型或字符串型的数字

16、谈谈你对模块化开发的理解?

我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。

由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污染,并且模块间没有联系。

后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所有的所有的模块成员,外部代码可以修改内部属性的值。

现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。

17、js 的几种模块规范?

js 中现在比较成熟的有四种模块加载方案:

第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。

第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。

第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。

第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。

javascript原型与原型链

原型

每个函数都有一个prototype属性,被称为显示原型

每个实例对象都会有_ proto _属性,其被称为隐式原型

每一个实例对象的隐式原型_ proto _属性指向自身构造函数的显式原型prototype

每个prototype原型都有一个constructor属性,指向它关联的构造函数。

原型链

获取对象属性时,如果对象本身没有这个属性,那就会去他的原型__proto__上去找,如果还查不到,就去找原型的原型,一直找到最 顶层(Object.prototype)为止。Object.prototype对象也有proto属性值为null。链式查找机制叫原型链。

javascript 创建对象的几种方式

1、我们一般使用字面量的形式直接创建对象

(1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。

(2)第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。

(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。

(4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。

(5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。

(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,

ES6类的私有属性_ES6中class私有属性和私有方法

blog.csdn.net/weixin_3985…

什么是设计模式?

概念:

设计模式是一套被反复使用的代码,设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式让代码变得工程化,设计模式是软件工程的基石。

1、js工厂模式,去做同样的事情,实现同样的效果,解决多个相似的问题这时候需要使用工厂模式

2、发布订阅模式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

3、单例模式 单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点

EventLoop是什么(事件循环)

事件循环:javascript的执行规则里面有个事件循环Event Loot的规则,在事件循环中,异步事件会放到异步队列里面,但是异步队列里面又分为宏任务和微任务,浏览器端的宏任务一般有:script标签,setTimeout,setInterval,setImmediate,requestAnimationFrame。微任务有:MutationObserver,Promise.then catch finally。宏任务会阻塞浏览器的渲染进程,微任务会在宏任务结束后立即执行,在渲染之前。 www.ruanyifeng.com/blog/2013/1…

面向过程,面向对象,面向过程和面向对象的优缺点

一、面向过程:面向过程就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可。

二、面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程面向过程:

优点:性能上它是优于面向对象的,因为类在调用的时候需要实例化,开销过大。

缺点:不易维护、复用、扩展

用途:单片机、嵌入式开发、Linux/Unix等对性能要求较高的地方

面向对象:

面向对象有三大特性:封装,继承,多态。

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 。

缺点:性能比面向过程低

js和java的区别

1、Java是面向对象的语言,JavaScript是脚本语言,是基于对象和事件驱动的语言。

2、Java的源代码在执行之前必须经过编译,而JavaScript的代码不需要,可以由浏览器直接解释执行。

3、java主要在服务端运行;javascript主要运行在客户端浏览器中。

4、JavaScript是动态类型语言;而Java是静态类型语言。java在定义了一个数组的长度以后就不能再改变了,但是javascript却可以。

5、JavaScript是弱类型的,即在使用前不需要声明,而是浏览器解释器在运行时检查数据类型;Java属于强类型,即所有变量在编译前必须作声明;

6、JavaScript 的面向对象是基于原型的(prototype-based)实现的,Java 是基于类(class-based)的;

7、Java的语法规则比JavaScript要严格的多,功能要强大的多。

8、java语言的代码是一种HTML没有关系的语言;javascript语言的代码是一种文本字符格式,可以直接嵌入HTML文档中,并且可动态加载。

this指向的问题(高频)

在全局的环境下this是指向window 的

普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window

在对象里调用的this,指向调用函数的那个对象,

在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象

方法中的this谁调用就指向谁。

箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this

this指向?

答:①如果作为对象的实例方法被调用就是指向这个对象。②如果在全局环境中调用就是指向window。③可以使用call(),apply()方法改变this的指向。④用new调用的函数中的this指向刚创建的这个对象。

call、apply、bind封装与区别

都是来改变this指向和函数的调⽤,实际上call与apply的功能是相同的,只是两者的传参方式不一样,

call⽅法跟的是⼀个参数列表,

apply跟⼀个 数组作为参数,call⽅法和apply使⽤后就直接调⽤

bind 传参后不会立即执行,而是返回一个改变了this指向的函数,这个函数可以继续传参,且执行,需要类似于bind()()两个括号才能调⽤。

call 的性能要比apply好一点(尤其是当函数传递参数超过3个的时候)后期开发 call 多多一点

call 用扩展运算符就可以吧 apply 来代替了

new

使用面向对象编程时,new关键字做了什么?

新建了一个Object对象

修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数

为Object对象添加了一个proto属性,是其指向构造函数的prototype属性

将这个Object对象返回出去

new的原理

new实际上是在堆内存中开辟一个空间。 ①创建一个空对象,构造函数中的this指向这个空对象;

②这个新对象被执行[ [ 原型 ] ]连接;

③执行构造函数方法,属性和方法被添加到this引用的对象中;

④如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。

自己理解的new:

new实际上是在堆内存中开辟一个新的空间。首先创建一个空对象obj,然后呢,把这个空对象的原型(proto)和构造函数的原型对象 (constructor.prototype)连接(等于);然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象,那就返回这个对象,如果不是,那就返回我们创建的对象。

如果用new调用函数,而这个函数中有return,那它return出来的是什么?

答:①如果没有写return就是返回这个新创建的对象。②如果返回值是非对象类型的数据(比如写了个return 0;),那么还是正常返回这个新创建的对象。③如果写了对象类型的返回值,那么就返回这个显式声明的对象。

用new调用函数时怎么把新创建的对象与构造函数的原型联系起来的?(或者说实例对象、原型对象和构造函数三者的关系是怎样的?)

答:实例对象与构造函数没有直接关系,原型对象作为纽带将它们联系起来。实例对象的_proto_指向原型对象,原型对象的constructor指向构造函数,构造函数上的prototype指向原型对象。

constructor,proto,prototype的三角关系。

构造函数的prototype指向原型对象

实例对象的proto指向构造函数的prototype所指向原型对象

原型对象的constructor指向构造函数

闭包

1、闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。

3、闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理

4、闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

5、为什么要用闭包:使用场景 : 防抖、节流、函数套函数避免全局污染

闭包原理

函数执行分成两个阶段(预编译阶段和执行阶段)。

1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,如果已存在“闭包”,则只需要增加对应属性值即可。

2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量

利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁, 但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。

什么是深拷贝,浅拷贝,浅拷贝 赋值的区别,如何实现

深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。

1.浅拷贝:

将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存

如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,

2.深拷贝:

创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象

3、赋值:

当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象

防抖节流

防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。