一、箭头函数和普通函数有什么区别
(1)箭头函数比普通函数更加简洁
(2)箭头函数没有自己的this
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变
(3)箭头函数继承来的this指向永远不会改变
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
var id = 'Global';
let fun1 = () => {
console.log(this.id)
};
fun1(); // 'Global'
fun1.call({id: 'Obj'}); // 'Global'
fun1.apply({id: 'Obj'}); // 'Global'
fun1.bind({id: 'Obj'})(); // 'Global'
(5)箭头函数不能作为构造函数使用 (在JavaScript中,用new关键字来调用的函数,称为构造函数,构造函数首字母一般大写)
-
由于箭头函数时没有自己的this,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
-
构造函数是通过new关键字来生成对象实例,生成对象实例的过程也是通过构造函数给实例绑定this的过程,而箭头函数没有自己的this。创建对象过程,new 首先会创建一个空对象,并将这个空对象的proto指向构造函数的prototype,从而继承原型上的方法,但是箭头函数没有prototype。因此不能使用箭头作为构造函数,也就不能通过new操作符来调用箭头函数。
原型和原型链具体可看这篇文章JS的基础知识学习(二)(原型和原型链的基础理论)_七小山的博客-CSDN博客
函数 - prototype - 对象(函数的prototype)
对象 - 对象._proto -对象(函数大的prototype)
(6)箭头函数没有自己的arguments
arguments,它是js中函数内置的一个对象,而执行函数方法的实参中值都存储在arguments中;要想获取到这些实参,就需要像 数组 一样,用下标/索引来定位到每个值上面,但是又不能说它是一个数组,因为它里面还有其他的属性,如callee; 并且不能对它使用shift、push、join等方法。而没有传递值的命名参数将会被自动赋予undefined;
arguments.length----参数个数
arguments.callee()---调用自身
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
二、 讲讲promise
1、 promise的3种状态和状态转换
(1)promise状态
pending(等待) fullfilled(已完成) rejected(已拒绝)
(2)先看下三种状态的产生
pending状态的Promise
const promise1 = new Promise((resolve,reject) => { }) console.log(promise1);
fulfilled状态的Promise
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }) }) console.log(promise1);
rejected状态的Promise
const promise = new Promise((resolve, reject) => { setTimeout(() => { reject(); }) }); console.log(promise);
(3)从pending状态到fulfilled状态的变化过程
resolve()后状态从pending变为了fulfilled
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolve前Promise的状态:',promise);
resolve();
console.log('resolve后Promise的状态:',promise);
})
});
(4)从pending状态到rejected状态的变化过程
reject()后状态从pending变为了rejected
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("Promise的状态:reject前:",promise);
reject();
console.log("Promise的状态:reject后:",promise);
})
});
未调用resolve或者reject时候处于pending状态,调用resolve后处于fullfilled状态,调用reject后处于rejected状态。如果在pending状态时候,执行任务抛出错误,则变成reject状态。 状态变化后,会执行通过then注册的回调。执行顺序和调用then方法的顺序相同。 调用then方法时候,如果状态是pending则注册回调,等到状态改变时候执行,如果状态已经改变则执行相应的回调。
状态一旦改变,就无法再次改变状态,这也是它名字 promise-承诺 的由来,一个promise对象只能改变一次
const p = new Promise((resolve, reject) => {
resolve('test');
});
p.then(
data => console.log(1, 'resolve', data),
data => console.log(1, 'reject', data)
);
p.then(
data => console.log(2, 'resolve', data),
data => console.log(2, 'reject', data)
);
// 执行结果
1 "resolve" "test"
2 "resolve" "test"
const p = new Promise((resolve, reject) => {
throw new Error('test-error');
// 由于抛出错误,promise状态已经改变为rejected,再调用resolve将不会改变promise状态
resolve('test');
});
p.then(
data => console.log(1, 'resolve', data),
data => console.log(1, 'reject', data)
);
p.then(
data => console.log(2, 'resolve', data),
data => console.log(2, 'reject', data)
);
// 执行结果
1 "reject" Error: test-error
2 "reject" Error: test-error
2、 Promise中回调函数是同步的还是异步的?
-
回调函数是 什么
一个函数A,作为另一个函数B的参数,那么函数A就被称为回调函数
-
promise本身 —同步
let obj=new Promise((res,rej)=>{
console.log('这是promise对象');
});
console.log('哼哼');
-
promise的回调函数then --异步
let obj=new Promise((res,rej)=>{
console.log('这是promise本身');
res('这是回调函数then的值')
});
obj.then((res) => {
console.log(res);
});
console.log('哼哼');
3、then的链式调用是同步的还是异步的?
// 链式调用
p.then(value => {
}).then(value => {
});
同步
4、catch和then
catch方法和then方法的reject回调用法相同,如果这时候任务处于rejected状态,则直接执行catch,catch的参数就是reject的reason;如果任务处于pending状态,则注册catch回调,等到状态变成rejected时候再执行。
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数
p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // 等同于 p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log("rejected:", err));
三、 apply、bind、call
说下call、apply、bind方法的作用和区别?
具体可以看这篇文章call,apply,bind三者的区别_call和apply及bind三者的区别_七小山的博客-CSDN博客
-
call、apply、bind都是用来改变this指向的,
-
call和apply调用时候立即执行,bind调用不会立即执行而是会返回新的函数。
-
当需要传递参数时候,call直接写多个参数,apply将多个参数写成数组。
-
bind在绑定时候需要固定参数时候,也是直接写多个参数
-
call和apply第一个参数obj正常都是传入的this,若是没有传,则默认是windows
let obj = { name:'张三', age:18, func:function(){ console.log(this.name + "年龄" + this.age); } } let obj2 = { name:'李四', age:20 } obj.func.call(obj2) //李四年龄20 obj.func.apply(obj2); //李四年龄20 obj.func.bind(obj2)(); //李四年龄20
这三个结果都一样
bind
后面多一个括号是因为bind
返回值是一个函数加上()编程立即执行函数
四、 typescript泛型
泛型,顾名思义,就是可以适用于多个类型,使用类型变量比如T帮助我们捕获传入的类型,之后我们就可以继续使用这个类型。
本质是参数化类型,通俗的将就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和函数的创建中,分别成为泛型类,泛型接口、泛型函数
(1)泛型-泛型函数
function fn<Type>(value: Type): Type { return value }
// 上面的Type只是一个名字而已,可以改成其他的
function fn<T>(value: T): T { return value }
语法:在函数名称的后面写 <>(尖括号),尖括号中添加类型变量,比如此处的 Type。
类型变量 Type,是一种特殊类型的变量,它处理类型而不是值
该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
因为 Type 是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型
类型变量 Type,可以是任意合法的变量名称
调用泛型函数的格式
const num = fn<number>(10) const str = fn<string>('a')
这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全
(2)泛型-类型推断简化函数调用
function fn<T>(value: T): T { return value } // 省略 <number> 调用函数 let num = fn(10) let str = fn('a')
-
在调用泛型函数时,可以省略 <类型> 来简化泛型函数的调用
-
此时,TS 内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量 Type 的类型
-
比如,传入实参 10,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型
-
推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读
-
说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数
(3)总结
定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定,此时泛型便能够发挥作用。
五、 JavaScript 防抖和节流
1、防抖与节流是什么
本质上是优化高频率执行代码的一种手段
如:浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能
为了优化体验,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率
-
防抖:就是一定时间内,只会执行最后一次任务;
-
节流:就是一定时间内,只执行一次 ;
定义
(1)防抖:n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
防抖具体指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次。假如我们设置了一个等待时间 3 秒的函数,在这 3 秒内如果遇到函数调用请求就重新计时 3 秒,直至新的 3 秒内没有函数调用请求,此时执行函数,不然就以此类推重新计时
防抖实现原理就是利用定时器,函数第一次执行时设定一个定时器,并且通过闭包缓存起来,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。
(2)节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
函数节流指的是某个函数在一定时间间隔内(例如 3 秒)只执行一次,在这 3 秒内 无视后来产生的函数调用请求,也不会延长时间间隔。3 秒间隔结束后第一次遇到新的函数调用会触发执行,然后在这新的 3 秒内依旧无视后来产生的函数调用请求,以此类推。函数节流非常适用于函数被频繁调用的场景,例如:window.onresize() 事件、mousemove 事件、上传进度等情况。
实现原理就是通过一个布尔类型变量来判断是否可执行回调,当变量为true时,生成一个定时器,同时将变量取反通过闭包保存起来,当定时器执行完回调后,再将变量变为true,在变量为期false间,调用节流函数不会生成定时器。
2、防抖案例
(1)搜索
<button id="debounce">搜索!</button>
<script>
const dd = document.querySelector("#debounce");
dd.addEventListener("click", debounce());
function debounce() {
let timer; // 使用闭包,多次调用都能访问到同一个变量,而不是生成新的变量
return function () {
// 每次当用户点击/输入的时候,把前一个定时器清除
clearTimeout(timer);
timer = setTimeout(() => {
// 然后创建一个新的 setTimeout,
// 这样就能保证点击按钮后的 interval 间隔内
// 如果用户还点击了的话,就不会执行 定时器里面的内容
// 需要防抖的操作...
console.log("防抖成功!");
}, 500);
};
}
</script>
(2)滚动
let timer;
window.onscroll = function () {
console.log(
"上一个定时器的序号" +
timer +
"滚动" +
document.documentElement.scrollTop
);
clearTimeout(timer);
timer = setTimeout(function () {
//滚动条位置
let scrollTop =
document.body.scrollTop || document.documentElement.scrollTop;
console.log("滚动条位置:" + scrollTop);
}, 500);
};
3、节流案例
(1)搜索点击
<button id="throttle">点我节流!</button>
<script>
$('#throttle').on('click', throttle());
function throttle(fn) {
let flag = true;
// 使用闭包,方法多次调用都能访问到同一个变量,而不是生成新的flag变量
return function () {
if (!flag) { return; }
flag = false;
setTimeout(() => {
console.log("节流成功!");
flag = true;
}, 1000);
};
}
</script>
4、总结
(1)防抖
单位时间内,频繁触发事件,只执行最后一次
典型场景:搜索框搜索输入
代码思路是利用定时器,每次触发先清掉以前的定时器(从新开始)
(2)节流
单位时间内,频繁触发事件,只执行一次
典型场景:高频事件快速点击、鼠标滑动、resize事件、scroll事件
代码思路也是利用定时器,等定时器执行完毕,才开区定时器(不要打断)