JavaScript进阶

正则表达式

正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象,通常用来查找、替换那些符合正则表达式的文本

语法

// 定义正则表达式语法
const 变量名 = /表达式/

//判断是否有符合规则的字符串
const str = 'adafdfadf前端亲戚额企鹅kkk前端'
const reg = /前端/
console.log(reg.test(str)) //返回boolean
console.log(reg.exec(str)) //返回数组

元字符

普通字符仅能够描述它们本身,,例如所有的字母和数字。
元字符是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能,比如,只能输入英文字母,[a-z]。
参考文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions

元字符可大致分为以下几类:

  1. 边界符
    正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
边界符说明
^表示匹配行首的文本
$表示匹配行位的文件
  1. 量词
    量词用来 设定某个模式出现的次数
量词说明
*重复0次或更多次
+重复一次或更多次
?重复零次或一次
{n}重复n次
{n,}重复n或更多次,逗号左右两侧千万不要出现空格
{n, m}重复n到m次,逗号左右两侧千万不要出现空格
  1. 字符类
字符类说明
[ ]匹配字符集合,[abc]匹配abc一字符,返回true
-连字符,[a-z]表示a到z26个小写字母
^ 取反, [^a-z] 匹配除了小写字母以外的字符
.匹配除换行符以外的任何单个字符
\d匹配0-9之间任意数字,相当于[0-9]
\D匹配0-9以外的字符,相当于[^0-9]
\w匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
\W除所有字母、数字、下划线以外的字符,相当于[^A-Za-z0-9_]
/s匹配空格,包括换行符、制表符、空格符,相当于[\t\r\n\v\f]
/S匹配非空格,相当于 [^\t\r\n\v\f]

修饰符

修饰符约束正则执行的某些细节行为,如是否区分大小写、是否支持多行匹配等
语法:

/表达式/修饰符

i 是单词 ignore 的缩写,正则匹配时字母不区分大小写
g 是单词 global 的缩写,匹配所有满足正则表达式的结果

作用域

作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问

局部作用域

局部作用域分为函数作用域和块作用域。
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

  1. 函数内部声明的变量,在函数外部无法被访问
  2. 函数的参数也是函数内部的局部变量
  3. 不同函数内部声明的变量无法互相访问
  4. 函数执行完毕后,函数内部的变量实际被清空了

块作用域
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将有可能无法被访问。

  1. let 声明的变量会产生块作用域,var 不会产生块作用域
  2. const 声明的常量也会产生块作用域
  3. 不同代码块之间的变量无法互相访问
  4. 推荐使用 let 或 const

全局作用域

<script> 标签和 .js 文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。全局作用域中声明的变量,任何其它作用域都可以被访问

  1. 为 window 对象动态添加的属性默认也是全局的,不推荐!
  2. 函数中未使用任何关键字声明的变量为全局变量,不推荐!
  3. 尽可能少的声明全局变量,防止全局变量被污染

作用域链

作用域链本质上是底层的变量查找机制。
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域

  1. 嵌套关系的作用域串联起来形成了作用域链
  2. 相同作用域链中按着从小到大的规则查找变量
  3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域

JS垃圾回收机制

垃圾回收机制(Garbage Collection) 简称 GC。JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题。但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况,不再用到的内存,没有及时释放,就叫做内存泄漏。

内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:

  1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
  2. 内存使用:即读写内存,也就是使用变量、函数等
  3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
  4. 说明:全局变量一般不会回收(关闭页面回收);一般情况下局部变量的值, 不用了, 会被自动回收掉。

堆栈空间分配区别:

  • 栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
  • 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。

回收算法

  1. 引用计数
    IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象算法,但它却存在一个致命的问题:嵌套引用(循环引用)
  2. 标记清除法
    标记清除算法将“不再使用的对象”定义为“无法达到的对象”,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

闭包

一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解:闭包 = 内层函数 + 外层函数的变量
闭包作用:封闭数据,实现数据私有,外部也可以访问函数内部的变量,允许将函数与其所操作的某些数据(环境)关联起来
例如:以下代码,调用一次count(),变量i增加1,但是i是全局变量,容易被修改

let i = 0
function count() {
	i++
	console.log(i);
 }
 count()

以下为通过闭包实现了数据私有化,无法直接修改count

