目录
一、数据类型转换
1.1 Number()
有3个函数可以将非数值转换为数值:Number()、parseInt()、parseFloat()。 Number() 是转型函数,可用于任何数据类型。parseInt()、parseFloat() 主要用于将字符串转换为数值
Number() 转换规则:
- undefined NaN
- null 0
- 布尔值 true为1,false为0
- 字符串
空字符串,空格字符串转为0
非空字符串,并且里面包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值,因此,Number("1")返回1,Number("123")返回123,Number("011")返回11(忽略前面的0)
如果字符串包含有效的浮点值格式如“1.1”,则会转换为相应的浮点值(同样,忽略前面的0)
如果字符串中包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值
如果字符串包含除上述情况之外的,其余都返回NaN
- 数字(值) 直接返回原来的数字(值)
- 对象
对象,调用 valueOf() 方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString() 方法,再按照转换字符串的规则进行转换
考虑到用 Number() 函数转换字符串时相对复杂,通常在需要得到整数时可以优先使用 parseInt(),因为它更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换,如果第一个字符不是数值字符、加号、减号,parseInt()就会立即返回NaN,这意味着空字符串也会返回NaN(Number()返回0)。如果第一个字符是数值字符,比如“1234false”会被转换为“1234”,因为“false”会被完全忽略,类似的“22.5”会被转换为“22”,因为小数点不是有效的整数字符
1、对象、函数转成NaN
2、空数组转为0,数组里只有一个数据并且这个数据能转成数字,则转成对应的数字,其它都转成NaN
parseInt() 也可以接收第二个参数,用于指定底数(进制数):parseInt("0xAF", 16)
parseFloat() 工作方式跟 parseInt() 类似,都是从位置 0 开始检测每个字符。同样它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就是无效的,此时字符串的剩余字符都会被忽略,它始终忽略字符串开头的0,十六进制数值始终返回0,因为它只解析十进制数值,因此不能指定底数。如果字符串表示正数,则parseFloat() 返回整数
parseFloat("1234blue") // 1234 按整数解析
parseFloat("0xA") // 0
parseFloat("22.5") // 22.5
parseFloat("0908.5") // 908.5
parseFloat("3.125e7") // 31250000
示例:
<script>
console.log(
Number(undefined), //NaN
Number(null), //0
Number(true), //1
Number(false), //0
Number(''), //0
Number(' '), //0
Number('2'), //2
Number({}), //NaN
Number(function() {}), //NaN
Number([]), //0
Number(['2']), //2
Number(['2', '1']), //NaN
);
</script>
1.2 String()
1、基本数据类型、null、undefined的结果就是给数据加上引号变成字符串
2、对象
1、数组的结果为把所有中括号去掉,外面加个引号
2、对象的结果为'[object Object]'
3、函数的结果为在函数整体外面加个引号
示例:
<script>
console.log(
String(null),
String([1, ['a'], 2]),
String({}), //[object Object]
String(function() {}), //function(){}
)
</script>
1.3 Boolean()
1、undefined false
2、null false
3、数字
+0、-0、NaN转布尔值的结果为false,其它的转布尔值的结果为true
4、布尔值 转为对应的值
5、字符串
空字符串转布尔值的结果为false,其它(包括空格字符串)的都转成true
6、对象转布尔值都是 true
示例:
<script>
console.log(
Boolean(''), //false
Boolean(' '), //true
Boolean([]), //true
Boolean({}), //true
Boolean(function() {}), //true
)
</script>
1.4 Object类型
ECMAScript中的对象其实就是一组数据和功能的集合。对象通过new操作符后跟对象类型的名称来创建:
- let obj = new Object()
- 如果没有参数,可以省略括号(let obj = new Object),但是不推荐
每个 Object 实例都有如下属性和方法:
constructor:用于创建当前对象的函数。在上面的例子中,这个属性就是 Object() 函数
hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty("name"))或符号
isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型
propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用 for-in 语句枚举。与 hasOwnProperty() 一样,属性名必须是字符串
toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
toString():返回对象的字符串表示
valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString() 的返回值相同
1.5 转换对象数据的背后原理
valueOf() 返回对象对应的原始值
toString() 返回对象的字符串表现形式
调用结果:
1、数组
valueOf 数据本身(对象形式)
toString 去掉中括号,外面加个引号
2、函数
valueOf 数据本身(对象形式)
toString 在数据外面加了个引号
3、对象
valueOf 数据本身(对象形式)
toString "[object Object]"
示例:
<script>
var data = [{
type: '数组',
value: [1, 2]
}, {
type: '函数',
value: function() {}
}, {
type: '对象',
value: {
a: 10
}
}, ]
for (var i = 0; i < data.length; i++) {
console.log(
data[i].type + 'valueOf的结果为:',
data[i].value.valueOf()
)
console.log(
data[i].type + 'toString的结果为:',
data[i].value.toString()
)
}
</script>
1.5 Number参数为对象的转换原理
1、调用对象的valueOf方法。如果返回原始类型的值,再使用Number函数,不再进行后续步骤
2、如果valueOf方法返回的还是对象,则调用toString方法
3、如果toString方法返回原始类型的值,则对该值使用Number方法,不再进行后续步骤
4、如果toString方法后返回的是还是对象,就报错(一般不会出现)
示例:
<script>
//Number()方法的参数为对象时背后的步骤
obj.valueOf = function() {
alert('你调用了valueOf方法');
// return 'kaivon';
//return 88;
return {
a: 12
};
}
obj.toString = function() {
alert('你调用了toString方法');
return '[object Object]';
//return {}
}
console.log(Number(obj)); //NaN
</script>
1.6 String参数为对象的转换原理
1、调用对象的toString方法。如果返回原始类型的值,再使用String函数,不再进行后续步骤
2、如果toString方法返回的还是对象,再调用对象的valueOf方法
3、如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤
4、如果valueOf方法返回的是还是对象,就报错(一般不会出现)
示例:
<script>
var obj = {
a: 12
};
//String()方法的参数为对象时背后的步骤
obj.valueOf = function() {
alert('你调用了valueOf方法');
// return 'kaivon';
return {
a: 12
};
}
obj.toString = function() {
alert('你调用了toString方法');
return '[object Object]';
//return {}
}
console.log(String(obj));
</script>
总结:
1、对象转数字,先调valueOf再调toString,所以大部分对象转数字都是NaN
2、对象转字符串,一般只会调用toString
二、类数组
组成部分:属性要为索引(数字)属性;必须要有length属性;最好加上push
<script>
var obj = {
"0": 'a',
"1": 'b',
"2": 'c',
"length": 3,
"push": Array.prototype.push
};
var newObj = obj.push('d');
console.log(obj);
</script>
示例2:
<script>
//类数组
//组成部分:属性要为索引(数字)属性;必须要有length属性;最好加上push
var obj = {
"2": 'a',
"3": 'b',
"length": 2,
"push": Array.prototype.push
};
obj.push('c');
obj.push('d');
</script>
示例2:
<script>
//类数组
//组成部分:属性要为索引(数字)属性;必须要有length属性;最好加上push
var obj = {
"1": 'a',
"2": 'c',
"3": 'd',
"length": 3,
"push": Array.prototype.push
};
obj.push('b');
</script>
示例3:
<script>
//类数组
//组成部分:属性要为索引(数字)属性;必须要有length属性;最好加上push
var obj = {
"1": 'a',
"2": 'c',
"3": 'd',
name: "jack",
age: 14,
sex: 'male',
length: 7,
push: Array.prototype.push,
splice: Array.prototype.splice
};
for (var prop in obj) {
console.log(obj[prop]);
}
</script>
三、立即执行函数
执行完后就会被立即销毁
3.1 最常用的写法
a.(function () {
//块级作用域
})();
b.(function () {
//块级作用域
}());
当然,立即执行函数也可以进行传参操作:
<script>
(function(a, b, c) {
console.log(a + b + c);
})(1, 4, 9);
</script>
同理,立即执行函数也可以拥有返回值:
<script>
var num = (function(a, b, c) {
var d = a + b + c ** 2;
return d;
})(1, 4, 9);
</script>
3.2 注意
- 只有表达式才能被执行符号执行
示例1:
<script>
//不叫表达式,而是函数声明
function test() {
console.log('123');
}()
</script>
示例2:
<script>
//表达式
var num = function test() {
console.log('123');
}()
</script>
- 能被执行符号执行的表达式,该函数的名字就会被自动忽略
示例1:
<script>
var num = function () {
console.log('123');
}
</script>
示例2:
<script>
var num = function() {
console.log('123');
}();
</script>
示例3:
<script>
+ function test() {
console.log('123');
}();
</script>
案例1:
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
const btns = document.querySelectorAll("button");
for (var i = 0; i < btns.length; i++) {
const btn = btns[i];
(function fn(index) {
btn.onclick = function() {
console.log(index); // 0 1 2
}
})(i);
}
</script>
案例2
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
const btns = document.querySelectorAll("button");
for (let i = 0; i < btns.length; i++) {
const btn = btns[i];
btn.onclick = function fn() {
console.log(i); // 0 1 2
};
}
</script>
四、函数作用域
[[scope]]:每个javascript函数都是一个对象,对象中有些属性我们可以访问,但是有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中之一;
[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把 这种链式链接叫做作用域链
执行期上下文:当函数执行时(函数执行的前一刻),会创建一个称为执行期上下文的内部对象。
一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,
所以多次调用一个函数会导致创建多个执行上下文,当含疏忽执行完毕,它所产生的执行上下文被销毁。
查找变量:从作用域的顶端依次向下查找。
4.1 作用域
- 全局作用域:整个程序本身
- 函数作用域(局部作用域):作用在函数大括号内
a.全局变量:在全局作用域中通过var声明的变量或者直接写出变量的变量名(形参除外)
b.局部变量:在函数内部通过var来声明的变量
注意:
- 全局变量在整个程序范围内都能使用
- 局部变量只能在声明的函数内使用
<script>
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
</script>
五、预编译
5.1 铺垫知识
一切定义在全局的变量都归window所有,window就是全局:
<script>
var a = 10;
console.log(a);
console.log(window.a);
</script>
任何变量未经声明就赋值也归window所有:
<script>
a = 10;
console.log(a);
console.log(window.a);
</script>
变量 只提升声明
<script>
console.log(a);
var a = 10;
</script>
函数 整体提升
<script>
demo();
function demo() {
var a = 15;
console.log(a);
}
</script>
5.2 预编译
预编译发生在函数执行的前一刻
过程:
a.创建AO对象(Activation Object 执行期上下文)
AO{
}
b.找形参和变量声明,将变量和形参名作为OA属性名,值为undefined
AO{
a:undefined
b:undefined
}
c.将实参值和形参统一
AO{
a:1
b:undefined
}
d.在函数体里找到函数声明,值赋予函数体
函数体里找到函数声明
AO{
a:1
b:undefined
d:
}
值赋予函数体
AO{
a:function(){}
b:undefined
d:function d(){}
}
<script>
function fn(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
fn(1);
</script>
示例1:
<script>
function test(a, b) {
console.log(a); //1
c = 0;
var c;
a = 3;
b = 2;
console.log(b); //2
function b() {};
function d() {}
console.log(b); //2
}
test(1);
</script>
示例2:
<script>
function test(a, b) {
console.log(a); //fn
console.log(b); //undefined
var b = 234;
console.log(b); //234
a = 123;
console.log(a); //123
function a() {}
var a;
b = 345;
var b = function() {}
console.log(a); //123
console.log(b); //fn
}
test(1);
</script>
示例3:
<script>
function test(test) {
console.log(b); //undefined
if (a) {
var b = 100;
}
console.log(b); //undefined
c = 234;
console.log(c); //234
}
var a;
test();
a = 10;
console.log(c); //234
</script>
六、递归
递归函数通常的形式就是一个函数通过名称调用自己:
<script>
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(n - 1);
}
}
</script>
但是对于以上案例,如果把该函数赋值给其它变量,就会出问题:
<script>
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4));
</script>
解析:这里把factorial()函数保存在了变量anotherFactorial中,然后将factorial设置为null,于是只保留了一个对原始函数的引用。而在调用anotherFactorial()函数时,要递归调用factorial(),但因为它已经不是函数了,因此会出错。
在写递归函数时使用:arguments.callee可以避免该问题,arguments.callee是一个指向正在执行函数的指针,因此可以在函数内部递归调用:
<script>
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
var anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(4));
</script>
因此在编写递归函数时,arguments.callee是引用当前函数的首选。
不过,在严格模式下运行的代码是不能访问arguments.callee的,因为访问会出错。此时可以使用命名函数表达式来达到目的:
<script>
var factorial = (function f(num) {
if (num <= 1) {
return 1;
} else {
return num * f(num - 1);
}
})
</script>
这里创建了一个命名函数表达式f(),然后将其赋值给变量factorial,即使把函数赋值给了另一个变量,函数表达式的名称f也不变,因此递归调用不会有问题。这个模式在严格模式下和非严格模式下都可以使用。
七、this用法
用法:
0.全局作用域中 this 指向:window对象
1.html事件中:指向window对象
2.dom0事件中:指向函数的调用者
3.dom2事件中:非IE下,指向函数的调用者;IE下,指向window对象
非IE下:
<script>
var but = document.querySelector('button');
but.addEventListener('click', function() {
console.log(this);
})
</script>
<button>点我</button>
IE下:
<button>点我</button>
<script>
var but = document.querySelector('button');
but.attachEvent('onclick', function() {
console.log(this);
})
</script>
4.间隔调用和延迟调用:指向window对象
5.call/apply:第一个参数是谁,this就指向谁
6.闭包:闭包中this指向window对象
7.自执行函数:this指向window对象
<script>
(function() {
console.log(this);
}());
</script>
8.this在正常函数中(非箭头函数),谁调用了函数,this就指向谁
// 普通函数:window,调用者
function fn() {
console.log(this); // window
}
fn();
9. 构造函数中 this 指向实例化对象
// 构造函数:实例化对象
function Perosn(uname, age) {
this.uname = uname;
this.age = age;
console.log(this);
}
let obj1 = new Perosn("阿飞", 22);
console.log(obj1);
let obj2 = new Perosn("李寻欢", 23);
console.log(obj2);
10. 在方法中 this 指向调用者
let obj = {
uname : '张三丰',
age : 22,
fei : function () {
console.log(this);
}
}
obj.fei();
11 .箭头函数
箭头函数中的 this
与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this
!箭头函数中访问的 this
不过是箭头函数所在作用域的 this
变量。
<script>
console.log(this); // 此处为 window
// 箭头函数
let sayHi = function() {
console.log(this); // 该箭头函数中的 this 为函数声明环境中 this 一致
}
// 普通对象
let user = {
name: '小明',
// 该箭头函数中的 this 为函数声明环境中 this 一致
walk: () => {
console.log(this);
},
sleep: function () {
let str = 'hello';
console.log(this);
let fn = () => {
console.log(str);
console.log(this); // 该箭头函数中的 this 与 sleep 中的 this 一致
}
// 调用箭头函数
fn();
}
}
// 动态添加方法
user.sayHi = sayHi;
// 函数调用
user.sayHi();
user.sleep();
user.walk();
</script>
在开发中【使用箭头函数前需要考虑函数中 this
的值】,事件回调函数使用箭头函数时,this
为全局的 window
,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:
<script>
// DOM 节点
let btn = document.querySelector('.btn');
// 箭头函数 此时 this 指向了 window
btn.addEventListener('click', () => {
console.log(this);
})
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener('click', function () {
console.log(this);
})
</script>
同样由于箭头函数 this
的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
<script>
function Person() {
}
// 原型对像上添加了箭头函数
Person.prototype.walk = () => {
console.log('人都要走路...');
console.log(this); // widow
}
let p1 = new Person();
p1.walk();
</script>
注意:
- 严格模式指向 undefined
- 有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁
-
this 指向和函数在哪儿调用没关系,只和谁在调用有关
-
没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window
最后上一个案例:
<script>
const obj = {
fn1: function() {
console.log(this);
},
fn2: test
}
function test() {
console.log(this);
setTimeout(function() {
console.log(this);
})
}
const res = obj.fn1;
res();
obj.fn2();
</script>
八、闭包
闭包(closure):一个函数作用域有权访问另一个函数作用域中变量的函数
<script>
// 闭包: 我们fun 这个函数作用域 访问了另外一个函数 fn 里面的局部变量 num
function fn() {
var num = 10;
function fun() {
console.log(num); // 10
}
fun();
}
fn();
</script>
闭包有三个特性:
函数嵌套函数。
函数内部可以引用外部的参数和变量。
参数和变量不会被垃圾回收机制回收。
闭包的好处:
希望一个变量长期存储在内存中。
避免全局变量的污染。
私有成员的存在。
闭包的缺点:
常驻内存,增加内存使用量。
使用不当会很容易造成内存泄露。
8.1 闭包原理
描述:闭包就是将函数内部和函数外部连接起来的一座桥梁
作用:函数内部的局部变量传递到了函数外部
例子:
<script>
// 写法1
function func() {
var num = 999;
return function func2() {
console.log(num);
}
}
func()();
// 写法2
function func() {
var num = 999;
function func2() {
console.log(num);
}
// 将func2当做一个参数返回
return func2;
}
var result = func();
// result要加上括号才能执行 func2()函数
result();
</script>
8.2 闭包用途
闭包在js中可以使用在很多地方,但是闭包最常见的用途会使用在如下两个方面:
(1)通过闭包,可以在函数外部读取函数内局部变量的值
(2)让局部变量始终生存在内存当中,避免被垃圾回收机制杀死
例子:
<script>
function func() {
var num = 999;
// 闭包
nAdd = function() {
num += 1;
}
// 闭包
return function() {
console.log(num);
};
}
//func()-->拿到的其实是:
//function() {
// console.log(num);
// };
var result = func();
//result()-->其实就是对:
//function() {
// console.log(num);
// };进行调用
result(); //999
//nAdd()-->其实就是执行:
//nAdd = function() {
// num += 1;
// }但是由于没有返回值,因此不会有任何输出,
//但是在内存中num实际上现在为1000
nAdd();
//result()-->其实就是对:
//function() {
// console.log(num);
// };进行调用
result(); //1000
</script>
8.3 作用
1.实现公有变量
函数累加器
<script>
function add() {
var num = 0;
function a() {
console.log(++num);
}
return a;
}
var myAdd = add();
myAdd();
myAdd();
myAdd();
</script>
2.可以做缓存(存储结构)
<script>
function test() {
var food = "apple";
var obj = {
eatFood: function() {
if (food != "") {
console.log("I am eating " + food);
food = '';
} else {
console.log("There is noting");
}
},
pushFood: function(myFood) {
food = myFood;
}
}
return obj;
}
var person = test();
person.eatFood();
person.eatFood(); //There is noting
person.pushFood('banana');
person.eatFood();
</script>
3.可以实现封装,属性私有化(后续补充)
4.模块化开发,防止污染全局变量(后续补充)
注意事项:
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致大量内存泄露;解决方法:在退出函数前,将不使用的局部变量全部删除
(2)由于闭包的实际调用者都是window,因此所有闭包中的this均指向了window,如果非要在函数中通过this来调用函数中的变量,则记得使用apply或者call变更闭包中this的指向
(3)闭包会在父函数外部,改变父函数内部的值,所以如果把父函数当做对象,那么闭包就好比是对象的共用方法,而内部变量则就是对象的私有 属性,此时不要随便改变父函数内部变量的值
例子1:
<script>
var name = 'The Window';
var obj = {
name: 'My Object',
getNameFunc: function() {
return function() {
return this.name;
};
}
};
console.log(obj.getNameFunc()());
</script>
例子2:
<script>
function personPurse() {
//私有的
var money = 1;
return function(changeMoney) {
money = changeMoney;
console.log(money);
}
}
var zhuanzhang = personPurse();
zhuanzhang(5000);
</script>
//作为返回值
function fn() {
let num = 10;
return function() {
return num;
}
}
let fnc = fn();
console.log(fnc());
//函数赋值
var fn1;
function fn() {
let num = 20;
fn1 = function() {
return num;
}
}
fn(); //赋值
console.log(fn1()); //调用
//函数参数
function fn() {
let num = 20;
return function callback() {
return num;
}
}
//执行函数,将返回值(callback函数)赋值给fn1
let fn1 = fn();
function fn2(f) {
//将函数作为参数传入
console.log(f()); //执行函数,并输出
}
fn2(fn1);
//IIFE(自执行函数)
(
function() {
let num = 30;
var fn = function() {
return num;
}
//直接在自执行函数里面调用fn2,将fn1作为参数传入
fn1(fn);
}
)()
function fn1(f) {
//将函数作为参数传入
console.log(f()); //执行函数,并输出
}
//循环赋值
//每秒执行1次,分别输出1-10
for (let i = 1; i <= 10; i++) {
(function(j) {
//j来接收
setTimeout(function() {
console.log(j);
}, j * 1000);
})(i) //i作为实参传入
}
//迭代器(执行一次函数往下取一个值)
let arr = ['aa', 'bb', 'cc'];
function incre(arr) {
let i = 0;
return function() {
//这个函数每次被执行都返回数组arr中 i下标对应的元素
return arr[i++] || '数组值已经遍历完';
}
}
let next = incre(arr);
console.log(next()); //aa
console.log(next()); //bb
console.log(next()); //cc
console.log(next()); //数组值已经遍历完
//缓存
//比如求和操作,如果没有缓存,每次调用都要重复计算,采用缓存已经执行过的去查找,查找到了就直接返回,不需要重新计算
var fn = (function() {
var cache = {}; //缓存对象
var calc = function(arr) { //计算函数
var sum = 0;
//求和
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
return function() {
var args = Array.prototype.slice.call(arguments, 0); //arguments转换成数组
var key = args.join(","); //将args用逗号连接成字符串
var result, tSum = cache[key];
if (tSum) { //如果缓存有
console.log('从缓存中取:', cache) //打印方便查看
result = tSum;
} else {
//重新计算,并存入缓存同时赋值给result
result = cache[key] = calc(args);
console.log('存入缓存:', cache) //打印方便查看
}
return result;
}
})();
fn(1, 2, 3, 4, 5);
fn(1, 2, 3, 4, 5);
fn(1, 2, 3, 4, 5, 6);
fn(1, 2, 3, 4, 5, 8);
fn(1, 2, 3, 4, 5, 6);
//防抖
function debounce(callback, time) {
var timer;
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
callback()
}, time)
}
}