目录
作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
局部作用域
局部作用域分为函数作用域和块作用域。
函数作用域
定义:在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
<script> function fn(){ // 函数内部声明的变量 num let num = 10; console.log(num); // 10 } fn() // 访问函数内部变量 num console.log(num); // 报错 num未定义 </script>
总结:
1.函数内部声明的变量,函数外部无法访问。
2.函数的形参也是函数内部的局部变量。
3.不同函数内部声明的变量无法互相访问。
4.函数执行完毕后,函数内部的变量实际被清空了。
块级作用域
定义:在JS中使用{}包裹的代码称为代码块,代码块内部声明的变量外部无法访问(也有可能访问)
{ // a 只能在代码块中被访问 let a = 10; console.log(a); // 10 } console.log(a); // 超出作用域 报错 if(true){ let b = 20; console.log(b); // 20 } console.log(b); //报错 未定义
注意:JS中除了变量还有常量,常量和变量的本质区别就是,常量必须有初始值且不允许被重新赋值,但常量值为对象时它的属性和方法可以被重新赋值(因为对象的地址存放在栈中,数据存放在堆中,更改对象的属性是更改的堆中的数据,而不是地址,所以不会报错。但是不能给对象重新赋值)
const num = 10; num = 20; console.log(num); // 报错 const obj = { uname:'张三', age:20, } obj.uname = '李四'; obj.age = 20; console.log(obj.uname,obj.age); // 李四 20 const obj = {} // 不能给对象重新赋值
总结:
1.let声明的变量会产生块级作用域,var不会产生
2.const声明的变量也会产生块级作用域
3.不同代码块之间的变量无法互相访问
4.推荐使用let
全局作用域
定义:全局作用域中声明的变量,任何其他作用域都可以被访问。
<script>标签和 .js 文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
总结:
1.为window对象动态添加的属性默认也是全局的,不推荐!
2.函数中未使用任何关键字声明的变量为全局变量,不推荐!
3.尽可能减少的声明全局变量,放在全局变量污染。
作用域链
定义:函数内部允许创建新的函数,会产生新的作用域,这时作用域产生了嵌套关系。类型父子关系的作用域链关联在一起形成了链状的解构
// 全局作用域 let a = 1; let b = 2; // 局部作用域 function fn1(){ let c = 3; // 局部作用域 function fn2(){ let d = 4 } }
作用域链本质上是底层的变量查找机制,当函数执行时,会根据就近原则查找当前函数作用域中查找变量,如果没有会依次向上级作用域查找变量直到全局作用域。
<script> // 全局作用域 let a = 1; let b = 2; // 局部作用域 function fn1(){ let a = 10; console.log(a); // 10 // 局部作用域 function fn2(){ console.log(b); // 2 } fn2() } fn1(); </script>
总结:
1.嵌套关系的作用域串联起来形成了作用域链。
2.相同作用域链中按着从小到大的规则查找变量。
3.子作用域能够访问父级作用域,父级作用域无法访问子级作用域
闭包
定义:闭包是一种比较特殊的函数,使用闭包能够访问到函数作用域中的变量。其实就相当于闭包是一个作为返回值的函数。
个人理解:1.形成闭包的条件函数嵌套函数,内层函数访问到外层函数的变量。如下代码:
<script> function fn1(){ let i = 10; function fn2(){ console.log(i); // 10 } fn2() } fn1(); </script>
闭包的好处:1.创建私有内存,保护变量不被随意更改。
不用闭包的情况:以下代码实现调用一次就加1
<script> let i = 0; function fn1(){ function fn2(){ i++ console.log(i); } return fn2 } const fun = fn1() fun() </script>
因为 i 实在全局作用域下,所以可以被任意修改。
用闭包的情况下:以下代码实现调用一次就加1
<script> function fn1(){ let i = 0; function fn2(){ i++ console.log(i); } return fn2 } const fun = fn1() fun() </script>
这时 i 写在了函数内部,不能被随意更改。
2.外层函数可以访问内存函数的变量
<script> function fn(){ function fn1(){ let a = 1; console.log(a); } return fn1 } fn()() // 1 </script>
其实就是把内层函数作为返回值给了外层函数
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问。
<script> // 访问变量 str console.log(str + 'world!'); // undefindeword // 声明变量 str var str = 'hello '; </script>
总结:
变量在未声明即被访问时会报语法错误
变量在声明之前即被访问,变量的值为
undefined
let
声明的变量不存在变量提升,推荐使用let
变量提升出现在相同作用域当中
实际开发中推荐先声明再访问变量
函数
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
函数表达式不存在提示现象
<script> // 调用函数 foo() // 声明函数 function foo() { console.log('声明之前即被调用...') } // 不存在提升现象 bar() // 错误 var bar = function () { console.log('函数表达式不存在提升现象...') } </script>
函数参数Plus版
动态参数 --- arguments
arguements是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
注意:箭头函数没有动态参数
使用场景:在不确认实参的情况下。
<script> function fn(){ console.log(arguments); // [2,2,3,4,4,2,3,4] } fn(2,2,3,4,4,2,3,4); </script>
剩余参数 --- ...(三个点)
...是语法符号,置于最末函数形参之前,用于获取多余的实参。返回的是真数组
<script> function fn(a,b,c,...arr){ console.log(arr); // [4, 4, 2, 3, 4] } fn(2,2,3,4,4,2,3,4); </script>
也可以获取全部的实参
<script> function fn(...arr){ console.log(arr); // [2, 2, 3, 4, 4, 2, 3, 4] } fn(2,2,3,4,4,2,3,4); </script>
补充:展开运算符(也是三个点 ... 在数组中)
数组展开,可以获得数组中的所有元素,且不会修改原数组。
作用:求最大最小值;合并数组
求最大值:
<script> let arr = [2,2,3,4,5,2,4,1]; console.log(...arr); // 2,2,3,4,5,2,4,1 console.log(Math.max(...arr)); // 5 </script>
合并数组:
<script> let arr1 = [2,2,3,4]; let arr2 = [3,2,3,4] let arr = [...arr1,...arr2] console.log(arr); //[2, 2, 3, 4, 3, 2, 3, 4] </script>
箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
箭头函数的格式:
<script> const fn = () => { console.log('老子就是箭头函数'); } fn(); </script>
1.箭头函数属于表达式函数,所以没有函数提示
2.箭头函数只有一个参数时可以省略圆括号()
const fn = x => {console.log(x);} // 1 fn(1);
3.箭头函数函数体只有一行代码时可以省略花括号{}
const fn = x => console.log(x); // 1 fn(1);
4.只有一行代码的时候,可以省略return
const fn = (x,y) => x + y; console.log(fn(1,2)); // 3
5.箭头函数可以直接返回一个对象
const fn = (uname,age) => ({uname:uname,age:age}) console.log(fn('张三',20)); // 张三 20
注意:箭头函数没有arguments动态参数,只可以使用...剩余参数
<script> const fn = (...arr) => { console.log(arr); // [2, 3, 4, 5, 1] } fn(2,3,4,5,1) const fn1 = () => { console.log(arguments); // 报错 arguments is not defined } fn1(3,12,314,21); </script>
箭头函数的this
1.首先箭头函数不会创建自己的this,而是从自己的作用链上找上一层的this指向
粗暴理解:箭头函数的this指向就是上一级作用域中的this指向,如果上一级还是箭头函数,那就继续往上找呗,实在没有就指向window咯!!!
2.DOM事件不推荐使用箭头函数。懂得都懂!
对象方法箭头函数this:
<script> const obj = { uname:'张胜男', age:20, sayHi:()=>{ console.log(this); // this指向window } } obj.sayHi() const obj1 = { uname:'李亚男', age:21, sayHello:function fn(){ console.log(this); // obj const fn1 = () => { console.log(this); // obj } fn1(); } } obj1.sayHello() </script>
解构赋值
数组解构
语法:赋值运算符 = 左侧的 [ ] 用于批量声明变量,右侧数组的单元值被赋值给左侧的的变量
注意:解构之后必须加分号
<script> const arr = [100,200,300]; const [a,b,c] = arr; console.log(a,b,c); //100 200 300 </script>
交换两个变量的值 不用临时变量
<script> let a = 1; let b = 2; [b,a] = [a,b]; console.log(a,b); //2 1 </script>
数组解构变量和单元值数量
变量多, 单元值少
变量少, 单元值多
剩余参数 变量少, 单元值多
防止 undefined 传递
按需导入赋值
// 1. 变量多, 单元值少 , undefined const [a, b, c, d] = [1, 2, 3] console.log(a) // 1 console.log(b) // 2 console.log(c) // 3 // console.log(d) // undefined // 2. 变量少, 单元值多 const [a, b] = [1, 2, 3] console.log(a) // 1 console.log(b) // 2 // 3. 剩余参数 变量少, 单元值多 const [a, b, ...c] = [1, 2, 3, 4] console.log(a) // 1 console.log(b) // 2 console.log(c) // [3, 4] 真数组 // 4. 防止 undefined 传递 const [a = 0, b = 0] = [1, 2] const [a = 0, b = 0] = [] console.log(a) // 1 console.log(b) // 2 // 5. 按需导入赋值 const [a, b, , d] = [1, 2, 3, 4] console.log(a) // 1 console.log(b) // 2 console.log(d) // 4
对象解构
语法:赋值运算符 = 左侧的 { } 用于批量声明变量,右侧数组的单元值被赋值给左侧的的变量。
注意:属性名和变量名必须一致才可以
<script> const obj = { uname:'张三', age:20 } const {uname,age} = obj console.log(uname,age); //张三 20 </script>
对象解构变量重新命名
语法:const { 旧变量名:新变量名} = {变量名:值}
<script> const obj = { uname:'张三', age:20 } const {uname: cname,age} = obj console.log(cname,age); //张三 20 </script>
多级对象解构
<script> const user = { uname:'张三', friend:{ ls:'李四', zw:'赵五', }, age:18 } const {uname,friend:{ls,zw},age} = user console.log(uname,ls,zw,age); // 张三 李四 赵五 18 </script>
forEach遍历数组
forEach 就是遍历,加强版的数组对象,适合于遍历数组对象。返回的是元素本身
语法:数组.forEach( (数组元素,数组索引) => { } )
<script> const arr = ['red','green','blue']; arr.forEach((item,i) => { console.log(item); // red green blue console.log(i); // 0 1 2 }) </script>
filter筛选数组
返回符合条件的新数组
语法:数组.filter( (数组元素,数组索引) => { return 符合条件的元素 } )
const arr = [10, 20, 30] const newArr = arr.filter(function (item, index) { return item >= 20 }) //箭头函数简写 const newArr = arr.filter(item => item >= 20) console.log(newArr) // [30]
构造函数
定义:也是函数,主要用来创建具有相同属性名的对象,初始化对象。
应用场景:把一些类似的函数封装成一个构造函数,用这个构造函数去创建更多的对象。
规定:1.命名以大写字母开头 2.只能由 new 操作符来执行
构造函数的写法
作用:构造函数是来快速创建多个类似的对象
注意:构造函数没有return,会自动返回创建新的对象。因为new了一下
new实例化执行的过程
1.创建新对象
2.构造函数this指向这个新对象
3.执行构造函数代码,修改this,添加新属性
4.返回新对象
实例成员和静态成员
1.通过构造函数创建的对象是实例对象,实例对象中的属性和方法称为实例成员。
2.构造函数的属性和方法称为静态成员(通常是一些共享的属性或方法)
<script> function Goods(uname,price,count){ this.uname = uname; //实例成员 this.price = price; //实例成员 this.count = count; //实例成员 } const mi = new Goods('小米',1999,21); mi.id = 1; // 这个id也是实例成员 Goods.sum = 20; // 静态成员 Goods.sayHi = () => { // 静态成员 console.log('Hi~~~'); } </script>
内置构造函数
Object静态方法
静态方法就是只有构造函数Object可以调用
常用:Object.keys(对象名) --- 获取对象里的属性名 返回一个新数组
Object.values(对象名) --- 获取对象里的属性值 返回一个新数组
Object.assign(新对象名,旧对象名) --- 拷贝对象 这是个浅拷贝
<script> const obj = { uname:'张三', age:18, gender:'男' } console.log(Object.keys(obj)); // ['uname', 'age', 'gender'] console.log(Object.values(obj)); // ['张三', 18, '男'] const newObj = {}; Object.assign(newObj,obj); console.log(newObj); // {uname: '张三', age: 18, gender: '男'} </script>
扩展提示 数组方法
数组 reduce方法
reduce 返回函数累计处理的结果,经常用于求和等。
数组.reduce( function(累计值,当前元素,[索引号],[原数组]){ return 出来的值就是新的累计值 },起始值 ) ---- 如果起始值不传的话,就会以数组中第一个算。
<script> const arr = [2,2,3,4]; const res = arr.reduce(function(prev, item){ return prev + item; }) console.log(res); // 11 </script>
数组find方法
用在数组对象中,找到自己想要的数据,把符合条件的对象返回出来。
<script> const arr = [ { uname:'小米', price:1999 }, { uname:'华为', price:9999 } ] let res = arr.find(function(item){ return item.uname === '小米' }) console.log(res); // {uname: '小米', price: 1999} </script>
数组 every和some方法
返回值是true和false
every方法:设定一个条件,数组中的元素必须全部满足才能返回ture
some方法:设定一个条件,数组中的元素只要有一个满足就返回true
<script> const arr = [4,5,4,1,5,6,7]; let res = arr.every(function(item){ // console.log(item); // 只打印第一个 return item >= 3; }) console.log(res); // false let res1 = arr.some(function(item){ // console.log(item); // 全部打印出来 return item >= 3; }) console.log(res1); // true </script>
String常见方法
1. split() 把字符串 转换为 数组 和 join() 相反
const str = 'red,green,blue'; const arr = str.split(','); console.log(arr); // ['red','green','blue']
2. 字符串的截取 substring(开始的索引号,结束的索引号)
2.1 如果结束索引号省略,则一直截取到尾部
2.2 顾头不顾尾(从0开始数数)
const str2 = '今天又要做核酸了'; console.log(str2.substring(5,7)); // 核酸
3. 检测是否以某字符开头 startWidth(检测字符串) 返回值 true或false
3.1 被检测的字符串的开头一定要和检测字符串一致,不然为false
const str3 = 'Peiqi不是猪'; console.log(str3.startsWith('P')); // true console.log(str3.startsWith('pei')); false
4.判断某个字符是不是包含在一个字符串里面 includes(检测字符串) 返回值 true或false
4.1 被检测的字符串只要有一个和检测字符串一致就为true
const str4 = 'To be or not to be'; console.log(str4.includes('to')); // true
Number常见方法
toFixed() 保留几位小数 遵循四舍五入原则
const num = 10.945; console.log(num.toFixed()); // 11 console.log(num.toFixed(1)); // 11.0 console.log(num.toFixed(2)); // 10.95 console.log(num.toFixed(3)); // 10.945
面向对象
面向对象和面向过程
面向过程:面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。
面向对象:面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。
简单的问题可以用面向过程的思路来解决,直接有效,但是当问题的规模变得更大时,用面向对象效率更高点
原型 prototype
定义:每一个构造函数都有一个prototype属性,指向另一个对象,所以也称为原型对象。
作用:1. 共享方法,解决构造函数浪费内存的问题。
2. 可以固定不变的方法,定义在prototype对象上,方便调用。
构造函数浪费内存是什么?
答:当实例对象调用构造函数里面的复杂数据类型(方法)时,每当有一个实例对象调用时,都要在内存的栈中开辟一个新的地址,但堆中的数据都是一样的。这样大大的浪费了内存。
构造函数和原型对象里的this指向
都指向实例对象
<script> let thatFn; let thatPro; function Fn(){ // 构造函数里的this thatFn = this; } Fn.prototype.eat = function (){ // 原型对象里面的this thatPro = this } let obj = new Fn(); obj.eat(); console.log(thatFn === obj); // true 构造函数里的this指向实例对象 console.log(thatPro === obj); // true 原型对象里面的this指向实例对象 </script>
原型对象里的constructor属性
作用:指向构造函数
constructor属性的应用:避免以赋值的形式给原型对象添加方法的时候,修改了初始化时的原型对象在内存栈中的地址,丢失了constructor而不能指回构造函数了。这时候只要添加constructor属性让constructor重新指回构造函数即可。
<script> function Fn(uname){ this.uname = uname; } // 以对象的形式给原型对象赋值添加方法 Fn.prototype = { eat:function(){ console.log(this.uname+'爱吃饭'); }, drink:function(){ console.log(this.uname+'爱喝可乐'); } } let zs = new Fn('张三'); console.log(Fn.prototype); </script>
这时候原型对象里面以及没有constructor属性了
<script> function Fn(uname){ this.uname = uname; } // 以对象的形式给原型对象赋值添加方法 Fn.prototype = { // 设置constructor属性重新指回构造函数 constructor:Fn, eat:function(){ console.log(this.uname+'爱吃饭'); }, drink:function(){ console.log(this.uname+'爱喝可乐'); } } let zs = new Fn('张三'); console.log(Fn.prototype); </script>
设置constructor属性重新指回构造函数
对象原型__proto__
对象原型在实例对象里面,指向构造函数的原型对象prototype。构造函数里没有对象原型,
对象原型里面也有constructor属性并且指向构造函数
构造函数、原型对象、实例对象三者的关系
1. 实例对象是构造函数创造出来的。
2. 原型对象是构造函数里面特有的。
所以原型对象和实例对象都是构造函数搞出来的,粗暴理解成构造函数为爸爸,原型对象为大儿子,实例对象为二儿子。
三者的关系:
构造函数指向原型对象,同时指向实例对象。原型对象里的constructor属性指向构造函数;实例对象里的对象原型指向原型对象,constructor属性指向构造函数。
原型链
原型链是一种查找规则:
调用A实例对象的方法,如果没有就往A原型对象找,如果没有就往Object原型对象上找,如果找到最后一层Object原型对象的对象原型就是null,这样就会形成一个查找链条,这就是原型链。
查找规则:
当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性,如果没有就查找它的原型(就是__proto__指向的prototype原型对象),如果还没有找到就查找原型对象的原型(object的原型对象),依次类推一直找到object的原型对象上的对象原型为止(null)。
作用:
提供一个查找规则,找到相应的方法。
浅拷贝和深拷贝
为什么会有拷贝?
因为直接赋值对象会有问题,赋值是把对象的地址给另一个对象,相当于两个对象共用一个地址,所以修改新对象的值也会改变旧对象的值。拷贝就可以解决这种问题。
赋值和拷贝的区别?
赋值是两个对象共用一个地址,修改新对象的值,旧对象也会被修改。
拷贝是新对象拷贝旧对象的地址,都各有自己的地址,所以新对象修改自己的值不会影响旧对象的值。
浅拷贝
定义:拷贝对象的地址值。
方法:拷贝对象:Object.assign(新对象,旧对象) 展开运算符{...被拷贝的对象obj}
拷贝数组:Array.prototype.concat() [...arr]
浅拷贝适合用于只有简单数据类型的对象,没有复杂数据类型的对象,如果里面有复杂数据类型,拷贝的是地址。
<script> const oldObj = { uname:'张三', age:18, hobby:['唱','跳','rap'], star:{ sname:'才虚困' } } const newObj = {} Object.assign(newObj,oldObj); // 新对象修改简单数据类型不会影响,修改复杂数据类型会影响。 newObj.uname = '李四'; newObj.hobby.push('篮球'); console.log('旧对象',oldObj); console.log('新对象',newObj); </script>
递归函数
定义:函数内部自己调用自己,这个函数就是递归函数。
作用:递归函数的作用和循环效果类型。
由于递归很容易发生 栈溢出 错误,所以必须加要加退出条件 return
//需求:让这个函数执行6次就结束 // 1.有个变量计数 // 2.判断条件 // 3.变量的变化量 let i = 1 function fn() { console.log(`这是第${i}次`) if (i >= 6) { return } i++ fn() } fn()
深拷贝
深拷贝和浅拷贝的区别:
如果被拷贝的对象里面有复杂数据类型,新对象修改复杂数据类型的值,旧对象仍然会受到影响,而深拷贝可以解决这个问题。
利用递归函数实现深拷贝
实现步骤:
1.深拷贝是地址值相互不影响,如果需要做的话,需要用到函数的递归
2.普通类型的不用管,直接用forin就可以,但是如果是数组的需要再次调用递归函数
3.如果遇到对象类型的也需要再次调用递归函数,如果是用instanceof来判断是否是数组对象的需要先数组在对象
<script> const obj = { uname: '张三', age: 18, hobby: ['唱', '跳', 'rap'], star: { sname: '才虚困' } }; const o = {}; function deepCopy(newObj,oldObj){ for(let k in oldObj){ if(oldObj[k] instanceof Array){ newObj[k] = []; deepCopy(newObj[k],oldObj[k]) } if(oldObj[k] instanceof Object){ newObj[k] = {}; deepCopy(newObj[k],oldObj[k]) } else{ newObj[k] = oldObj[k] } } } deepCopy(o,obj); o.star.sname = '王一博'; o.hobby[0] = '篮球'; console.log('旧对象',obj); console.log('新对象',o); </script>
利用lodash实现深拷贝
Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。
<script src="./lodash.min.js"></script> <script> const obj = { uname: '张三', age: 18, hobby: ['唱', '跳', 'rap'], star: { sname: '才虚困' } } const o = _.cloneDeep(obj) console.log(o) </script>
利用JSON实现深拷贝
JSON.stringify() //对象转换为字符串
JSON.parse() //字符串转换为对象
<script> const obj = { uname: '张三', age: 18, hobby: ['唱', '跳', 'rap'], star: { sname: '才虚困' } }; // 第一步 利用 JSON.stringify() 把对象转换成JSON字符串 const str = JSON.stringify(obj); // 第二步 利用 JSON.parse() 把JSON字符串转换成对象 const o = JSON.parse(str); console.log(o); </script>
this相关知识
普通函数的this指向
普通函数中:谁调用我,this就指向谁。
dom事件里的函数中:谁绑定事件,this就指向谁。
对象里的函数中:指向对象
// 普通函数: 谁调用我,this就指向谁 console.log(this) // window function fn() { console.log(this) // window } window.fn() window.setTimeout(function () { console.log(this) // window }, 1000) document.querySelector('button').addEventListener('click', function () { console.log(this) // 指向 button }) const obj = { sayHi: function () { console.log(this) // 指向 obj } } obj.sayHi()
箭头函数中的this指向
箭头函数内不存在this,this的指向问题看上一级作用域的this指向。过程:向外层作用域,一层一层查找this,直到有this的定义。
箭头函数不适应:构造函数、原型函数、字面量对象中的函数、dom事件函数
箭头函数适应:需要使用上层this完成需求的地方
call方法改变this指向
作用:call方法调用函数,同时指定被调用函数中this的值。
语法:函数名.call(thisArg,arg1,arg2,....) // 这里的参数是字符串
<script> const obj = { uname:'pink' } function fn(x,y){ console.log(this); // window console.log(x + y); // 3 } // 改变this指向 fn.call(obj,1,2) // 此时函数里的this指向了Object </script>
apply方法改变this指向
作用:apply方法调用函数,同时指定被调用函数中this的值。
语法:函数名.apply(thisArg,arg1,arg2,....) // 这里的参数是数组
call和apply区别:
都是调用函数,都能改变this指向。
参数不一样,apply传递的必须是数组。
<script> const obj = { uname:'张三' } function fn(x,y){ console.log(this); // window console.log(x + y); // 3 } fn.apply(obj,[1,2]) // 此时函数里的this指向了Object </script>
bind方法改变this指向
作用:bind方法不会调用函数。但是能改变函数内部的this指向
语法:函数名.bind(thisArg,arg1,arg2,....) // 返回值是原函数的拷贝(新函数)
<script> const obj = { uname:'张三' } function fn(){ console.log(this); // Object --- { uname:'张三 } } // 1. bind 不会调用函数 // 2. 能改变this指向 // 3. 返回值是个函数 但是这个函数里的this是更改过的obj了 fn.bind(obj)(); </script>
总结:call和apply以及bind三者的区别
相同点:都可以改变函数内部的this指向
不同点:1. call和apply会调用函数,并且改变函数内部的this指向。
2. call和apply传递的参数不一样,call传递参数aru1,aru2;apply必须传递数组。
3. bind 不会调用函数,可以改变函数内部this指向。
节流和防抖
节流:就是指连续触发事件但是在n秒中只执行一次函数。
防抖:就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间。