function count() {
	let i = 0
	function fn() {
		i++
		console.log(i);
	}
	return fn
}
const fun = count()
fun()

变量提升

变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)

  • 变量在var声明之前即被访问,变量的值为 undefined
  • 变量提升出现在相同作用域当中
  • let/const 声明的变量不存在变量提升
console.log(str + 'world')
var str = 'hello '

函数

函数提升

函数提升与变量提升比较类似,是指函数在声明之前即可被调用。

  • 函数提升能够使函数的声明调用更灵活
  • 函数表达式不存在提升的现象
  • 函数提升出现在相同作用域当中
// 函数提升 在声明之前就可以使用
// 只提升函数声明定义 不提升调用
fn()
function fn() {
    console.log('aaa');
}
fun()  //报错
const fun = function() {
    console.log('bbb');
}

函数参数

动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参

function getSum() {
    // arguments 动态参数,只存在于函数内
    console.log(arguments);
    let sum = 0;
    for(let i = 0; i < arguments.length; i++) {
        sum += arguments[i]
    }
    console.log(sum);
}
getSum(2, 3)
getSum(2, 3, 4)

剩余参数
剩余参数允许将一个不定数量的参数表示为一个数组,开发中提倡多使用剩余参数。

function getSum(a, ...arr) {
    console.log(arr);
    // arr 剩余的参数,真数组
}
getSum(2, 3)
getSum(2, 3, 4)

展开运算符

展开运算符(…),将一个数组进行展开,不会修改原数组,可以利用求数组最大值以及合并数组等操作

const arr = [1, 2, 3, 4, 5]
console.log(...arr);
// 求数组最大值
console.log(Math.max(...arr));
// 合并数组
const arr2 = [6, 7, 8, 9]
const arr3 = [...arr, ...arr2]
console.log(arr3);

箭头函数

引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁,箭头函数更适用于那些本来需要匿名函数的地方
基本语法

// 1.只有一个形参,可以省略括号
const fn = x => {
    console.log(x);
}
fn(3)
// 2.只有一行代码,可以省略大括号
const fn2 = x => console.log(x);
fn2(3)
// 3.只有一行代码,可以省略return
const fn3 = x => x + x
console.log(fn3(3))
//4.可以直接返回对象,需要加小括号,对象的大括号与代码块大括号冲突
const fn4 = uname => ({name: uname})
console.log(fn4('abc'));

箭头函数参数
普通函数有arguments 动态参数,箭头函数没有 arguments 动态参数,但是有 剩余参数 …args

// 箭头函数没有arguments,但是有剩余参数
const getSum = (...arr) => {
    let sum = 0
    for(let i = 0; i < arr.length; i++) {
        sum += arr[i]
    }
    return sum
}
console.log(getSum(2, 3, 5))

箭头函数this
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值,箭头函数不会创建自己的this,只会从自己的作用域链的上一层沿用this
在开发中使用箭头函数前需要考虑函数中 this 的值,事件回调函数使用箭头函数时,this为全局的 window,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数

console.log(this);  //window
// 普通函数
function fn() {
    console.log(this);  //window
}
fn()
const obj = {
    name: 'andy',
    sayHi: function() {
        console.log(this); // Object
        const count = () => {
            console.log(this); // 向上,Object
        }
        count()
    },
    sayHello: () => {
        console.log(this); // window
    }
}
obj.sayHi()
obj.sayHello()
// 箭头函数的this,箭头函数没有this,会向上一层去找
const fn2 = () => {
    console.log(this); //window
}
fn2()
// DOM事件回调函数为了简便,不太推荐使用箭头函数

解构赋值

解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值。

数组解构

数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。

// 基础语法
const arr = [1, 2, 3]
const [a, b, c] = arr

// 1.变量多,单元值少
const [a, b, c, d] = [1, 2, 3]
console.log(a, b, c, d);
// 2.变量少,单元值多
const [a, b] = [1, 2, 3]
console.log(a, b);
// 3.剩余参数
const [a, b, ...c] = [1, 2, 3, 4]
console.log(a, b ,c);

// 4.防止undefined
const [a = 0, b = 0, c = 0] = [1, 2]
console.log(a, b, c);
// 5.按需导入赋值
const [a, b, , d] = [1, 2, 3, 4]
console.log(a, b, d);

