面经
Vuex 的 5 个核心属性是什么?
State
、 Mutation
、Action
、Getter
、 Module
state
在 state 中定义我们需要管理的数组、对象、字符串等等,只有在这里定义了,在 Vue.js 的组件中才能获取你定义的这个对象的状态。
mutation
更改 store 中 state 状态的唯一方法就是提交 mutation,就很类似事件。每个mutation 都有一个字符串类型的事件类型和一个回调函数,我们需要改变 state的值就要在回调函数中改变。我们要执行这个回调函数,那么我们需要执行一个相应的调用方法:store.commit。
action
action 可以提交 mutation,在 action 中可以执行 store.commit,而且 action 中可以有任何的异步操作。在页面中如果我们要用这个 action,则需要执行store.dispatch
getter
getter 有点类似 Vue.js 的计算属性,当我们需要从 store 的 state 中派生出一些状态,那么我们就需要使用 getter,getter 会接收 state 作为第一个参数,而且 getter的返回值会根据它的依赖被缓存起来,只有 getter 中的依赖值(state 中的某个需要派生状态的值)发生改变的时候才会被重新计算。
module
module其实只是解决了当state中很复杂臃肿的时候,module可以将store分割成模块,每个模块中拥有自己的 state、mutation、action 和 getter
BFC 是什么?
定义:BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,并且与这个区域外部毫不相干
布局规则
触发 BFC的方式
- position 值为 absolute 或 fixed
- float:left/right ;浮动的元素多个放在一起,会互相隔开。
- overflflow 值不为 visible ,为hidden/auto/scroll
- display 值为 inline-block 、 table-cell 、table-caption、 flex, inline-flex
BFC的应用
- 处理块级元素:上下 margin 合并问题
- 处理margin塌陷
- 清除浮动
- 实现自适应布局:左边固定,右边自适应;
flex=>display:flex;左边定宽,右边flex:1;
浮动=>先浮动占位置,再中间盒子overflow:hidden
定位=>先定位,再设置padding即可
如何判断 JavaScript 的数据类型?
typeof
typeof 可以用来区分除了 null 类型以外的原始数据类型,对象类型的可以从普通对象里面识别出函数:
typeof undefined // "undefined"
typeof null // "object"
typeof 1 // "number"
typeof "1" // "string"
typeof Symbol() // "symbol"
typeof function() {} // "function"
typeof {} // "object"
问题一:typeof 不能识别 null,如何识别 null?
答案:如果想要判断是否为 null,可以直接使用===全等运算符来判断(或者使用下面的 Object.prototype.toString 方法):
let a = null
a === null // true
问题二:typeof 作用于未定义的变量,会报错吗?
答案:不会报错,返回"undefined"。
typeof randomVariable // "undefined"
问题三:typeof Number(1)的返回值是什么?
答案:"number"。注意 Number 和 String 作为普通函数调用的时候,是把参数转化为相应的原始数据类型,也就是类似于做一个强制类型转换的操作,而不是默认当做构造函数调用。注意和 Array 区分,Array(...)等价于 new Array(...)
typeof Number(1) // "number"
typeof String("1") // "string"
Array(1, 2, 3)
// 等价于
new Array(1, 2, 3)
问题四:typeof new Number(1)的返回值是什么?
答案:"object"。
typeof new Number(1) // "object"
typeof new String(1) // "object"
instanceof
- instanceof 不能用于判断原始数据类型的数据:
3 instanceof Number // false
'3' instanceof String // false
true instanceof Boolean // false
- instanceof 可以用来判断对象的类型:
var date = new Date()
date instanceof Date // true
var number = new Number()
number instanceof Number // true
var string = new String()
string instanceof String // true
需要注意的是,instanceof 的结果并不一定是可靠的,因为在 ECMAScript7 规范中可以通过自定义 Symbol.hasInstance 方法来覆盖默认行为。
Object.prototype.toString
Object.prototype.toString.call(undefined).slice(8, -1) // "Undefined"
Object.prototype.toString.call(null).slice(8, -1) // "Null"
Object.prototype.toString.call(3).slice(8, -1) // "Number"
Object.prototype.toString.call(new Number(3)).slice(8, -1) // "Number"
Object.prototype.toString.call(true).slice(8, -1) // "Boolean"
Object.prototype.toString.call('3').slice(8, -1) // "String"
Object.prototype.toString.call(Symbol()).slice(8, -1) // "Symbol"
由上面的示例可知,该方法没有办法区分数字类型和数字对象类型,同理还有字符串类型和字符串对象类型、布尔类型和布尔对象类型,另外,ECMAScript7 规范定义了符号 Symbol.toStringTag,你可以通过这个符号自定义
Object.prototype.toString 方法的行为:
'use strict'
var number = new Number(3)
number[Symbol.toStringTag] = 'Custom'
Object.prototype.toString.call(number).slice(8, -1) // "Custom"
function a () {}
a[Symbol.toStringTag] = 'Custom'
Object.prototype.toString.call(a).slice(8, -1) // "Custom"
var array = []
array[Symbol.toStringTag] = 'Custom'
Object.prototype.toString.call(array).slice(8, -1) // "Custom"
因为 Object.prototype.toString 方法可以通过 Symbol.toStringTag 属性来覆盖默认行为,所以使用这个方法来判断数据类型也不一定是可靠的
Array.isArray
Array.isArray(value)可以用来判断 value 是否是数组:
Array.isArray([]) // true
Array.isArray({}) // false
(function () {console.log(Array.isArray(arguments))}()) // false
如何区分数组和对象?
通过 ES6中的 Array.isArray 来识别
Array.isArray([]) //true
Array.isArray({}) //false
通过 instanceof 来识别
[] instanceof Array //true
{} instanceof Array //false
通过调用 constructor 来识别
{}.constructor //返回 object
[].constructor //返回 Array
通过 Object.prototype.toString.call 方法来识别
Object.prototype.toString.call([]) //["object Array"]
Object.prototype.toString.call({}) //["object Object"]
JavaScript 中的作用域、预解析与变量声明提升?
作用域
就是变量的有效范围。 在一定的空间里可以对数据进行读写操作,这个空间就是数据的作用域
- 全局作用域:
最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的; - 局部作用域:
局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部。在 ES6 之前,只有函数可以划分变量的作用域,所以在函数的外面无法访问函数内的变量 - 块级作用域:凡是代码块就可以划分变量的作用域,这种作用域的规则就叫块级作用域
块级作用域 函数作用域 词法作用域之间的区别:
3.1
块级作用域和函数作用域描述的是,什么东西可以划分变量的作用域
3.2
词法作用域描述的是,变量的查找规则之间的关系:
3.3
块级作用域包含 函数作用域 - 词法作用域与块级作用域、函数作用域之间没有任何交集, 他们从两个角度描述了作用域的规则
ES6之前 JavaScript 采用的 是函数作用域+词法作用域,ES6 js 采用的是块级作用域+词法作用域
预解析
JavaScript 代码的执行是由浏览器中的JavaScript解析器来执行的。JavaScript 解析器执行
JavaScript 代码的时候,分为两个过程:预解析过程和代码执行过程
预解析过程:
- 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值
- 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用
- 先提升 function,再提升 var
变量声明提升
变量提升:定义变量的时候,变量的声明会被提升到作用域的最上面,变量的赋值不会提升
函数提升:JavaScript 解析器首先会把当前作用域的函数声明提前到整个作用域的最前面
变量声明提升:
使用 var 关键字定义的变量,被称为变量声明;
函数声明提升的特点是,在函数声明的前面,可以调用这个函数
标准盒模型和怪异盒模型的区别
盒模型的指定:
在CSS3中,我们可以通过设置 box-sizing 的值来决定具体使用何种盒模型:
content-box 标准盒模型
border-box 怪异盒模型
标准盒模型:
box-sizing: content-box; (默认值)
元素宽高是内容的宽高。
因此,计算一个元素宽度的公式如下(不考虑margin, margin是外边距, 如果是计算占用页面的空间, 就要带上margin):
盒子宽度 = border-left + padding-left + width + padding-right + border-right
占据页面宽度 = margin-left + border-left + padding-left + width + padding-right + border-right + margin-right
怪异盒模型:
box-sizing: border-box; (目前主流常用值)
元素宽高不是content的宽高,而是去除margin后剩下的元素占用区域的宽高,即:
盒子宽度= width
因此,计算一个元素占用了页面总宽度的公式如下:
盒子占据页面宽度= margin-left + width + margin-right
谈谈你对闭包的理解
什么是闭包?
一个作用域可以访问另外一个函数内部的局部变量 ,或者说一个函数(子函数)访问另一个函数(父函数)中的变量。 此时就会有闭包产生 ,那么这个变量所在的函数我们就称之为闭包函数。
闭包的本质:将函数外部与函数内部连接起来的桥梁
在实际开发中,闭包最大的作用就是用来 实现数据私有。
注意:外部函数中,一般需要return引用(才不会被释放掉,或者有指向他的其他引用)
下面再来看一个简单示例:
function Person() {
// 以 let 声明一个局部变量,而不是 this.name
// this.name = 'zs' => p.name
let name = 'hm_programmer' // 数据私有
this.getName = function(){
return name
}
this.setName = function(value){
name = value
}
}
// new:
// 1. 创建一个新的对象
// 2. 让构造函数的this指向这个新对象
// 3. 执行构造函数
// 4. 返回实例
const p = new Person()
console.log(p.getName()) // hm_programmer
p.setName('Tom')
console.log(p.getName()) // Tom
p.name // 访问不到 name 变量:undefined
在此示例中,变量 name
只能通过 Person 的实例方法进行访问,外部不能直接通过实例进行访问,形成了一个私有变量。
function aaa() {
var a = 0;
return function () {
alert(a++);
};
}
var fun = aaa();
fun(); //1
fun = null //释放内存,断开对于内部函数的引用,对应的缓存的变量内容也会被释放掉
闭包的优缺点
- 优点:
- 实现封装性操作
- 实现缓存
- 缺点:
- 消耗内存:因为闭包函数中的局部变量不会等着闭包函数 执行完就销毁, 因为还有别的函数要调用它 , 只有等着所有的函数都调用完了他才会销毁
闭包的主要作用: 延伸了变量的作用范围
闭包会造成内存泄漏,如何解决:用完之后手动释放
详解:
闭包不仅仅可以实现函数内部的作用域访问这个函数中的局部变量, 还可以实现全局作用域或者是别的地方的作用域也可以访问到函数内部的局部变量 , 实现方法就是 return 了一个函数
所以return 函数也是我们实现闭包的一个主要原理, 因为返回的这个函数本身就是我们 fn 函数内部的一个子函数 ,所以子函数是可以访问父函数里面的局部变量的, 所以返回完毕 之后 ,外面的函数一调用, 就会回头调用返回的这个函数, 所以就可以拿到这个子函数对 应的父函数里面的局部变量。
注意:
1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包, 否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象 (object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
闭包的应用场景1
- 缓存:假如有一个耗时的函数,每次调用都耗时很长,而该函数的相关内容不经常改变,这样我们可以将它的计算结果进行缓存,下次执行时,进行判断,如果有直接返回,如果没有再进行计算,将计算结果放入缓存中
var cacheApp = (function () {
var cache = {};
return {
getResult: function (id) {
if (id in cache) {
return "得到的结果是:" + cache[id];
}
var result = timeFn(id);
cache[id] = result;
return "得到的结果是:" + result;
},
};
})();
function timeFn(id) {
console.log("这是一个耗时的操作");
return id;
}
console.log(cacheApp.getResult(12));
console.log(cacheApp.getResult(12));
闭包的应用场景2
具有一定特征的代码封装到相应的函数当中,对外暴露出对应的方法,用户直接用,不用关心内部的逻辑
//使用闭包完成对外开放关于入栈和出栈对应的方法
var stack = (function () {
var arr = [];
return {
push: function (value) {
arr.push(value);
},
pop: function () {
return arr.pop();
},
size: function () {
return arr.length;
},
};
})();
stack.push("abc");
stack.push("def");
console.log(stack.size());
console.log(stack.pop());
console.log(stack.size());
闭包常见面试题1
解决点击li ,获取到索引值,不能获取到li,然后for循环的方式遍历li,添加点击事件,因为触发之前for循环已经结束了,
<body>
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
<li>e</li>
</ul>
</body>
<script>
var list = document.getElementsByTagName("ul")[0].children;
for (var i = 0; i < list.length; i++) {
(function (index) {
list[index].onclick = function () {
console.log(index);
};
})(i);
}
</script>
闭包常见面试题2
var arr = ["a", "b", "c"];
for (var i = 0; i < arr.length; i++) {
setTimeout(function () {
console.log(arr[i]); //undefined,因为for循环已经结束,i=3,arr[3]不满足
}, 1000);
}
//正确方法如下:
var arr = ["a", "b", "c"];
for (var i = 0; i < arr.length; i++) {
(function (index) {
setTimeout(function () {
console.log(arr[index]);
}, 1000);
})(i);
}
var userName = "zhangsan";
var person = {
userName: "lisi",
method: function () {
return function () {
return this.userName;
};
},
};
console.log(person.method()()); //zhangsan,因为在windows调用了返回的函数
//正确方法如下:
var userName = "zhangsan";
var person = {
userName: "lisi",
method: function () {
var that = this
return function () {
return that.userName;
};
},
};
console.log(person.method()());
闭包常见面试题3
function create() {
var a = 100;
return function () {
console.log(a);
};
}
var fn = create();
var a = 200;
fn(); //100
function print(fn) {
var a = 200;
fn();
}
var a = 100;
function fn() {
console.log(a); //在函数定义的时候向上级作用域查找,而不是在执行的地方
}
print(fn);
ES6 的新增方法
新增声明命令 let 和 const
在 ES6 中通常用 let 和 const 来声明,let 表示变量、const 表示常量
特点
- let 和 const 都是块级作用域。以{}代码块作为作用域范围 只能在代码块里面使用
- let不存在变量提升,只能先声明再使用,否则会报错。语法上,称为“暂时性死区” 在同一个代码块内,不允许重复声明同一个变量 ,声明时可以不需要马上赋值,变量可以修改
- const 声明的是一个只读常量,在声明时就需要赋值。(如果 const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址不能改变,而 变量成员是可以修改的。)
模板字符串(Template String)
用一对反引号标识,它可以当作普通字符串使用,也可以用来定义多行字符串,也可以在字符串中嵌入变量,js 表达式或函数,变量、js 表达式或函数需要写在${ }中。
函数的扩展
- 函数的默认参数
ES6 为参数提供了默认值。在定义函数时便初始化了这个参数,以便在参数没有被传递进去时使用。 - 箭头函数
写法:函数名=(形参)=>{……}
当函数体中只有一个表达式时,{}和 return 可以省略, 当函数体中形参只有一个时,()可以省略。 - 特点
它其实没有属于⾃⼰的 this,捕获其外层上下⽂的 this 值作为⾃⼰的 this 值。如果没有就指向 window。
对象的扩展
-
属性的简写/方法的简写
-
Object.keys()方法
-
Object.assign ()
5、for...of 循环
import 和 expor
ES6 标准中,JavaScript 原生支持模块(module)。这种将 JS 代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用
export 用于对外输出本模块(一个文件可以理解为一个模块)变量的接口
import 用于在一个模块中加载另一个含有 export 接口的模块
import 和 export 命令只能在模块的顶部,不能在代码块之中
Promise 对象
Promise 是异步编程的一种解决方案,将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,要是为了解决异步处理回调地狱(也就是循环嵌套的问题)而产生的
Promise 构造函数包含一个参数和一个带有 resolve(解析)和 reject(拒绝)两个参数的回调。在回调中执行一些操作(例如异步),如果一切都正常,则调用 resolve,否则调用 reject。
对于已经实例化过的 Promise 对象可以调用 Promise.then() 方法,传递 resolve 和 reject方法作为回调。then()方法接收两个参数:onResolve 和 onReject,分别代表当前 Promise 对象在成功或失败时
Promise 的 3 种状态
Fulfilled 为成功的状态,Rejected 为失败的状态,Pending 既不是 Fulfilld也不是Rejected的状态,可以理解为 Promise 对象实例创建时候的初始状态
解构赋值
- 数组的解构赋值
解构赋值是对赋值运算符的扩展。
是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
数组中的值会自动被解析到对应接收该值的变量中,数组的解构赋值要一一对应如果
有对应不上的就是 undefined
let [a, b, c] = [1, 2, 3];
// a = 1 // b = 2 // c = 3 - 对象的解构赋值
对象的解构赋值和数组的解构赋值其实类似,但是数组的数组成员是有序的而对象的属性则是无序的,所以对象的解构赋值简单理解是等号的左边和右边的结构相同
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // foo = 'aaa' // bar = 'bbb'
let { baz : foo } = { baz : 'ddd' }; // foo = 'ddd
Set 数据结构
Set 数据结构,类似数组。所有的数据都是唯一的,没有重复的值。它本身是一个构造函数。
- Set 属性和方法
Size() 数据的长度
Add()
添加某个值,返回 Set 结构本身。
Delete() 删除某个值,返回一个布尔值,表示删除是否成功。
Has() 查找某条数据,返回一个布尔值。
Clear()清除所有成员,没有返回值。
9.2)主要应用场景:数组去重
10、class
class 类的继承 ES6 中不再像 ES5 一样使用原型链实现继承,而是引入 Class 这个概念
ES6 所写的类相比于 ES5 的优点:
区别于函数,更加专业化(类似于 JAVA 中的类)
写法更加简便,更加容易实现类的继承
11、…
展开运算符可以将数组或对象里面的值展开;还可以将多个值收集为一个变量
12、async、await
使用 async/await, 搭配 Promise,可以通过编写形似同步的代码来处理异步流程, 提高代码
的简洁性和可读性 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成
修饰器
@decorator 是一个函数,用来修改类甚至于是方法的行为。修饰器本质就是编译时执行的函数
Symbol
Symbol 是一种基本类型。Symbol 通过调用 symbol 函数产生,它接收一个可选的名字参数,
该函数返回的 symbol 是唯一的
Proxy
Proxy 代理使用代理(
Proxy)监听对象的操作,然后可以做一些相应事情
简述创建函数的几种方式? (3种)
第一种(函数声明)
function sum1(num1,num2){
return num1+num2;
}
第二种(函数表达式)
var sum2 = function(num1,num2){
return num1+num2;
}
第三种(函数对象方式)
var sum3 = new Function("num1","num2","return num1+num2");
CSS选择器的优先级是怎么样的?
!important>行内样式>id选择器>类选择器>标签选择器>通配符选择器>继承
选择器相同,下面的css权重高于上面的css权重
通过CSS的哪些方式可以实现隐藏页面上的元素?
方式 | 说明 |
---|---|
opacity: 0 |
透明度为0,占位置 |
visibility: hidden |
与透明度为0的方案非常类似,占位置 |
overflflow: hidden | 只会隐藏元素溢出的部分;占据空间且不可交互 |
display: none |
盒子不占位置 |
z-index: -9999 | 通过将元素的层级置于最底层,让其他元素覆盖住它,达到看起来隐藏的效果 |
transform: scale(0,0) |
通过将元素进行缩放,缩小为0;依然会占据空间,但不可交互 |
left: -9999px | 通过将元素定位到屏幕外面,达到看起来看不到的效果 |
transform变换的只是一个视觉效果,不会影响其他盒子的布局
px、em、rem之间有什么区别?
单位名称 | 说明 |
---|---|
px | 绝对单位。代表像素数量,页面会按照给出的精确像素进行展示 |
em | 相对单位。相对于父盒子的font-size ,而如果自身定义了字体大小则按自身的来算 |
rem | 相对单位。相对于html根标签 |
rem一般可以用于适配, 原理:
- 使用rem为单位 (转换问题:利用webpack插件,写px自动转rem)
- 动态设置不同屏幕下html的 font-size(媒体查询, js设置, 插件设置都可以)