字节跳动一面总结
Updated:
1. let,const 和 var
var
如果使用关键字 var 声明一个变量,那么这个变量就属于当前函数作用域
如果声明是发生在任何函数的顶层声明,那么这个变量就属于全局作用域。
let
ES6新增,用于声明变量,用法和 var 类似,但是所声明的变量只在let 命令所在的代码块内有效
举个例子:
- 对于var:
1
2
3
4
5
6
7var a=[]
for(var i=0;i<10;i++){
a[i] = function(){
console.log(i)
}
}
a[6](); //10
在这个代码中,变量i 是var声明的,在全局范围内都有效,所以全局只有一个变量 i,每一次循环,变量i 的值都会发生改变,而循环内,被赋给数组a 的函数内部console.log(i) 中的i 指向全局的i 。也就是说,所有数组a的成员中的i 所指向的都是同一个i ,导致运行时最后输出的是最后一轮的i 值
- 对于 let
1
2
3
4
5
6
7var a=[]
for(let i=0;i<10;i++){
a[i] = function(){
console.log(i)
}
}
a[6](); //10
在这个代码中,变量 i 是let声明的,当前的i 只在本轮循环内有效,所以每一次循环的i 其实都是一个新的变量
问题1
如果每一轮循环的变量 i都是重新声明的,那它怎么知道上一轮循环的值从而计算出本轮循环的值?
这是因为js 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算
问题2
for 循环有一个特别之处,就是设置循环变量的那部分其实是一个父作用域,而循环体内部是一个单独的子作用域。标明函数内部的变量 i和循环变量i不在同一个作用域,而是各有各自单独的作用域。
区别:
- 不存在变量提升
1
2
3
4
5
6
7// var
console.log(foo)//undefined
var foo = 2
// let
console.log(bar)
let bar = 2; //ReferenceError
let 改变了语法行为,它所声明的变量一定要在声明后使用,否则就会报错
- 暂时性死区
本质:
只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现时,才可以获取和使用该变量
在这里我涉及一点也是面试官问到我的问题:有关作用域,
- 对于var而言,只要进入该作用域,所要使用的变量就已经存在,会被赋值为undefined,可以使用和和获取
- 而对于let而言,只要进入该作用域,所要使用的变量也会存在,但是不能使用和获取
(我想,这应该是面试官想听见的)
只要块级作用域里内存在let命令,它所声明的变量就绑定这个区域,不再受外部的影响1
2
3
4
5var tmp = 123
if(true){
tmp = 123 //ReferenceError
let tmp
}
注意:
ES6规定,如果区块中存在let 和const 这两个命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。
- 不允许重复声明
let 不允许在相同作用域内重复声明一个变量1
2
3
4
5
6
7
8
9
10//Error
function(){
var a = 15;
let a = 2;
}
//Error
function() {
let a = 15;
let a = 8;
}
const
- 声明一个只读的常量,一旦声明,常量的值就不可以改变。
- const一旦声明常量,就必须立即初始化,不能留到以后赋值
- const 命令声明的常量作用域与let相同,只在声明所在的块级作用域有效,不会变量提升,同样存在暂时性死区,只能在声明后使用
本质
实际上保证的并不是变量的值不得改动,二是变量指向的那个内存地址不得改动;
const 只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,这不能控制
顶层对象的属性
顶层对象
- 在浏览器中指的是windows对象
- 在node中指的是global对象
- ES5中,顶层对象与全局变量是等价的
- var命令和 function 命令生成的全局变量是顶层对象的属性
- let,const,class 命令声明的全局变量不属于顶层对象的属性
let | const | var |
---|---|---|
变量 | 常量 | 常量 |
不存在变量提升 | 不存在变量提升 | 存在变量提升(undefined) |
暂时性死区 | 暂时性死区 | 不存在暂时性死区 |
不允许重复赋值 | 不允许重复赋值 | 允许重复赋值 |
声明后允许不赋值(undefined) | 声明后必须初始化 | 声明后允许不赋值(undefined) |
2. mvc 和 mvvm
mvc = Model View Controller
模型 - 视图 - 控制器
- model 和 view 永远不能相互通信,只能通过 controller 传递
- controller 可以直接与 model 对话(读写调用model),model 通过notification 和 kvo 机制与controller 间接通信
- Controller可以直接与View对话,通过outlet,直接操作View,outlet直接对应到View中的控件,view 通过action向controller报告事件的发生(比如用户touch我了)。controller是view的直接数据源(数据很可能是controller 从model 中取得并经过加工了)。controller 是view 的代理。
缺点
在通常的开发中,除了简单的model,view 以外的所有部分都被放在了controller中,controller 负责显示界面,响应用户的操作,网络请求以及与model交互,这就导致了controller
- 逻辑复杂,难以维护
- 和view 紧耦合,无法测试
mvvm = model - viewmodel -view
一个 MVC 的增强版,我们正式连接了视图和控制器,并将表示逻辑从 Controller 移出放到一个新的对象里,即 View Model。MVVM 听起来很复杂,但它本质上就是一个精心优化的 MVC 架构
- model: 数据的拥有者,实现具体的业务逻辑
- ViewModel: 属于view+model,是一个放置用户输入验证逻辑,视图显示验证,发起网络等其他的地方。换句话说,就是把原来的View Controller 层的业务逻辑和页面逻辑等剥离出来放到viewModel层
- view层:就是view层和viewController层,他的任务就是从view层获得数据,然后显示
mvvm的优点
- mvvm降低了一个视图控制器的复杂性
- mvvm与现有的mvc架构兼容
- mvvm使程序更容易测试
- mvvm适合使用绑定机制
mvvm 采用数据双向绑定,v的变动直接反应在vm上,m的变化也直接反应在vm上
数据双向绑定
文章详情:https://github.com/DMQ/mvvm
实现双向绑定的做法
发布者- 订阅模式(backbone.js)
一般通过sub,pub的方式实现数据和视图的绑定监视,更新数据的方式通常是vm.set('property',value)
我们更希望的是通过vm.set('property',value)
这种方式更新数据,同时更新视图、脏值检查(angular.js)
angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,angular只有在指定的事件触发时进入脏值检测(最简单的方式就是通过setInterval()
定时轮询检测数据变动)- 数据劫持(vue.js)
vue.js是通过采用 数据劫持 的方式结合 发布者-订阅者 的方式,通过object.definePrpperty()
来劫持各个属性的setter
,getter
在数据变动时发布消息给订阅者,触发相应的监听回调
vue是什么?
vue是一个构建用户界面的框架库,目标是通过尽可能简单的api 实现相应的数据绑定和组合的视图集合,vue自身不是一个全能的框架,核心只是关心视图层。
3. js作用域和闭包
作用域
记得之前写过一篇作用域的文章
https://blog.csdn.net/Welkin_qing/article/details/80673682
先来介绍三个职位:
- 引擎: 从头到尾,负责整个js 程序的编译及执行过程
- 编译器: 引擎的好朋友之一,负责语法分析及代码生成等脏活和累活
- 作用域: 引擎的另一位好朋友,负责收集并维护由所有变量(声明的标识符)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些变量的访问权限
作用域分为两个步骤:1. 声明 2. 查找
首先编译器会在当前作用域声明一个变量(如果之前没声明过),其次,在运行时引擎会在作用域查找该变量,如果找到会对它赋值。
闭包
- 简单的理解是函数的嵌套形成闭包,闭包包括函数本身以及它的外部作用域
闭包是指有权访问另一个函数作用域中的变量的函数 - 创建闭包的常见方式就是:在一个函数内部创建另一个函数
- 使用闭包可以形成独立的空间,延长变量的生命周期,包括中间状态值
4. v-model
引述:https://blog.csdn.net/xidongdong1/article/details/79539243
v-model虽然很像使用了双向数据绑定的 Angular 的 ng-model,但是 Vue 是单项数据流,v-model 只是语法糖而已。
第一行的代码其实只是第二行的语法糖。1
2<input v-model="sth" />
<input v-bind:value="sth" v-on:input="sth = $event.target.value" />
然后第二行代码还能简写成这样:1
<input :value="sth" @input="sth = $event.target.value" />
要理解这行代码,首先你要知道 input 元素本身有个 oninput 事件,这是 HTML5 新增加的,类似 onchange ,每当输入框内容发生变化时,就会触发oninput,把最新的value传递给 sth。
我们仔细观察语法糖和原始语法那两行代码,可以得出一个结论:
在给元素添加v-model属性时,默认会把value作为元素的属性,然后把’input’事件作为实时传递value的触发事件
5. toast
引述:https://www.jianshu.com/p/ae0c4a055cce
步骤:
- 初始化toast:
使用section设计toast 弹出内容,确保一个页面只有一个toast,对这个toast 设置隐藏使用section设计toast 弹出内容,确保一个页面只有一个toast,对这个toast 设置隐藏 - 显示toast
- 显示toast时,需要确保上一个TimeOut已经被清空
- 判断弹出内容,不能为空
- 隐藏toast
判断TimeOut 是否存在,若存在则清空
1 | /** |
6. session,cookies
cookies
cookie的作用是与服务器进行交互(在浏览器和服务器来回传递),作为HTTP规范的一部分而存在。
服务器和客户端都可以访问,大小只有4kb左右;存在有效期,过期后将会被删除。
Cookies 的大小是受限的,并且每次你请求一个新的页面的时候Cookies都会被发送过去,这样无形浪费了带宽,另外,cookie还需要指定作用域,不可以跨域调用。
Web Storage
web storage 仅仅是为了在本地“存储”数据而生。
除此之外,web Storage 拥有setItem,getItem,removeItem,clear 等方法,不像cookie 需要前端开发者自己封装setCookie,getCookie。
session:(sessionStorage)中的数据,这些数据只有在同一个会话找那个的页面才能访问,并且当会话结束后数据也随之销毁,因此session 不是一种持久化的本地存储,仅仅是会话级别的存储。
localStorage:(本地存储)用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期。只有本地浏览器端可以访问数据,服务器不能访问本地存储直到故意通过POST 或者GET的通道发送到服务器;每个域5MB,没有过期数据,它将保留直到用户从浏览器清除或者使用js 代码清除。
cookie 和 sessionStorage localStorage
- sessionStorage 和 localStorage 的存储空间更大
- sessionStorage 和 localStorage 有更多的丰富易用的接口
- sessionStorage 和 localStorage各自独立的存储空间
7. 中间件
引文:http://expressjs.com/en/guide/using-middleware.html
我之前写过一篇博客:https://blog.csdn.net/Welkin_qing/article/details/84175499
提及一下express:
express 是一个路由和中间件Web框架,它具有自己的最小功能:Express应用程序本质上是一系列中间件函数调用。
Express 应用程序可以使用以下类型的中间件:
- 应用程序级中间件
- 路由器级中间件
- 错误处理中间件
- 内置中间件
- 第三方中间件
可以使用可选的装载路径加载应用程序级和路由器级中间级。还可以将一系列中间件功能加载在一起,从而在安装点创建中间件系统的子堆栈。
(详情见引述)
中间件函数是可以访问请求对象(req),响应对象(res)以及应用程序的请求,去响应周期中的下一个中间件函数的函数,下一个中间件函数通常由名为 next 的变量表示。
中间件的功能可以执行以下任务:
- 执行任何代码
- 更改请求和响应对象
- 结束请求 - 响应循环
- 调用堆栈中的下一个中间件函数
如果当前的中间件函数没有结束请求 - 响应周期,则必须调用 next() 以将控制传递给下一个中间件函数。否则,请求将被挂起。