// 多维数组
const arr = [1, 2, [3, 4]]
console.log(arr[0]);
console.log(arr[1]);
console.log(arr[2]);
console.log(arr[2][0]);
console.log(arr[2][1]);
const [a, b, [c, d]] = arr
console.log(a, b, c, d);

js前面必须加分号情况

// 立即执行函数
(function fn(){ })();
// 或者
;(function fn() { })()

// 数组解构
// 交换两个变量的值
let x = 1
let y = 2; // 必须加分号
[y, x] = [x, y] 
// 或者
;[y, x] = [x, y]
console.log(x);
console.log(y);

遍历数组forEach

// 参数当前数组元素是必须要写的,索引号可选
数组.forEach(function(当前数组元素, 当前元素索引号) {
  // 函数体
})

对象解构

对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法

const obj = {
    name: 'mark',
    age: 18
}
// 变量名必须要与属性名一致
const { age, name } = obj
console.log(name, age);
// 变量名可以重新改名
const { age: age1, name: uname } = obj
console.log(age1, uname);

// 解构数组对象
const pig = [{pname: 'n1'}, {pname: 'n2'}]
const [{ pname: pname1 }, { pname: pname2 }] = pig
console.log(pname1, pname2);

多级对象解构

const pig = {
     name: '佩奇',
     family: {
         mother: '猪妈妈',
         father: '猪爸爸',
         sister: '乔治'
     },
     age: 6
 }
 const {name, family:{mother, father, sister}, age} = pig
 console.log(name, age, mother, father, sister);
 const pigs = [{
     name: '佩奇',
     family: {
         mother: '猪妈妈',
         father: '猪爸爸',
         sister: '乔治'
     },
     age: 6
 }]
 
 const [{name, family:{mother, father, sister}, age}] = pigs
 console.log(name, age, mother, father, sister);
 function render({data}) {
     console.log(data);
     
 }
 const rObj = {
     data: {
         a: 'a',
         b: 'b'
     }
 }
 render(rObj)

面相对象

创建对象

// 1.字面量
const obj1 = {name: 'mark'}
// 2.new Object
const obj2 = new Object()
obj.name = 'Mark'
console.log(obj2);
// 3.构造函数
function Pig(name, age) {
    this.name = name
    this.age = age
}
const peiqi = new Pig('佩奇', 6)
console.log(peiqi);
const qiaozhi = new Pig('乔治', 3)
console.log(qiaozhi);

构造函数
构造函数是一种特殊的函数,主要用来初始化对象,可以通过构造函数来快速创建多个类似的对象。
构造函数在技术上是常规函数,不过有两个约定:
1.命名以大写字母开头
2.只能由"new" 操作符来执行

实例化执行过程:
1.创建新对象
2.构造函数this指向新对象
3.执行构造函数代码,修改this,添加新的属性
4.返回新对象

实例成员&静态成员

实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。

function Pig(name) {
	this.name = name  //实例成员
	this.sayHello = function() {
		 console.log('hello');
	}             
}

const pig = new Pig('乔治')
console.log(pig.name) // 访问实例属性
pig.sayHello() // 访问实例方法

静态成员
构造函数的属性和方法被称为静态成员,静态成员方法中的this 指向构造函数本身,一般公共特征的属性或方法静态成员设置为静态成员,不能通过实例访问静态成员

function Pig(name) {
}
Pig.eyes = 2 //静态成员
Pig.sayHi = function() { console.log('hi');
}
const pig = new Pig('乔治')
console.log(pig.eyes);  // undefined
pig.sayHello()
console.log(Pig.eyes);  // 2
Pig.sayHi()

内置构造函数

Object
Object 是内置的构造函数,用于创建普通对象。

const o = { name: 'Mark', age: 30}
// 获取对象属性和值
for (let k in o) {
	console.log(k) // 属性 name age
	console.log(o[k]) // 值 Mark 30
}

// 获得对象所有键,返回一个组数
const arrK = Object.keys(o)

// 法获取对象中所有属性值,返回一个组数
const arrV = Object.values(o)

// 对象拷贝
const obj = {}
Object.assign(obj, o)

// 对象添加属性
Object.assign(o, { gender: 'Male' })

Array

