前端开发面试——试题简介
vue常见面试题 - Lucky Girl的文章 - 知乎
https://zhuanlan.zhihu.com/p/92407628
https://www.cnblogs.com/moluy/p/13858954.html
JS部分
解释一下变量提升
JavaScript引擎是通过先解析代码 获取所有声明的变量 然后再逐行向下运行 所以造成一个问题 就是所有变量声明的语句 都会被提升到代码顶部 这就叫做变量提升
console.log(a) // undefined
var a = 1
提升执行顺序
引擎将var a = 1
拆分为 var a = undefined
和 a = 1
并且会提升var a = undefined
放到顶端
简单描述一下闭包这个概念
闭包
= 函数和函数内可访问变量的总和
-
闭包的特点
- 函数内部总是可以访问其所在的外部函数中声明的参数和变量
- 使用
3. 闭包的缺点:闭包会导致内存占用过高,因为变量都没有释放内存,从而垃圾回收机制不会销毁该变量
function Person(){ var name = 'wl'; this.getName = function(){ return name; } this.setName = function(value){ name = value; } } const person = new Person() console.log(person.getName()) //wl person.setName('test') console.log(person.getName()) //test console.log(name) // undefined // 解决办法二: for (var j = 0; j < op.length; j++) { op[j].onclick = (function(j) { return function() { alert(j); }; })(j); }
### 垃圾回收原理
垃圾回收原理博客
1.js的垃圾回收机制是为了以防内存泄露。内存泄露的含义就是当已经不需要某块内存是,这快内存还存在者,垃圾回收机制就是不定期的寻找到不适用的变量,
并释放掉他们所指向的内存。
js有哪些类型
1. 基本类型(原始类型):
* String
* Number
* undefined
* null
* boolean
* symbol(es6)
2. 引用类型
* Object
js引用类型和基本类型有什么区别
- 存储方式不同
- 也就是说基本类型互不干扰 引用类型会指向相同的内存地址
let a = 1
let b
b = a
b = 2
console.log(a, b) // 1 2
let obj = { name: 'test' }
let obj2 = obj1
obj2.name = 'bar'
console.log(obj.name) // bar
* 栈内存和堆内存
* 栈:存放基本类型,栈会自动分配内存空间,会自动释放,简单的数据段,占据固定的大小空间
* 堆:存放引用类型,动态分配内存,大小不定也不会会自动释放,
* 区别:栈所有在方法中定义的变量都是栈内存中,随着方法执行结束,这个方法的内存栈也会自然销毁。
null和undefined的区别
- null表示空值 null本身是一个对象 是拥有指针地址的
- undefined表示
不存在
0.1 + 0.2为什么不等于0.3
-
因为0.1和0.2被转换为二进制以后其实是无限循环的 但是js采取的浮点数裁剪方案会导致精度失效
-
解决方案
-
ParseFloat((0.1 + 0.2).toFixed(10))
-
原形链的理解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttrJE6Ki-1625026337021)(./assets/proto.jpg)]
详细解释在我的博客
1.所有的引用类型都可以自定义添加属性
2. 所有的引用类型都有自己的隐式原型(proto)
3. 函数都有自己的显示原型(prototype)
4. 所有的引用类型的隐式原型都指向对应构造函数的显示原型
5. 使用引用类型的某个自定义属性时,如果没有这个属性,会去该引用类型的proto(也就是对应构造函数的prototype)中去找
this的理解
- 其实this只有三种情况
-
- 声明在全局的指向
window
- 声明在全局的指向
-
- 谁调用了某个函数 那么this就指向谁
-
- 对于new出来的一个对象 那么this指向对象本身
-
EventLoop
- js优先执行同步代码 这属于宏任务 当js确认调用栈里没有宏任务以后 会扫描一遍微任务队列 如果发现有微任务 就会批量一次性执行队列里的所有微任务 每执行完一个宏任务都会伴生一个微任务队列
- 宏任务:
script
标签setTimeout
setInterval
I/O
- 微任务
promise
mutationObserver
深浅拷贝
1.浅拷贝:也就是只复制了第一层属性,复制对象是基本类型
在复制基本类型时,直接使用等号完成,在复制引用类型时,循环便利对象,对每个属性或值使用等号完成。
2. 深拷贝:对属性中所有引用类型的值,遍历到是基本类型的值为止,利用递归来实现深拷贝
### 递归的方法及什么地方常用
1.递归就是函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。、
```js
var data = [
{
name: "所有物品",
children: [
{
name: "水果",
children: [{name: "苹果", children: [{name: '青苹果'}, {name: '红苹果'}]}]
},
{
name: '主食',
children: [
{name: "米饭", children: [{name: '北方米饭'}, {name: '南方米饭'}]}
]
},
{
name: '生活用品',
children: [
{name: "电脑类", children: [{name: '联想电脑'}, {name: '苹果电脑'}]},
{name: "工具类", children: [{name: "锄头"}, {name: "锤子"}]},
{name: "生活用品", children: [{name: "洗发水"}, {name: "沐浴露"}]}
]
}
]
}]
//普通遍历实现
var forFunction = function () {
var str = ""
data.forEach(function(row){
row.children.forEach(function(row){
row.children.forEach(function(row){
row.children.forEach(function(row){
str += (row.name + ";")
})
})
})
})
console.log(str)
}
forFunction()
//递归遍历实现
var recursiveFunction = function(){
var str = ''
const getStr = function(list){
list.forEach(function(row){
if(row.children){
getStr(row.children)
}else {
str += row.name + ";"
}
})
}
getStr(data)
console.log(str)
}
recursiveFunction()
```
new运算符做了什么
1. 一个新的对象被创建 继承自构造函数的prototype foo.prototype
2. 构造函数被执行传入参数 以及this this指向这个新的实例
3. 判断构造函数是否返回了对象 如果是返回的数据结构是一个对象 那么就会取代new 如果没有 那么就是第一步创建的对象
###继承
1.原型链继承(将父类的实例作为子类的原型)
特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点:1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
2. 借用构造函数继承
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
3. 组合继承
重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
4. 原型式继承
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
特点:类似于复制一个对象,用函数来包装。
缺点:1、所有实例都会继承原型上的属性。
2、无法实现复用。(新实例属性都是后面添加的)
5. 寄生式继承
重点:就是给原型式继承外面套了个壳子。
优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:没用到原型,无法复用。
6. 寄生组合式继承
寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参
重点:修复了组合继承的问题
ES Module 和 CommonJS Module区别
- ES Moudule是静态引入的
- Common JS是动态引入的
- 前者支持tree shaking 后者不支持
###Promise 原理
1.promise 是一种异步编程的解决方案。用于解决回调地狱的问题。
2.promise 是事件循环中的微任务。在每次事情循环中会清空了微任务队列。
3.promise 一旦执行无法中途取消
4.promise的错误无法在外部被捕捉到,只能在内部进行预判处理
5.在promise内如何执行的,监测起来很难。
promise对象存在三种状态:pending(进行中) Fulfolled(已成功) Rejected(已失败)
resove和reject 改变promise的状态 将接受的值作为promise 的value
Vue面试题
Vue的核心
- 数据驱动视图(MVVM) + 组件化
双向绑定的原理
Object.definePrototy getter setter 网上随便搜一下 多的很
getter 是调用了这个方法
setter 是数据更新了返我
configurable:true, //默认false, 是否可删除
Vue watch 与computed的区别
watch的使用场景是在data中某个数据发生变化时或者需要在数据变化执行异步
watch 普通监听跟深度监听 (使用handler 的immediate属性 可以立即执行)
deep 为true 可以深度监听某个对象的值。
相同点:他们两者都会观察页面数据变化的
不同点:computed只有当依赖的数据发生变化时才会计算,当数据没有变化时,它会读取缓存数据
watch每次都需要执行函数,wacth更适用数据变化是的异步操作。
computed原理
Vue为什么监听不到对象和数组
- 因为引用类型是存放在堆内存里的 平时我们操作的引用类型其实是存放在栈内存里的一份拷贝 所以无法监听到
data为什么要是函数
- 函数作用域 防止不同组件相同变量造成的污染
描述一下Vue初次更新和数据更新的过程
- 初次更新
- 解析模板为render
- 触发
obj.defineProptryty
的getter - 执行render 生成VNode
- patch(ele, VNode)
- 数据更新
- 触发```obj.defineProptry``的setter
- 执行render 生成VNode
- patch(oldVnode, newVnode)
Vue的diff算法
- 新旧节点通过 首尾比较 新头旧尾 新尾旧头四种比较方式来做diff比较
- 当4中方式都无法匹配的时候 会用到key 所以key最好是唯一值 不能是index 因为index会变化
Vue的Compiler过程
Complie的主要作用就是解析模板,生成渲染模板的render,render的作用主要是为了生成Vnode
- parse - optimize - generate
- parse 接受template原始模板,按着模板的节点和数据生成对应的ast
- optimize 遍历ast的每一个节点,标记静态节点,这样就知道哪部分不会变化,于是在页面需要更新时,减少去对比这部分DOM,提升性能
- generate 把前两步生成完善的ast,组成render字符串,然后将render字符串通过new Function的方式转换成渲染函数
描述一下Vuex
- 首先vuex适用于一些同级组件或者完全八竿子打不着关系的组件之间进行传值
- state: 组件之间需要公用的值 可以存放在state里
- actions: 存放的是异步操作或者是批量的同步操作 组件通过
dispatch
调用actions - mutations: 存放的是一些同步方法 对state的修改 actions通过
commit
调用 - actions和mutations最大的区别就是前者是异步的后者是同步的
尝试用js模拟vnode
// html代码<div class="div-container" id="div"> <p>vnode</p> <div> <p style="font-size: 12px">try to describe V-node by using js</p> </div> </div>
// 用js对象模拟出的vnode
{
tag: 'div',
props: {
className: 'div-container',
id: 'div'
},
children: [
{
tag: 'p',
children: 'vnode'
},
{
tag: 'div',
children: [
tag: 'p',
props: {
style: 'font-size: 20px'
},
children: 'try to describe V-node by using js'
]
}
]
}
Vue-Router实现原理
- 首先有两种模式
hash
和history
hash
实现原理: 监听window.hashOnChange
事件 hash模式路由带#号history
利用了html5新出的APIpushState
和popState
父子组件生命周期
- 父beforeCreated -> 父created -> 父beforeMounted -> 子beforeCreated -> 子created -> 子beforeMounted -> 子mounted -> 父mounted
Webpack面试题
devser
##性能优化
1. css 压缩css optimize-css-assetsWebpackPlugin mini-css-extrect-plugin
2. js 压缩生产环境自动压缩 mode:'production' html 压缩再htmlPlugin minfigy的两个属性 js按序加载usage
3. 优化配置 一半分为2种,开发环境和生产环境,
##开发环境 优化打包构建速度。 优化代码调试。
*HMR 模块热替换 构建速度会更快
*source-map 如果构建后代码出错了,通过构建后提示错误信息
##生产环境 优化打包构建速度 。优化代码性能
*打包速度oneOf -- babel 缓存
_dll 对第三方的库再次进行打包 DllLinkPlugin
通过cnd 链接引用 externals
*缓存(hash-chunkhash-contenthash)
*优化性能的 tree shaking //必须es6,会自动清除没用的代码的 sideEffect:[注释起来]
4。code spliting 代码分割 单入口-就是一个bundle配置向多个 (通过import引入的js 代码就会分割) commonsChunkPlugin
5.懒加载,预加载
6.pwa 是离线打包技术 离线也可以访问 workboxwebpackPlugin.get
7.多进程打包,可以提示打包 速度
### webpack中都有用到那些插件
1.optimize-css-assetsWebpackPlugin css 压缩
2. speed-measure-webpack-plugin 打包速度分析 这个不是在plugin中引入。而是先实例化,smp.wrap()方法包裹
3. webpack-bundle-analyzer 体积分析
4. terser-webpack-plugin 多进程多实例并进行代码压缩 optimization 中进行压缩
5. CommonsChunkPlugin DLLPlugin 就是把第三方库和我们自己的代码分开,每次只打包项目自身代码
module bundle chunk的区别
- module - 源码文件(模块)
- chunk - 多个模块合成的代码
- bundle - 输出文件
Loader和Plugin的区别
- Loader是翻译器 帮助webpack识别该如何打包对应类型的文件
- Plugin会在webpack对应的生命周期自动帮我们做某件事
Webpack构建流程
- 初始化参数: 通过shell命令以及config.js合并参数
- 开始编译: 通过参数初始化Compiler对象 执行run方法
- 确定入口: 根据entry确定入口
- 编译模块: 从入口文件开始 利用loader开始翻译 并且通过递归来确定所有依赖的文件都已确立相互的依赖关系
- 输出资源
- 写入内存
利用webpack优化性能
1. 优化```sourcemap```
2. 利用CDN加速 => 静态资源路径修改为cdn路径
3. 利用```Tree shaking```默认开启 只支持es6 Moudle
4. code spliting(代码分割 你随便搜一下 很多的 其实就是配置项)
提高webpack打包效率
-
happyPack
-
dll打包
-
减少loader的搜索范围 loader可以配置exclude 因为node_modules 其实是已经打好包了 所以第三方文件不需要再次打包
###跨域如何解决的
- jsop jsonp的话请求一定需要对方服务器的支持才可以,优点是简单兼容性好,缺点是🈲支持get方法,具有局限性,不安全
- cors CORS需要
输入URl发生什么
1.域名解析,浏览器将url解析出相应的服务器的IP地址 2. 浏览器与目标服务器建立一条Tcp链接(三次握手) 3. 浏览器向服务器发送一次Http请求报文 4. 服务器返回浏览器一条HTTP响应报文 5. 浏览器进行渲染 6. 关闭TCP链接,四次挥手*浏览器渲染的步骤1.html解析出Dom2.css解析出style Rules3.两者关联生成render4.layout布局根据render计算每个节点信息5.painting 根据计算好的信息进行渲染页面
强制缓存和协商缓存
强制缓存是在我们第一次请求资源是在http响应头设置一个过期时间,在有效时间内都将直接从浏览器中进行获取,常见的http响应头字段如cache-Control 和Expires协商缓存是我们通过http响应头字段etag或者last-Modified等判断服务器上的资源是否修改,如果修改则从服务器重新获取,如果没修改则304指向浏览器器缓存获取
###http1.0/1.1/2.0
1.HTTP1.0定义了三种请求方法:get post head。1.1新增了六种请求方法:optoions put patch delete trace connect
2.缓存处理:在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,
HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
3.host头处理:
4.长链接
##2.0与和1.1相比
新的二进制格式(Binary Format):HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,
要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。
基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
多路复用(MultiPlexing):即连接共享,即每一个request都是是用作连接共享机制的。
一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,
接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
header压缩:如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,
HTTP2.0使用了专门为首部压缩而设计的 HPACK 算法,使用encoder来减少需要传输的header大小,
通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
服务端推送(server push):服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。
正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。
例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,
服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。
###http状态码
1xxx 临时响应表示临时响应并需要请求者继续执行操作的状态码
2xx(成功)表示成功处理了请求的状态码
3xx(重定向)表示要完成请求,需要进一步操作;通常,这些状态代码用来重定向
4xx(请求错误)这些状态码表示请求可能出错,妨碍了服务器的处理
5xx(服务器错误)这些状态码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错
/1.对数组的结构
// const ARRAY = [ '小石','小王']
// let [ xs, xw ] = ARRAY
// console.log(xs)
//es6的模板字符串
//内容中可以直接出现换行
// 2.变量的拼接
// let a = '小石'
// let b = `${a}是小仙女`;
// console.log(b)
//es6对象的简化写法
// let name ='1';
// const obj ={
// name
// }
//箭头函数
// 1.箭头函数的特性
// this是静态的,this始终指向函数声明是所在作用下的this的值
//call方法调用
// 2.不能作为构造实例化对象
// 箭头函数没有rutuen
// let arr =[
// {
// id:'1',
// c:[
// {
// id:2,
// },
// {
// id:3,
// }
// ]
// }
// ];
// let c;
// let res = arr.filter(item =>{
// c = item.c.filter(child => child.id==2)
// })
// console.log(c)
//rest 参数
// args参数必须放到最后
// function date(...args) {
// console.log(args)
// }
// date({id:'1',name:'wl'})
//拓展运算
// let arr =['wk','li1']
// function box() {
// console.log(arguments)
// }
// box(...arr)
//symbol 数据类型 是类似字符串的
//对象循环
// let user = {name:1,id:'2'}
// for(let [key, vlaue] of Object.entries(user)) {
// console.log(`${key}`)
// }
// let user = new Map()
// user.set('name', 'John')
// user.set('age', '30')
// for (let [key, value] of user.entries()) {
// console.log(`${key}:${value}`) // name:John, then age:30
// }
//数组循环 Array
let arr = [1,2,4,5,7]
// arr.forEach(function(elem, index, array) {
// if (arr[i] == 2) {
// continue
// }
// console.log(elem, index)
// })
//forEach 不支持continue 及break
//map 返回新的数组 每个元素为调用func的结果
// let result = arr.map(function(value) {
// value += 1
// console.log(value)
// return value
// })
// console.log(arr, result)
//filter() 返回符合条件的元素数组
// let result = arr.filter(value => value==2)
// console.log(arr, result)
// some() 返回的boolean, 判断是否有元素符合的func条件
// let ret = arr.some(item => item==8)
// console.log(arr, ret)
//every() 返回boolean ,判断每个元素都符合func条件
// let ret = arr.every(item =>{
// console.log(item)
// item ==7
// })
// console.log(arr, ret)
//reduce() 接受一个函数作为累加器
// let sum = arr.reduce(function(prev, cur, index, array) {
// return prev + cur
// }, 0)
// console.log(sum)
// let max = arr.reduce(function(prev, cur) {
// return Math.max(prev, cur)
// })
// console.log(max)
// let res = arr.reduce(function(prev, cur) {
// prev.indexOf(cur) == -1 && prev.push(cur)
// return prev
// }, [])
// console.log(res)
//es6 中数组遍历方式
// for (let item of arr) {
// console.log(item)
// // if(item ==4) {
// // break
// // }
// }
// for (let item of arr.values()) {
// console.log(item)
// }
// for (let item of arr.keys()) {
// console.log(item)
// }
// for (let [index, item] of arr.entries()) {
// console.log(index, item)
// }
//Array.from()
// let arrLike = {
// 0: 'a',
// 1: 'b',
// 2: 'c',
// length: 3
// }
// let ret = Array.from({
// length: 3
// }, function() {
// return arrLike
// })
// console.log(ret)
//Array.fill()
// fill()方法用一个固定值填充一个数组中从起始索引内的全部元素,不包括终止索引.
// arr.fill(9,3,4) //第一个元素是替换值 //第二个是开始值第三是结束值
// console.log(arr)
//Array.find() //返回数组中,满足第一个元素的值
// let ret = arr.find(item => item<5)
// console.log(ret)
// let ret = arr.findIndex(item => item==5)
// console.log(ret)
//Array.copyWithin() //在当前数组内部,将指定位置的成员复制到其他位置然后返回当前数组
// console.log(arr.copyWithin(1,3))
//function 箭头函数
// let pow = x => x * x
// console.log(pow(2))
//Object 复制对象 并相同替换
// const obj={
// id:1,
// name: 'w'
// }
// const obj2 ={
// name: 'wl'
// }
// const obj3 = Object.assign(obj,obj2)
// console.log(obj3)
//class 声明类
// class Animal {
// constructor(type) {
// this.type = type
// }
// walk() {
// console.log( `I am walking` )
// }
// }
// let dog = new Animal('dog')
// let monkey = new Animal('monkey')
// console.log(dog,monkey)
// class Animal {
// constructor(type) {
// this.type = type
// }
// walk() {
// console.log( `I am walking` )
// }
// static eat() {
// console.log( `I am eating` )
// }
// }
// let dog = new Animal()
// console.log(dog.eat())
// Symbol
// const grade = {
// 张三: {
// address: 'xxx',
// tel: '111'
// },
// 李四: {
// address: 'yyy',
// tel: '222'
// },
// 李四: {
// address: 'zzz',
// tel: '333'
// },
// }
// console.log(grade)
// const stu1 = Symbol('李四')
// const stu2 = Symbol('李四')
// const grade = {
// [stu1]: {
// address: 'yyy',
// tel: '222'
// },
// [stu2]: {
// address: 'zzz',
// tel: '333'
// },
// }
// console.log(grade)
// console.log(grade[stu1])
// console.log(grade[stu2])
// const sym = Symbol('imooc')
// class User {
// constructor(name) {
// this.name = name
// this[sym] = 'imooc.com'
// }
// getName() {
// return this.name + this[sym]
// }
// }
// const user = new User('xiecheng')
// console.log(user.getName())
// for (let key in user) {
// console.log(key)
// }
// for (let key of Object.keys(user)) {
// console.log(key)
// }
// for (let key of Object.getOwnPropertySymbols(user)) {
// console.log(key)
// }
// for (let key of Reflect.ownKeys(user)) {
// console.log(key)
// }
//消除魔术字符串
// const shapeType = {
// triangle: Symbol(),
// circle: Symbol()
// }
// function getArea(shape) {
// let area = 0
// switch (shape) {
// case shapeType.triangle:
// area = 1
// break
// case shapeType.circle:
// area = 2
// break
// }
// return area
// }
// console.log(getArea(shapeType.triangle))
//set
// let arr1 = [1, 2, 3, 4]
// let arr2 = [2, 3, 4, 5, 6]
// // 交集
// let s1 = new Set(arr1)
// let s2 = new Set(arr2)
// let result = new Set(arr1.filter(item => s2.has(item)))
// console.log(Array.from(result))
// // 差集
// let arr3 = new Set(arr1.filter(item => !s2.has(item)))
// let arr4 = new Set(arr2.filter(item => !s1.has(item)))
// console.log(arr3)
// console.log(arr4)
// console.log([...arr3, ...arr4])
// const str = 'imooc'
// console.log(str.includes('mo'))
// console.log(str.startsWith('im'))
// console.log(str.endsWith('mooc'))
// const newStr = str.repeat(10)
// console.log(newStr)
// Number 二进制
// Number.isFinite()
// 用来检查一个数值是否为有限的(finite),即不是Infinity。
// Number.isFinite(15) // true
// Number.isFinite(0.8) // true
// Number.isFinite(NaN) // false
// Number.isFinite(Infinity) // false
// Number.isFinite(-Infinity) // false
// Number.isFinite('foo') // false
// Number.isFinite('15') // false
// Number.isFinite(true) // false
// const a = 0B0101
// console.log(a)
// const b = 0O777
// console.log(b)
// Number.isInteger() //用来判断一个数值是否为整数
// Math.trunc()方法用于去除一个数的小数部分,返回整数部分。
// console.log(Math.trunc(5.5))
// console.log(Math.trunc(-5.5))
//Proxy
// let o = {
// name: 'xiaoming',
// age: 20
// }
// let handler = {
// get(obj, key) {
// return Reflect.has(obj, key) ? obj[key] : ''
// }
// }
// let p = new Proxy(o, handler)
// console.log(p.name)
// Promise .reject()
// new Promise(function(resolve) {
// resolve(42)
// })
// Promise.resolve(42).then(function(value) {
// console.log(value)
// })
// var p1 = Promise.resolve(1)
// var p2 = Promise.resolve(2)
// var p3 = Promise.resolve(3)
// Promise.all([p1, p2, p3]).then(function(results) {
// console.log(results) // [1, 2, 3]
// })
// function getPromise(url) {
// return new Promise((resolve, reject) => {
// ajax(url, res => {
// resolve(res)
// }, err => {
// reject(err)
// })
// })
// }
// getPromise('static/a.json')
// .then(res => {
// console.log(res)
// return getPromise('static/b.json')
// }).then(res => {
// console.log(res)
// return getPromise('static/c.json')
// }).then(res => {
// console.log(res)
// }).catch(err => {
// console.log(err)
// })
//git 撤销最近一次提交
// git reset HEAD^
//git push -f 强制推送
//git brabch -a 查看清理远程分支
// git push origin --delete dev
// git remote show origin //查看远程仓库信息
//git branch -d 清除本地分支
// git remote -v 查看当前仓库地址
// git remote add 添加仓库地址 set 是删除
// 使用 git fetch 拉取远程仓库信息(不会自动进行合并);
// 使用 git reset --hard origin/分支名 命令将远程仓库完全覆盖本地仓库。
//git revert
// git reset 命令会改变之前的版本记录,可能会导致不能提交到远程仓库;
// git revert命令只会撤销某个版本的代码,然后在当前分支增加一条版本新记录;
// git revert 只会撤销指定版本的代码,而不是指定版本后的所有版本。
//git reflog 恢复
//git rebase 与git merge 功能类似,可以记录版本变化,可以好修改撤销
// git merge 命令合并之后,版本记录会按照时间顺序,并自动产生一个merge branch的版本
//git cherry-pick 将另外的一个分支合并