面试中遇到的平常没注意的问题
1. for循环中let 声明的变量i 和 var 声明的变量i 有什么区别
- let 声明的是块级作用域;var 声明的是在全局范围内都有效
- let 声明的变量只在本轮循环有效;var 声明的全局范围有效,全局只有这一个i值,所以每次循环变量i的值都会发生改变
实例代码
var a = []
for (let i=0; i<10;i++){
a[i] = function(){
console.log(i)
}
}
console.log(a[6]()) // 6
var a = []
for (var i=0; i<10; i++) {
a[i] = function() {
console.log(i)
}
}
console.log(a[6]()) // 10
for循环还有一个特别的地方, 就是设置循环变量的部分是一个父作用域,循环体内部是一个单独的子作用域
例
for(let i = 0;i�<3;i++){
let i = 'abc'
console.log(i)
}
// abc
// abc
// abc
2.如何才能实现深复制(深拷贝)
使用递归的方式进行深拷贝
1 第一种递归方式,复制目标对象是函数参数
// 深拷贝函数 复制目标对象
function deepCopy(targetObject) {
let newObj = {}
if(typeof targetObject !== 'object'){
return targetObject
} else {
for(let property in targetObject){
newObj[property] = deepCopy(targetObject[property])
}
}
return newObj
}
2 序列化反序列化 (一句代码搞定,装逼爽歪歪)
let newObject = JSON.parse(JSON.stringify(targetObject))
3.实现一个function sum 达到以下目的
sum(1,2,3,4,5).valueOf() // 15
sum(1,2,3,4)(5).valueOf() // 15
sum(1,2,3)(4)(5).valueOf() // 15
sum(1,2)(3)(4)(5).valueOf() // 15
sum(1,2)(3,4)(5).valueOf() // 15
- 这道题考的是 函数 的柯里化
- valueOf 涉及到的是 js 的隐式转换
柯里化:(Currying) 又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。
1.接收单一参数,因为要携带不少信息,因此常常以回调函数的理由来解决
2.将部分参数通过回调函数等方式传入函数中
3.返回一个新函数,用于处理所有的想要传入的参数
概念我晚点查阅一下专业的书籍来补充,包括柯里化的好处这些
由浅入深的柯里化
// 普通的add函数
function add(x, y){
return x + y
}
// 柯里化后
function curryingAdd(x){
return function(y){
return x+y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
接着,补充几个隐式转换的小问题
function fn(){
return 20
}
console.log(fn + 10)
// 输出什么
function fn(){
return 20
}10
function fn() {
return 20
}
fn.toString = function() {
return 10
}
console.log(fn + 10)
// 输出什么
20
function fn() {
return 20
}
fn.toString = function() {
return 10
}
fn.valueOf = function() {
return 5
}
console.log(fn + 10)
// 输出什么
15
这里是JavaScript 中隐式转换的知识点
其中 valueOf 的优先级比 toString 要高
开始解这个题
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments)
// 在内部声明一个函数,利用闭包特性保存_args并收集所有的参数
var _adder = function() {
_args.push(...arguments);
return _adder
}
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回(valueOf 也可以,优先级比toString高)
_adder.toString = function() {
return _args.reduce(function(a,b){
return a+b;
})
}
_adder.valueOf = function() {
return _args.reduce(function(a,b){
return a+b;
})
}
return _adder
}
4.如何自己实现双向绑定
使用 Object.defineProperty
html 部分
<html>
<head></head>
<body>
<input type='text' id='test-input' />
<span id='test-label'></span>
<script>
(function(){
const obj = {}
const _input = document.getElementById('test-input')
const _label = document.getElementById('test-label')
Object.defineProperty(obj,'val', {
get: function(){
return obj
},
set: function(newValue){
_input.value = newValue;
_label.innerHTML = newValue
}
})
// 监听input的改值事件
_input.addEventListener('keyup', function(e){
obj.val = e.target.value
})
})()
</script>
</body>
</html>
5. react 中的key的作用,和为什么最好不要使用索引index作为key
和react的diff算法有关
- 当使用React,在某一个时间点,可以认为render()函数是在创建React元素树
- 在下一个状态或属性更新时,render()函数将返回一个不同的React元素树。
- React需要算出如何高效更新UI以匹配最新的树
- react 基于两点假设,实现了一个启发的O(n)算法
- 两个不同类型的元素将产生不同的树
- 开发者可以使用Key属性来提示那些子元素贯穿不同渲染是稳定的
所以使用key属性是为了区分相同类型的元素,避免重新渲染所有元素树来使用的
- 当子节点有key时,react使用Key来匹配原始树的子节点和随后树的子节点
- 这个key在兄弟元素中必须是惟一的
- 如果使用索引来作为Key, 如果元素顺序不变,效果不错,但是重新排序就会很慢
6.实现一个闭包,每调用一次就自增加1
const addSelf = (function(){
var num = 0;
return function(){
return num++
}
})()
6.盒子模型描述
标准盒子模型: 盒子的总宽度/总高度 = margin+padding+border+width/height
IE盒子模型: 盒子的总宽度/总高度 = marging+width/height
7. ajax 的事件是
ajaxStart()——————用于为ajax请求的ajaxStart事件绑定处理函数
ajaxStop()——————用于为ajax请求ajaxStop事件绑定处理函数
ajaxSend()——————用于设置当Ajax请求即将被发送时执行的回调函数
ajaxComplete()——————用于设置当Ajax请求完成(无论成功或失败)时执行的回调函数
ajaxSuccess()——————用于设置当Ajax请求成功完成时执行的回调函数
ajaxError()———————用于设置当Ajax请求失败时执行的回调函数
8. JavaScript 异步编程,如何解决回调地狱
- 拆解function
- 事件发布/监听模式
- Promise
- generator 函数
- async/await
- 拆解function // 缺点:缺少通用性
// 将各步拆解为单个的function
function getData(count){
get(`/sampleget?count=${count}`, data=>{
console.log(data)
})
}
function queryDB(kw){
db.find(`select * from sample where kw = ${kw}`, (err, res)=>{
getData(res.length)
})
}
function readFile(filepath){
fs.readFile(filepath, 'utf-8',(err, content)=>{
let keyword = content.substring(0, 5);
queryDB(keyword)
})
}
readFile('./sample.txt')
- 事件发布/监听模式 // event.emitter 一方面监听事件,事件发生时,进行相应回调操作; 另一方面,当某些操作完成后,通过发布事件触发回调
const events = require('events')
const eventEmitter = new events.EventEmitter();
// 订阅
eventEmitter.on('db',(err, kw)=>{
db.find(`select * from sample where kw = ${kw}`, (err, res)=>{
// 发布
eventEmitter.emit('get', res.length);
})
})
// 订阅
eventEmitter.on('get',(err,count)=>{
get(`/sampleget?count=${count}`, data=>{
console.log(data);
})
})
fs.readFile('./sample.txt', 'utf-8', (err, content)=>{
let keyword = content.substring(0, 5);
// 发布
eventEmitter.emit('db', keyword)
})
- Promise 链式调用
// 借助原生promise来改造需要用到的方法, 以 fs.readFile为例
// 对其他一些异步方法也可以参照这种方法进行改造
const readFile = function(filepath){
let resolve;
let reject;
let promise = new Promise((_resolve, _reject)=>{
resolve = _resolve;
reject = _reject;
})
let deferred = {
resolve,
reject,
promise
};
fs.readFile(filepath, 'utf-8', function(err, ...args){
if(err){
deferred.reject(err)
} else {
deferred.resolve(...args)
}
})
return deferred.promise
}
使用中就变成了
readFile('./sample.txt').then(content=>{
let keyword = content.substring(0, 5);
return queryDB(keyword)
}).then(res => {
return getData(res.length)
}).then(data => {
console.log(data)
}).catch(err => {
console.log(err);
})
- generator 函数 // 在function关键字后面添加*即可 通过 yield暂停; 通过next方法来恢复yield 后面的代码执行
- async/await 函数;相当于generator函数的语法糖,在async函数中可以使用await语句,await后一般是一个Promise对象
8. 隐式转换类问题 下面代码输出什么
console.log(1+"2"+"2") // 122
console.log(1+ +"2"+"2") // 32 这种隐式转换只有+和-才能转,* 和 / 皆要报错
console.log('A'-'B'+ 2) // NaN
console.log('A'-'B'+'2') // NaN2 字母和数字字符串的转换也只能用 +
9.以下js表达式返回false的是; 这个只有输出看,考隐式转换
1 == true;
''== false;
false == null; // false undefined == false; false == undefined 也是false
null==undefined;
9.关于屏幕尺寸标准
col-xs-* < 768 手机
col-sm-* >= 768 平板
col-md-* >= 992 桌面
col-lg-* >= 1200 大屏幕桌面
10. var a = b = 3 ===> var a = 3; b = 3; 变量声明提升是将var a;提升到最上面,赋值不会
11 什么是事件委托,事件委托的好处是什么?
事件委托就是利用事件冒泡机器指定一个事件处理程序,来管理某一类型的所有事件。
即: 利用冒泡的原理,给父元素绑定事件,用来监听子元素的冒泡事件,并找到是哪个子元素的事件
好处:只在内存中开辟了一块空间,避免对每个子元素添加事件监听器,节省资源同时减少了dom操作,提高性能
对于新添加的元素也会有之前的事件
12 用setTimeout来模拟setInterval();有什么区别
function simulateInterval(){
setTimeout(function(){
simulateInterval();
}, 1000)
}
setTimeout(simulateInterval, 1000)
区别是setTimeout是调用自身函数
13 垂直居中的方式
常用的
flex布局
父元素{
display: flex;
justify-content: center;
align-items: center;
}
万能居中
父元素{
position: relative;
}
子元素{
position: absoluted;
top: 50%;
left:50%;
transform: translate(-50%,-50%)
}
确定子元素高度的居中
父元素{
position: 除了static之外的定位
}
子元素{
position: absoluted;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
overflow:auto;
width: XX px;
height: XX px;
}
14 js中的new()到底做了什么?
- 创建了一个新的对象实例
- 将构造函数的作用域赋给了新对象实例(使this指向了这个新的对象实例)
- 调用构造函数(为新对象添加属性和方法)
- 返回新对象
15 js垃圾回收机制,它的存在有什么必要性(删除使用过不再使用的变量,释放内存),具体的垃圾回收方法有哪几种?各自的特点是什么?
垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
垃圾回收的方法有两种
1.标记清除法: 在函数声明一个变量的时候,就将这个变量标记为“进入环境”。而当变量离开环境时,则将其标记为“离开环境”。 垃圾回收器在运行时会给存储在内存中的所有变量都加上标记, 然后会去掉环境中的变量以及被环境中变量引用的的变量的标记。 在此之后再被标记的变量将被视为准备删除的变量,因为环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间
function test(){ var a = 10; // 被标记,进入环境 var b = 20; // 被标记, 进入环境 } test(); // 执行完毕之后, a、b又被标记离开环境,被回收
2.引用计数法
引用计数的含义是跟踪记录每个值被引用的次数。当生命了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1.如果同一个值又被赋给另一个变量,则该值的引用次数加1.相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1.当这个值的引用次数变成0时,则说明没有办法再范文这个值了,因而可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。当遇到循环引用的时候,函数的引用次数就不会为0,所以不会被垃圾回收器回收内存,会造成内存泄露
function test(){ var a = {}; // a的引用次数为0 var b = a; // a的引用次数加1, 为1 var c = a; // a 的引用次数再加1, 为2 var b = {} // a的引用次数减1, 为1 }
16 TCP与UDP区别总结
- TCP面向连接(如电话要先拨号建立连接); UDP是无连接的,即发送数据之前不需要建立连接
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按需到达;UDP尽最大努力交付,即不保证可靠交付
- UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性较高的通信或广播通信。
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP对系统资源要求较多,UDP对系统资源要求较少