// Array 是内置的构造函数,用于创建数组
// 创建数组建议使用字面量创建,不用Array构造函数创建
const arr = new Array(1, 2, 3)
方法作用说明
forEach遍历数组不返回,用于不改变值,经常用于查找打印输出值
filter过滤数组筛选数组元素,并生成新数组
map迭代数组返回新数组,新数组里面的元素是处理之后的值,经常用于处理数据
reduce累计器返回函数累计处理的结果,经常用于求和等
join数组元素拼接位字符串返回字符串
find查找元素返回符合测试条件的第一个数组元素的值
every检测数组所有元素是否都符合指定条件所有元素都通过返回true
some检测数组中元素是否满足指定条件有一个满足条件的元素返回true
concat合并两个数组返回新数组
sort排序对原数组单元值排序
splice删除或替换原数组单元
reverse反转数组
findIndex查找元素索引值
Array.form伪数组转为真数组静态方法
// 数组reduce方法
// arr.reduce(function(上一次值,当前值){},初始值)
const arr = [1, 5, 8]
// 1.没有初始值
const total = arr.reduce(function(prev, current) {
    return prev + current
})
console.log(total); // 14

// 2.有初始值
const total2 = arr.reduce(function(prev, current) {
    return prev + current
}, 10)
console.log(total2); // 24
const total3 = arr.reduce((prev, current) =>  prev + current, 10)
console.log(total3); // 24

String
在JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法
之所以具有对象特征的原因是字符串、数值、布尔类型数据是JavaScript底层使用Object构造函数“包装”来的,被称为包装类型

// 字符串类型
const str = 'abcde'
console.log(str.length) // 字符串长度
实例属性或方法说明
length获取字符串长度
split(‘分隔符’)将字符串拆分数组
substring(需要截取的第一个字符的索引[, 结束的索引号])字符串截取
startsWith(检测字符串[, 检测位置索引号])检测是否以某字符开头
includes(搜索的字符串[, 检测位置索引号])判断是否包含某字符串
toUpperCase()转大写字母
toLowerCase()转小写字母
indexOf()检测是否包含某字符
endWith检测是否以某字符结尾
replace()替换字符串,支持正则匹配
match()查找字符串,支持正则匹配

Number
Number 是内置的构造函数,用于创建数值

// 数值类型
const price = 19.9999
console.log(price.toFixed(2)) // 保留两位小数

原型

JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象,所以我们也称为原型对象,这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存,我们可以把那些不变的方法,直接定义在prototype 对象上,这样所有对象的实例就可以共享这些方法。
构造函数和原型对象中的this 都指向实例化的对象

// 公共的属性写到构造函数里
function Star(name, age) {
    this.name = name
    this.age = age
    // this.sing = function() {
    //     console.log('唱歌');
    // }
}
// 公共的方法写到原型对象
Star.prototype.sing = function() {
    console.log('唱歌');
}
const ldh = new Star('刘德华', 60)
const zxy = new Star('张学友', 61)
console.log(ldh === zxy);
console.log(ldh.sing === zxy.sing);

** constructor属性**
每个原型对象里面都有个constructor属性,该属性指向该原型对象的构造函数
在这里插入图片描述

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor 就不再指向当前构造函数了,时,我们可以在修改后的原型对象中,添加一个constructor 指向原来的构造函数。

function Star() {
}
Star.prototype.sing = function() {
    console.log('唱歌');
    
}
Star.prototype.dance = function() {
    console.log('跳舞');
    
}
Star.prototype = {
    sing: function() {
        console.log('唱歌');
    },
    dance: function() {
        console.log('跳舞');
    },
    // 一定要重新指定constructor为原构造函数
    constractor: Star
}
console.log(Star.prototype);
const ldh = new Star()
console.log(Star.prototype.constructor === Star);  //true

对象原型
对象都会有一个属性__proto__ 指向构造函数的prototype 原型对象,之所以我们对象可以使用构造函数prototype 原型对象的属性和方法,就是因为对象有__proto__ 原型的存在。
注意:

  • proto 是JS非标准属性
  • [[prototype]]和__proto__意义相同
  • 用来表明当前实例对象指向哪个原型对象prototype
  • __proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数

在这里插入图片描述

原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承的特性。

// 人类 公共的属性和方法
function Person() {
	this.head = 1
	this.eyes = 2
	this.say = function () { }
	this.eat = function () { }
}

