目录
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
以上如有错误,敬请指正。