1 js的预解析问题
题目
function fn(a,c){
console.log(a); //function a()
var a =123;
console.log(a); //变量的值123
console.log(c); //function c()
function a(){}
if(false){
var d = 678;
}
console.log(d); // 没有声明 undefined
console.log(b); // undefined
var b = function(){}
console.log(b);// function (){}
function c(){}
console.log(c); // function c(){}
}
fn(1,2);
这道题考察的是js变量的预解析。
步骤是:
- 创建ao对象
- 找形参和变量声明 作为ao对象的属性名 值是undefined
- 实参和形参统一
- 找到函数声明 覆盖变量的声明
对于问题来说第一步 创建ao对象
ao:{
}
找形参和变量声明 作为ao对象的属性名 值是undefined
ao:{
a: undefined,
c: undefined,
b: undefined,
d: undefined
}
实参和形参统一 传入的参数是1和2 所以把a c的值换成传入的值
ao:{
a: 1,
c: 2,
b: undefined,
d: undefined
}
最后 找到函数声明 覆盖变量的声明
ao:{
a: function(){},
c: function(){},
b: undefined,
d: undefined
}
2 有关this的问题
在函数中中直接使用
function get (content){
console.log(content)
}
get('hello')
get.call(window,'hello')
get(‘hello’) 的写法 本质上来说是get.call(window,‘hello’)的语法糖
函数作为对象的方法被使用(谁调用我 我就指向谁)
var person ={
name='',
run(){
console.log(this.name+'在跑步');
}
}
person.run();
person.run.call(pserson)
面试题
var name =222
var a ={
name:111,
say(){
console.log(this.name);
}
}
var fun = a.say
fun() //222 属于第一种情况函数直接调用 fun.call(window)
a.say() //111 属于第二种情况 a.say.call(a)
var b ={
name: 333,
say(fun){
fun() //本质上和 fun接收a.say再通过fun()执行类似
}
}
b.say(a.say); //222 属于第一种情况函数直接调用 fun.call(window)
b.say = a.say
b.say() //333 属于第二种情况 b.say.call(b)
3 箭头函数中的this
-
箭头函数的this是在定义函数的时候绑定,而不是在执行函数的时候绑定。
-
箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
-
建箭头函数的this是定义函数的时候绑定
var x = 11; var obj ={ x: 22, say: ()=>{ console.log(this.x); //这里的外层指的并不是obj { } obj.say(); // 11
-
所谓的定义时候绑定,就是this是继承自父执行上下文中的this,比如这里的箭头函数中的this.x,箭头函数本身与say平级,也就是箭头函数本身所在的对象为obj,而obj的父执行上下文就是window,因此这里的this.x实际上表示的是window.x ,因此输出的是11。
var obj = { birth:1990, getAge: function (){ var b = this.birth; var fn = () => new Date().getFullYear() - this.birth; return fn(); } } obj.getAge();
-
例子中箭头函数本身是在getAge方法中定义的,因此,getAge方法的父执行上下文是obj,因此这里的this指向则为obj对象
4 深拷贝 浅拷贝
深拷贝浅拷贝与赋值的区别
- 当我们把一个对象赋值给一个新的变量时,赋值的其实是对对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储内存的内容,因此,两个对象是联动的。
- 浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享一块内存,会互相影响、
- 深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
浅拷贝的实现方式
- Object.assign()
- lodash 里面的 _.clone
- …展开运算符
- Array.prototype.concat
- Array.proptype.slice
深拷贝的实现
- JSON.parse(JSON.stringify())
- 递归操作
- cloneDeep
5 防抖函数
当持续触发事件 一定时间内没有再触发事件 事件处理函数才会执行一次
如果设定的时间到来之前 又一次触发了事件 就重新开始延时
连续输入的时候不会输出,当一秒以上为输入时,打印输入结果
const input = document.getElementById('input');
let timer ;
input.addEventListener('keyup',(e)=>{
clearTimeout(timer);
timer = setTimeout(() => {
console.log(e.target.value);
}, 1000);
})
采用闭包的方式实现
const input = document.getElementById('input');
let timer ;
const deounce=(delay)=>{
let timer;
return (value)=>{
clearTimeout(timer)
timer = setTimeout(() => {
console.log(value);
}, delay);
}
}
const getV = deounce(1000)
input.addEventListener('keyup',(e)=>{
getV(e.target.value)
})
6 节流函数
当持续触发事件的时候,保证一段时间内,只调用一次事件处理函数,一段时间内只做一件事
经典的案例就是鼠标不断点击,规定在n秒内多次点击只有一次生效
{
const btn = document.getElementById('button');
const jiuliu = (delay) =>{
let timer;
return (backcall)=>{
if(!timer){
timer = setTimeout(() => {
backcall();
timer=null
}, delay);
}
}
}
const a = jiuliu(2000)
btn.addEventListener('click',()=>{
a(()=>{
console.log(Math.random());
})
})
}
上面的例子就是用闭包形成的节流函数,两秒内只会触发一次函数
7 js作用域
作用域的深层次理解
执行器的上下文
- 当函数代码执行的前期 会创建一个执行期上下文的内部对象AO(作用域)
- 这个内部的对象是预编译的时候创建出来的 因为当函数被调用的时候 会先进行预编译
- 在全局代码的前期会创建一个执行器的上下文的对象GO
函数作用域预编译 - 创建ao对象 AO{}
- 找形参和变量声明 将变量和形参名 当做AO对象的属性名值为undefined
- 形参实参想统一
- 在函数体里面找函数声明 值赋予函数体
全局作用域的预编译 - 创建 GO对象
- 找变量声明 将变量名作为GO对象的属性名 值是undefined
- 找函数声明 值赋予函数体
8 arguments是什么
接收参数
function a() {
console.log(arguments);
}
a(1,2,3);
是一个伪数组 可以通过[…]展开符的方式转换为真正的数组
箭头函数没有arguments
9那些操作会造成内存泄漏
- 闭包
- 意外的全局变量
- 没有清除的定时器
- 脱离dom的引用
10 手写map
const array = [1,2,3];
function map(arr,callback) {
let newArr = []
for(let i =0 ;i<arr.length;i++){
newArr = [...newArr,callback(arr[i],i)]
}
return newArr;
}
const a = map(array,(value,index)=>{
return value*2
})
console.log(a);
11 单例模式
定义: 只有一个实例 可以全局访问
主要解决: 一个全局使用的类,频繁的创建和销毁
何时使用:当你想控制实例的数目,节省系统化资源的使用
如何实现:判断系统是否已经有这个单利 如果有则返回 没有则创建
单利模式的优点:内存中只要一个实例 减少了内存的开销 尤其是频繁的创建和销毁实例(比如说是首页页面的缓存)
使用场景 : 全局的缓存 弹窗