// 男人
function Man() {

}
// 用new Person()替代固定的对象,增加原型对象方法时不会影响其他继承对象
Man.prototype = new Person()
Man.prototype.constructor = Man
const m = new Man()
Man.prototype.smoking = function() { }

// 女人
function Woman() { }
Woman.prototype = new Person()
Woman.prototype.constructor = Woman
const w = new Woman()

原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对
象的链状结构关系称为原型链
①当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
②如果没有就查找它的原型(也就是__proto__指向的prototype 原型对象)
③如果还没有就查找原型对象的原型(Object的原型对象)
④依此类推一直找到Object 为止(null)
⑤__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥可以使用 instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

在这里插入图片描述

深浅拷贝

浅拷贝和深拷贝只针对引用类型

浅拷贝
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址
常用方法:

  • 拷贝对象:Object.assgin() 或者展开运算符 {…obj} 拷贝对象
  • 拷贝数组:Array.prototype.concat() 或者 […arr]
 // 对象浅拷贝
const obj = {
    name: 'abc',
    age: 18
}
// 1.
const o = { ...obj }
console.log(o);
// 2.
const o2 = {}
Object.assign(o2, obj)
console.log(o2);
// 数组浅拷贝
const arr = [1, 2, 3]
// 展开操作符
const arr1 = [...arr]
console.log(arr1);

// Array.prototype.concat
const arr2 = arr1.concat()
console.log(arr2);

深拷贝
深拷贝拷贝的是对象,不是地址
常用方法:

  • 通过递归实现深拷贝
  • lodash/cloneDeep
  • 通过JSON.stringify()实现
// 通过递归实现深拷贝
const obj = {
     name: 'abc',
     age: 18,
     address: {
         province: 'liaoning',
         city: 'shenayng'
     },
     hobby: ['足球', '篮球']
 }
 const o = {}
 // 拷贝数据
 function deepCopy(newObj, oldObj) {
     for (let k in oldObj) {
         if (oldObj[k] instanceof Array) {
             newObj[k] = []
             deepCopy(newObj[k], oldObj[k])
         } else if (oldObj[k] instanceof Object) {
             newObj[k] = {}
             deepCopy(newObj[k], oldObj[k])
         }else {
             newObj[k] = oldObj[k]
         }
     }
 }
 deepCopy(o, obj) // o新对象 obj旧对象
 obj.hobby.push('乒乓')
 obj.address.province = '北京'
 obj.name = 'xxxx'
 console.log(o);
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="lodash.min.js"></script>
    <script>
        // js库lodash里面cloneDeep内部实现了深拷贝
        const obj = {
            name: 'abc',
            age: 18,
            address: {
                province: 'liaoning',
                city: 'shenayng'
            },
            hobby: ['足球', '篮球']
        }

        const o = _.cloneDeep(obj)
        console.log(o);
        
    </script>
</body>
</html>
const obj = {
    name: 'abc',
    age: 18,
    address: {
        province: 'liaoning',
        city: 'shenayng'
    },
    hobby: ['足球', '篮球']
}
const str = JSON.stringify(obj)
const o = JSON.parse(str)
console.log(o);

异常处理

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行

function fn(x, y) {
    if(!x || !y) {
        // throw '没有参数',使用Error对象会更详细
        throw new Error('没有参数')
    }
    return x + y
}
try {
    console.log(fn());
} catch(err) {
    console.log(err.message);
    throw new Error(err.message)
} finally {
    console.log('finish');
}

this

this指向

普通函数的调用方式决定了this的值,即谁调用this的值指向谁
普通函数没有明确调用者时this值为window,严格模式下没有调用者时this的值为undefined

//'use strict' //严格模式
// this大原则谁调用就是谁,箭头函数没有自己的this,向上一层去找
// 普通函数
function sayHi() {
    console.log(this);
}
// 函数表达式
const sayHello = function() {
    console.log(this);
}
sayHi() // window
sayHello() // window
// 普通对象
const obj = {
    name: 'aaa',
    fun: function() {
        console.log(this);
    }
}
obj.sayHi = sayHi 
obj.sayHi()  //obj 
obj.fun() // obj 
// 普通函数没有明确调用者时this值为window,严格模式下没有调用者时this的值为undefined
function fn() {
    console.log(this);
}
fn() // undefined
// 箭头函数,箭头函数没有自己的this,向上一层去找
const user = {
    name: 'aaa',
    walk: () => {
        console.log(this);
    }
}

user.walk() // window

注意:

  • 在开发中使用箭头函数前需要考虑函数中this的值,例如事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
  • 构造函数,原型函数,字面量对象中函数,dom事件函数需要使用this,不适合使用箭头函数

改变this

JavaScript 中还允许指定函数中this的指向,有3个方法可以动态指定普通函数中this的指向

  • call()
  • apply()
  • bind()
// call()
function fn(x, y) {
    console.log(this);
}
fn() //window
const obj = {}
// call 1.调用函数 2.改变this指向
fn.call(obj, 1, 2)  // this 指向obj

// apply
function fn(x, y) {
    console.log(this);
    console.log(x, y);
    
}
fn() //window
const obj = {name: '123'}
fn.apply(obj, [1, 2]) //obj
// apply作用:1.调用函数 2.改变this指向 3.参数是一个数组
// 返回值就是函数的返回值
const max = Math.max.apply(Math, [1, 2, 3])
console.log(max);

// bind()
// bind不调用函数,只改变this指向
// bind 返回值是修改this的函数
function fn() {
    console.log(this);
}
const obj = {name: '123'}
const fun = fn.bind(obj)
fun()

// 改变this的使用场景

// 按钮点击两秒后开始
const btn = document.querySelector('button')
btn.addEventListener('click', function() {
    this.disabled = true
    // setTimeout(function() {
    //     console.log(this);
    //     this.disabled = false
    // }, 2000)

    // 1.改用箭头函数
    // setTimeout(() => {
    //     this.disabled = false
    // }, 2000)

    // 2.改变回调函数的this
    setTimeout(function() {
        console.log(this);
        this.disabled = false
    }.bind(this), 2000)

})

节流防抖

节流

节流就是指连续触发事件但是在n秒中只执行一次函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    .box {
      width: 500px;
      height: 500px;
      background-color: #ccc;
      color: #fff;
      text-align: center;
      font-size: 100px;
    }
  </style>
</head>

<body>
  <div class="box"></div>

  <script src="lodash.min.js"></script>
  <script>
    // 在一定的时间内频发触发事件,只执行一次
    const box = document.querySelector('.box')
    let i = 1
    function mouseMove() {
      box.innerHTML = i++
      // 如果存在大量消耗的代码,可能造成性能问题
    }

    // 1.lodash实现
    // box.addEventListener('mousemove', _.throttle(mouseMove, 500))

    // 2.判断有定时器就不再开启新的定时器,定时器时间到了再清空
    function throttle(fn, t) {
      let timer = null
      return function() {
        if(!timer) {
          timer = setTimeout(function(){
            fn()
            timer = null   //不能用clearTimeout,清理timer的值也不会变,到期timer自己回结束,不需要再清除
          }, t)
        } 
      }
    }

    box.addEventListener('mousemove', throttle(mouseMove, 500))
  </script>
</body>

</html>

防抖

防抖就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    .box {
      width: 500px;
      height: 500px;
      background-color: #ccc;
      color: #fff;
      text-align: center;
      font-size: 100px;
    }
  </style>
</head>

<body>
  <div class="box"></div>

  <script src="lodash.min.js"></script>
  <script>
    // 防抖:在一定的时间内只执行最后一次
    const box = document.querySelector('.box')
    let i = 1
    function mouseMove() {
      box.innerHTML = i++
      // 如果存在大量消耗的代码,可能造成性能问题
    }

    // box.addEventListener('mousemove', mouseMove)

    // lodash实现防抖
    // box.addEventListener('mousemove', _.debounce(mouseMove, 500))

    // 手写防抖
    // 1.声明定时器变量
    // 2.每次事件触发的时候先判断是否有定时器,如果有先清除定时器
    // 3.如果没有定时器则开启定时器,存入到定时器变量中
    // 4.定时器里写函数调用
    function debounce(fn, t) {
        let timer
        // 返回一个匿名函数
        return function() {

          if(timer) {
            clearTimeout(timer)
          }

          // timer = setTimeout(function() {
          //   fn()
          // }, t)
          timer = setTimeout(fn, t)
        }
    }

    box.addEventListener('mousemove', debounce(mouseMove, 500))
  </script>
</body>

</html>
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值