“JavaScript进阶:含ES6语法“

JavaScript进阶(ES6)

1.let & const关键字

1.let关键字

(1) let不能在声明之前使用

num =10
console.log(num)
let num		// 报错  annot access 'num' before initialization
  • let没有变量提升

(2) let 不允许重复声明同一个变量

        let num = 10;
        num = 18;
		let num = 20;  // 报错 Identifier 'num' has already been declared
		console.log(num);

(3) let声明的变量具有块级作用域

function fun() {
	let a = 10
    console.log(a)
}
fun()			// 10
console.log(a)	//报错  a is not defined
  • let声明的变量只在所在的块级有效

letvar的区别:

// 1.var 可以先使用 再声明(不合理)
console.log(num)
var num = 10

// 2.var声明过的变量可以重复声明
var num2 = 10
var num2 = 20
console.log(num2)

// 3.var有变量提升,全局变量,没有块级作用域等
  • let就很好的解决了以上var存在的问题

2.const关键字

(1) const声明的同时必须要赋值

const uname;			// 报错
console.log(uname)		

(2) 当某个变量永远不会改变的时候,就使用const来声明

const uname = '张三'
console.log(uname)		// 张三
  • 不需要重新赋值的数据使用const

(3) const声明的变量具有块级作用域

function fun() {
	const a = 10
	console.log(a)
}
fun()				// 10
console.log(a)  	// a is not defined

let or const:

  • 变量声明优先使用const

  • 如果基本数据类型的值或复杂数据类型的地址发生变化的时候,使用let,如:

let num = 1
num++
console.log(num)		

for(let i = 0; i < 10;i++) {
	document.write(i)
}
  • 使用const声明的复杂数据类型(数组、对象等)里面存储的是地址,只要地址不变,就不会报错,如:
const arr = ['a','b','c']
arr.push('d')
console.log(arr)		

const person = {
	uname:'张三'
	age:18
}
person.address = '北京'
console.log(person)
  • 建议数组和对象的声明使用const

3.模板字符串

${表达式}

let age = 18
console.log(`今年${age}岁了`);

2.正则表达式

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

作用:

  • 验证表单(匹配)
  • 过滤敏感词(替换)
  • 字符串中提取我们想要的部分(提取)

1.正则表达式的基本使用

const str = 'javascript是一门编程语言,javascript通常用于web前端开发'
// 1.定义规则
const reg = /javascript/
// 2.检测是否匹配(匹配则返回)
console.log(reg.test(str))		// true
console.log(reg.exec(str))		// Array(1)
  • test(): 用于判断是否有符合规则的字符串,返回的是布尔值

  • exec(): 用于查找符合规范的字符串,找到返回的是数组,否则返回null

2.元字符

元字符是一些具有特殊含义的字符,可以极大的提高了灵活性和强大的匹配功能,例如英文26个字母,我们使用元字符[a-z],使用起来更简洁和灵活。

​ 元字符有: 1.边界符 2.量词 3.字符类

(1).边界符

^

console.log(/^哈/.test('哈'))		// true
console.log(/^哈/.test('二哈'))   // false

^:表示匹配行首的文本

$

console.log(/哈$/.test('哈哈')) // true
console.log(/哈$/.test('哈阿')) // false

$:表示匹配行尾的文本

如果^和$在一起使用,表示必须是精确匹配:

console.log(/^哈$/.test('哈'))  // true       
console.log(/^哈$/.test('哈哈'))// false

(2).量词

* + ?

// *(重复零次或多次 类似>=0)
console.log(/^哈*$/.test(''))          // true
console.log(/^哈*$/.test('哈哈哈'))     // true

// + (重复一次或多次 类似>=1)
console.log(/^哈+$/.test(''))		  // false
console.log(/^哈*$/.test('哈哈哈')) 	// true

// ? (重复0次或1次 类似0||1)	
console.log(/^哈?$/.test(''))		  // true
console.log(/^哈*$/.test('哈哈哈'))		// false

{n} {n,} {n,m}

// {n}(重复n次   类似=n)
console.log(/^哈{3}$/.test('哈哈'))        // false
console.log(/^哈{3}$/.test('哈哈哈'))      // true

// {n,}(重复n次或更多次  类似>=n)
console.log(/^哈{3,}$/.test('哈哈'))		// false
console.log(/^哈{3}$/.test('哈哈哈哈'))	   // true

// {n,m}(重复n到m次    类似>=n&<=m)
console.log(/^哈{1,3}$/.test('哈哈'));        // true
console.log(/^哈{1,3}$/.test('哈哈哈'));      // true
console.log(/^哈{1,3}$/.test('哈哈哈哈'));    // false

(3).字符类

(1)匹配字符集

[]

console.log(/[abc]/.test('ab'));     // true
// 精确匹配  [abc] 只选一个  n选1
console.log(/^[abc]$/.test('ab'));   // false
console.log(/^[abc]$/.test('a'));    // true
  • []:匹配括号里的任意字符

-

console.log(/^[a-z]$/.test('p'));    // true
console.log(/^[A-Z]$/.test('p'));    // false
console.log(/^[0-9]$/.test(2));      // true
console.log(/^[a-zA-Z0-9]$/.test(0));   // true
  • -:连字符,表示一个范围

^

// 表示除了英文小写字母
console.log(/[^a-z]/.test('a'))		// false
  • ^:取反符,表示除了…以外的字符(这里的^是用在[]内)

.

console.log(/[.a-z]/.test('a'));        // true
  • .:除换行符之外的任意单个字符
// 腾讯qq号(是从10000开始的)
[/^[1-9][0-9]{4,}$/]

(2)预定类

// \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]
// 日期格式
// ^\d{4}-\d{1,2}-\d{1,2}

3.修饰符

/表达式/修饰符

console.log(/^python$/ig.test('Python')) // true
  • i:ignore的缩写,表示正则匹配字母不区分大小写

  • g:global的缩写,匹配所有满足正则表达式的结果(全局查找)

4.替换文本

str.replace(/正则表达式/,'替换的文本')

const str = 'java是一门编程语言,Java语言非常优美'
const result = str.replace(/java/ig, 'javascript')
console.log(result)

使用repalce()过滤敏感词:

const text = document.querySelector('textarea');
const btn = document.querySelector('button');
const div = document.querySelector('div');

btn.addEventListener('click', function () {
            // console.log(text.value);
            div.innerHTML = text.value.replace(/激情|基情/g, '***');
            text.value = '';

        })

3.js垃圾回收机制

​ 垃圾回收机制简称GC,js中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。常见的垃圾回收算法:引用计数法标记清除法

1.引用计数法

​ ie采用的引用计数算法,定义"内存不再使用",就是看一个对象是否有指向它的引用,没有了就自动回收对象。

const arr = [1, 2, 3]
arr = null
  • 引用次数变为了0,垃圾回收机制就自动回收了该对象
  • 跟踪记录被引用的次数;如果被引用了一次,那么就记录次数1,多次引用就会累加;如果减少一个引用就减1;如果引用次数为0,则释放内存

引用技术器法存在一个致命问题:嵌套引用(循环引用):

function fun() {

            let o1 = {}
            let o2 = {}
            o1.a = o2
            o2.a = o1

            return '引用计数无法回收'
        }
        fun()
  • 如果两个对象互相引用,尽管他们已经不再使用,垃圾回收器不会进行回收,造成内存泄漏

2.标记清除法

​ 标记清除法定义"无法到达的对象";就是从根部(在js中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。

function fun() {

            let o1 = {}
            let o2 = {}
            o1.a = o2
            o2.a = o1

            return '标记清除法可以回收'
        }
        fun()
  • 全局gl来出发,从根部无法进入到函数内部,如无法直接找到函数内部的对象,稍后就会回收
  • 从根部扫描对象,能查到的就是还是要使用的,查不到的就进行回收

4.js闭包

​ 闭包概念: 一个函数对周围状态的引用捆绑在一起,内层函数中访问到其他外层函数的作用域

​ 简单理解: 闭包 = 内层函数 + 外层函数的变量

1.闭包的基本格式

// 外层函数
function fun1() {

            let a = 10
            // 内层函数
            function fun2() {
                console.log(a)
            }
            return fun2;
        }
// 外部outer
const outer = fun1()
outer() 	// 10
  • 闭包的作用: 封闭数据,提供操作,外部也可以访问函数内部的变量

2.闭包的应用

// 比如,我们要做个统计函数调用次数,函数调用一次,就++
function fun1() {

            let i = 0
            function fun2() {

                i++
                console.log(`函数被调用了${i}`)
            }
            return fun2
        }
const outer = fun1()
outer()     
  • 实现了数据私有,无法直接修改i,保证了安全
  • i是局部变量,但是却没有被回收,因为从根部查找到全局作用域的outer,继而找到i,ifun2中还在继续使用到。 这也就是使用闭包可能会产生内存泄漏

5.函数的动态参数和剩余参数

1.动态参数

arguments

function sum() {

            let s = 0
            for (let i = 0; i < arguments.length; i++) {

                s += arguments[i]
            }
            console.log(s)
        }
sum(5, 10)
sum(5, 10, 15)
  • 当我们不确定用户传递多少个参数时,使用argument动态参数

2.剩余参数

...Args

function getSum(a, b, ...arr) {

            console.log(arr) 	// []  [3]
        }
getSum(1, 2)		
getSum(1, 2, 3)
  • 前面两个参数传递给a,b,后面剩余的参数传递给...arr
  • 剩余参数允许我们将一个不定数量的参数表示为一个数组

argument动态参数和剩余参数的区别:

  • 剩余参数...是语法符号,接收最末位的实参(用于获取多余的实参)

  • 借助...获取的剩余实参,是个真数组

  • arguments获取到的实参是以伪数组的形式

6.展开运算符

...

const arr1 = [1, 3, 5]
console.log(...arr1)		// 1 3 5
  • 展开运算符能将数组展开

展开运算符最典型的应用: 求数组的最大(最小)值,合并数组等

// 求数组最大值
const arr1 = [1, 3, 5]
console.log(Math.max(...arr1))   // 5

// 合并数组
const arr2 = [2, 4, 6]
const arr = [...arr1, ...arr2]
console.log(arr)	// [1,3,5,2,4,6]

7.箭头函数

ES6引入箭头函数的目的: 是为了更简洁的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁。

使用场景: 箭头函数更适用于那些本来需要匿名函数的地方

(1) 箭头函数基本语法

const 函数名 = (形参) => { 函数体 }

const fn = () => {
            console.log(123)
        }
fn()
  • 箭头函数属于函数表达式的形式,因此也不存在函数提升

(2) 箭头函数省略()和{}

const fn2 = x => console.log(x)
fn2(1)
  • 箭头函数形参只有一个时,形参的小括号()可以省略(没有参数时,括号不能省略)
  • 箭头函数函数体只有一行代码时,函数体的大括号{}可以省略

(3) 箭头函数省略return

const fn3 = x => x + x
const result = fn3(1)
console.log(result)
  • 箭头函数只有一行代码时,可以省略return

(4)箭头函数可以直接返回一个对象

const fn4 = (uname,age) => ({ uanme: uname,age:age })
const result = fn4('张三',18)
console.log(result)
  • 这里的对象是使用()包起来,因为函数体的{}和对象的{}冲突了,所以这里使用的是()

(5) 箭头函数的参数

const getSum = (...arr) => {

            let sum = 0
            for (let i = 0; i < arr.length; i++) {
                sum += arr[i]
            }
            return sum
        }
const result = getSum(2, 3, 4)
console.log(result)
  • 箭头函数的参数是没有arguments动态参数的,但是有剩余参数...args

(6) 箭头函数的this指向

const obj = {

	uname: '张三',
            sayHi: () => {
                console.log(this)   // window
            }
        }
obj.sayHi()

const obj2 = {

            uname: '赵四',
            sayHi: function () {
                // console.log(this)
                let a = 10
                const fn = () => {
                    console.log(this)   // obj2
                }
                fn()
            }
        }
obj2.sayHi()
  • 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this

DOM事件的回调函数若使用到this,不推荐使用箭头函数:

btn.addEventListener('click', () => {
            console.log(this)   // window
        })

8.解构赋值

1.数组解构

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

const arr = [100, 60, 80]
const [max, min, avg] = arr
console.log(max,min,avg)	// 100 60 80
  • 赋值运算符=左侧[]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
  • 变量的顺序对应数组单元值的位置依次进行赋值操作
		// 数组解构细节 
        // (1)单元值少,变量多的情况
        const [a1, a2, a3] = [1, 2]
        console.log(a1, a2, a3) // 1  2  undefined
        // (2)单元值多,变量少的情况
        const [b1, b2] = [1, 2, 3]
        console.log(b1, b2)     // 1  2
        // (3)单元值多,变量少(可以使用剩余参数)
        const [c1, c2, ...c3] = [1, 2, 3, 4]
        console.log(c1, c2, c3) // 1 2 [3,4]
        // (4)防止undefined传递(可以设置默认值  有相对应的单元值就拿过来,无则使用默认值)
        const [d1 = 0, d2 = 0, d3 = 0] = [1, 2]
        console.log(d1, d2, d3) // 1  2  0
        // (5)按需导入赋值(忽略某些值)
        const [e1, e2, , e4] = [1, 2, 3, 4]
        console.log(e1, e2, e4) // 1  2  4
		// (6)支持多维数组的解构
		const arr2 = [1, 2, [3, 4]]
        const [f1, f2, [f3, f4]] = arr2
        console.log(f1,f2,f3,f4)     // 1 2 3 4

2.对象解构

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

const obj = {

	uname: '张三',
	age: 18
}
const { uname, age } = obj
console.log(uname,age)
  • 赋值运算符=左侧的{}用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
  • 对象的属性值将被赋值给与对象的属性名相同的变量
  • 注意解构的变量名不要和外面的变量名冲突否则报错
  • 对象中找不到与变量名一致的属性时,变量值为undefined

(1).修改解构的变量名

旧变量名:新变量名

		const obj = {
            uname: '张三',
            age: 18
        }
        const uname = 'pcc'
        const { uname: username, age } = obj
        console.log(username, age)  // '张三' 18
  • 如果解构的变量名和外面的变量名冲突,可以修改解构的变量名

(2).数组对象解构

const xiao = [
	{
		uname:'佩恩'
		age:18
	}
]
const [{uname,age}] = xiao
console.log(uname,age)

(3).多级对象解构

const xiao = {
	uname:'晓',
	family:{
		one:'佩恩',
		two:'宇智波鼬',
		three:'大蛇丸'
	},
	age:18
}
const {uname,family:{one,two,three}} = xiao
console.log(uname,one)
  • 里面的对象需要指定是哪个对象

9.实例成员和静态成员

实例成员: 实例对象的属性和方法即为实例成员,使用时通过对象.属性/对象.方法

静态成员: 构造函数的属性和方法即为静态成员,使用时通过构造函数.属性/构造函数.方法

		// 构造函数
        function Star(uname) {

            // 1.实例成员
            this.uname = uname
            this.sayHi = function () {
                console.log('hello')
            }
        }
        const hg = new Star('胡歌')
        hg.uname = '胡歌'
		hg.sayHi = () => {
            console.log('hello everybody')
        }
        console.log(hg.uname)
		console.log(hg.sayHi())
        // 2.静态成员
        Star.eyes = 2
        Star.walk = function () {
            console.log(this)       
        }
        console.log(Star.eyes)      // 2
        Star.walk()					// Star()
  • 实例对象相互独立,实例成员为当前实例对象使用
  • 静态成员只能通过构造函数来访问
  • 静态方法中的this指向构造函数

10.内置构造函数

​ javascript中内置构造函数:包装类型引用类型

  • 包装类型:StringNumberBoolean

  • 引用类型: ObjectArrayRegExpDate

1.Object的静态方法

(1) 获取对象的属性名

Object.keys()

const obj = {
            uname: '张三',
            age: 18
        }
console.log(Object.keys(obj))   // [uname,age]

(2)获取对象的属性值

Object.values()

const obj = {
            uname: '张三',
            age: 18
        }
console.log(Object.keys(obj))   // ['张三',18]

(3)拷贝对象

Object.assign(待拷贝对象,拷贝对象)

const obj2 = {}
// 将obj对象拷贝给obj2对象
Object.assign(obj2, obj)    
console.log(obj2)			// {uname:'张三',age:18}

拷贝对象的使用场景: 经常使用在给对象添加属性

// 给obj添加sex属性
Object.assign(obj, { sex: '男' })

2.Arrays实例方法

forEach()

const arr = [1, 2, 3, 4]
arr.forEach((ele, index) => {
            console.log(ele)	// 1 2 3 4
            console.log(index)	// 0 1 2 3
        })
  • forEach()有valueindex两个参数,value表示当前元素的值,index表示当前元素的下标
  • forEach()遍历数组,不改变原数组,经常用于查找遍历数组元素

filter()

const arr1 = [1, 2, 3, 4, 5, 6]
const result = arr1.filter((ele) => {
			// 筛选数组中为偶数的元素
            return ele % 2 === 0
        })
console.log(result)		// [2,4,6]
  • filter()有item,index,array参数,item为数组的每个元素,index为数组元素的下标,array为原始数组
  • filter()过滤数组,返回新数组,返回的是筛选满足条件的数组元素
  • filter()有return返回值

map()

const arr2 = [1, 2, 3, 4, 5]
const result = arr2.map((ele, index) => {

            return ele + '元素'
        })
console.log(result)
  • map()有element,index参数,element为数组元素,index为数组元素的下标
  • map()迭代数组,返回新数组,返回的是处理之后的数组,想要使用返回的新数组
  • map()有return返回值

reduce()

// 1.无初始值
const arr1 = [10, 20, 30]
const result = arr3.reduce(function (prev, current) {  
            return prev + current	
})
console.log(result)
// 2.有初始值
const result2 = arr1.reduce((prev, current) => prev + current, 10)
console.log(result3)    // 70
  • reduce()有prev,current,[初始值]参数,prev为上一次值,current为当前值
  • reduce()可以做累计器,返回累计处理的结果,经常用于求和等

find()

const arr = ['red', 'green', 'blue']
const result = arr.find(function (item) {
            return item === 'blue'
        })
console.log(result)     // blue
  • find()查找数组中第一个满足条件的元素的值,并返回

find()应用场景: 比如有很多数据,我们要查找并返回我们想要使用的数据

const phone = [
            {
                uname: '小米',
                price: 1999
            },
            {
                uname: '华为',
                price: 3999
            },
            {
                uname: '苹果',
                price: 5999
            }
        ]
const total = phone.find(item => item.uname === '苹果')
console.log(total)
  • 使用find()方法查找,根据item.uname==='苹果'这个关键字条件,查找到该对象,并返回该对象

every()

const arr1 = [10, 20, 30, 40]
const flag = arr1.every(item => item >= 20)
console.log(flag)   // false
  • every()检测数组所有元素是否都符合指定条件,如果所有元素都符合,则返回true,否则false

some()

const arr = [10, 20, 30, 40]
const flag = arr1.some(item => item >= 20)
console.log(flag)   // true
  • some()检测数组中的元素是否满足指定条件,如果数组中有元素满足条件,则返回true,否则false

3.String实例方法

includes(要查找的字符串[,检测位置索引号)

const str = 'pancc'
const re = str.includes('q')
console.log(re)     // false
const re2 = str.includes('n', 3)
console.log(re2)    // flase
  • includes()判断一个字符串是否包含在另一个字符串中,根据情况返回布尔值

match(regexp)

const str = "Nothing"
const re = str.match()
console.log(re)    // [""]

const str2 = 'javascript'
const reg = /java/
const result =str2.match(reg)
cosole.log(result)		// java
  • match()用于查找字符串,支持正则匹配,返回一个字符串匹配正则表达式的结果

11.原型

​ javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象。原型对象可以挂载函数,对象实例化不会多次创建原型上的函数,节约了内存。

​ 我们可以将公共的属性写在构造函数中,公共的方法写在原型prototype中。

1.原型prototype

function Star(uname, age) {
	this.uname = uname
	this.age = age
}
Star.prototype.sing = funcation() {
	console.log('唱歌~')
}
const zjl = new Star('周杰伦', 40)
zjl.sing()
const ljj = new Star('林俊杰', 40)
ljj.sing()
console.log(zjl.sing === ljj.sing)

使用原型扩展数组方法:

		// 1.求数组的最大值
        const arr = [1, 3, 5, 7, 9]
        // 使用原型对象prototype挂载函数(求最大值的方法)
        Array.prototype.max = function () {

            // 扩展运算符将数组展开
            return Math.max(...this)  // prototype原型对象中的this指向的也是实例
        }
        console.log(arr.max())      // 9 

        // 2.对数组求和
        // 使用原型对象prototype挂载函数(求和方法)
        Array.prototype.sum = function () {

            return this.reduce((prev, current) => prev + current, 0)
        }
        console.log(arr.sum())     // 25

2.constructor属性

​ 原型对象prototype里面有constructor属性,constructor属性指向该原型对象的构造函数。

function Dog() {

        }
console.log(Dog.prototype)						// Dog()
console.log(Dog.prototype.constructor === Dog)	// true
  • 有了constructor属性,我们就可以知道这个原型对象的构造函数是哪个了

constructor属性的应用:

  		function Star() {

        }
		Star.prototype = {

            // 添加一个constructor属性来指向他的构造函数
            constructor: Star,
            sing: function () {
                console.log('唱歌')
            },
            dance: function () {
                console.log('跳舞')
            }
        }
        console.log(Star.prototype)
  • 如果有多个对象的方法,我们可以给原型对象采取对象赋值的形式,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后原型对象就没有constructor来指向当前的构造函数了
  • 我们可以在修改后的原型对象中,添加一个constructor属性来指向他的构造函数

3.对象原型proto

​ 每个实例对象中都有一个__proto__对象原型,__proto__指向构造函数的prototype原型对象

		function Dog() {

        }
        Dog.prototype.run = function () {

            console.log('二哈在跑')
        }
        const erha = new Dog()
        erha.run()
  • 之所以我们的对象实例,能使用到构造函数prototype原型对象的属性和方法,是因为有了__proto__对象原型的存在
		function Star() {

        }
        const zjl = new Star()
        console.log(zjl.__proto__)  
        console.log(zjl.__proto__ === Star.prototype) // true
  • __proto__也有constructor属性
  • __proto__指向构造函数的prototype原型对象

prototype__proto__小结:

  • prototype是原型对象, 构造函数中自动有原型对象
  • prototype原型对象和对象原型__proto__里面都有constructor属性,都指向创建实例对象 / 原型的 构造函数
  • __proto__属性在实例对象里面, 它指向原型对象prototype

4.原型继承

子类的原型 = new 父类

		function Person() {
            this.eyes = 2
            this.head = 1
        }

        function Man() {

        }
        // 原型继承
        Man.prototype = new Person()
        Man.prototype.constructor = Man		// 重新让constructor指回原型的构造函数
       	const jack = new Man()
        console.log(jack.eyes)		// 2
		console.log(jack.head)		// 1
  • 通过原型继承Person,就能使用到父类的属性和方法

5.原型链

​ 基于原型对象的继承使得不同构造函数的对象关联在一起,并且使这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链。

function Star() {

        }
const zjl = new Star()
console.log(zjl.__proto__ === Star.prototype)     // true
// 构造函数中的原型对象也是对象,所以里面也有__proto__
console.log(Star.prototype.__proto__ === Object.prototype)  // true
console.log(Object.prototype.__proto__)     // null
  • 原型链也就是一种查找规则
  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
  • 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
  • 如果还没有就查找原型对象的原型(Object的原型对象)
  • 依次类推直到找到Object为止(null)
  • __proto__对象原型的意义就在于为对象成员查找机制提供一个方向

使用instanceof运算符用于检测构造函数中的prototype属性是否出现在某个实例对象的原型链上:

	    console.log(zjl instanceof Star)    // true
        console.log(zjl instanceof Object)  // true
        console.log(zjl instanceof Array)   // false

12.递归函数

​ 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数(自己调自己)

		let i = 1
        function fun() {

            console.log(`正在打印第${i}`)
            if (i >= 6) {
                return
            }
            i++
            fun()
        }
        fun()

13.浅拷贝&深拷贝

1.浅拷贝

​ 使用浅拷贝拷贝数据时,如果是简单数据类型拷贝的就是,如果是复杂数据类型拷贝的就是地址值

拷贝对象:

  • Object.assign()

  • { ...obj }

拷贝数组:

  • Array.prototype.concat()
  • [...arr]
		const obj = {

            uname: '张三',
            age: 18,
            family: {

                uname: '王五'
            }
        }
		const o2 = {}
		Object.assign(o2, obj)
		// 简单数据类型
		o2.age = 19
		console.log(o2)     // 19
		console.log(obj)    // 18
		// 复杂数据类型
		o2.family.uname = '赵四'
		console.log(o2)		// 赵四
		console.log(obj)	// 赵四
  • 以上可以看出拷贝复杂数据类型时,还是指向的同一个地址,操作同一个对象。

2.深拷贝

​ 深拷贝拷贝的直接就是对象,而不是地址。

三种方式:

  • 通过递归实现深拷贝
  • lodash/cloneDeep
  • JSON.stringify()

(1)递归函数实现深拷贝

		const obj2 = {

            uname: '刘备',
            age: 18,
            hobby: ['乒乓球', '篮球'],
            family: {
                wujiang: '关羽'
            }
        }
		const o3 = {}
		// 递归函数
        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] === o3.uname (给新对象添加属性)
                    newObj[k] = oldObj[k]
                }
            }
        }
        deepCopy(o3, obj2)
        o3.age = 20
        o3.hobby[0] = '游泳'
        o3.family.wujiang = '张飞'
        console.log(o3)
        console.log(obj2)
  • 深拷贝就是拷贝出来的新对象不会影响到旧对象,要想实现深拷贝,可以使用到递归函数
  • 如果拷贝的是简单数据类型的时候, 直接赋值就可以了。但是遇到数组的,再次调用这个递归函数
  • 如果遇到的是对象的形式,再次调用这个递归函数。
  • 先处理Array后处理Object

(2)利用lodash实现深拷贝

_.cloneDeep(拷贝的对象)

<script src="./lodash/lodash.min.js"></script>
<script>

        const obj = {

            uname: '刘备',
            age: 18,
            hobby: ['三顾茅庐', '如鱼得水'],
            family: {
                wujiang: '关羽'
            }
        }

        const o = _.cloneDeep(obj)
        o.family.wujiang = '诸葛亮'
        console.log(o)		
        console.log(obj)
</script>

(3) 利用JSON实现深拷贝

		const obj = {

            uname: '刘备',
            age: 18,
            hobby: ['三顾茅庐', '如鱼得水'],
            family: {
                wujiang: '关羽'
            }
        }
        const o = JSON.parse(JSON.stringify(obj))
        o.family.wujiang = '诸葛亮'
        console.log(o)
        console.log(obj)
  • 将对象转换为JSON字符串(得到的是简单数据类型)
  • 再将JSON字符串转换为对象(得到的是新的对象)

14.异常处理

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

(1).throw抛异常

		function fun(x, y) {

            if (!x || !y) {
                // throw '用户没有传参'
                throw new Error('用户没有传参')
            }
            return x + y
        }
        console.log(fun())
  • throw抛出异常,程序也会终止执行
  • throw后面跟的是错误信息
  • Error对象配合throw使用,能够设置更详细的错误信息

(2).try/catch捕获异常

		try {
            
            const p = document.querySelector('.div')
            p.style.color = 'deeppink'
        } catch (err) { 
            console.log(err.message)
            throw new Error('你看看,我就说标签写错了把')
            // return
        }

        finally {
            console.log('执行')
        }
  • try块中放可能出现的错误代码
  • 若try块的代码出错,则会执行catch块,并捕获到错误信息
  • catch中提示的是浏览器的错误信息,但是不会中断程序的执行,想要中断可以使用return,也可以搭配throw
  • finally块中不管程序有没有出错,都会执行

(3).debugger调试

		const arr2 = [2, 4, 6];
        const newArr = arr2.map(function (ele, index) {

            debugger
            console.log(ele);   // 2 4 6
            console.log(index); // 0 1 2
            return ele + '块钱';
        })
        console.log(newArr);
  • debugger可以在代码上打上断点

15.this指向

​ 使用call(),apply(),bind()都可以改变this的指向。

call(thisArg,arg1,arg2...)

		const obj = {
            uname: '张三',
            age: 18
        }

        function fn(x, y) {

            console.log(this)
            console.log(x + y)  // 3
        }
        fn.call(obj, 1, 2)    // fn的this指向了obj

apply(thisArg,[argArray])

fn.apply(obj2, [1, 2])		  //  // fn的this指向了obj

apply()的应用场景:

		// 求数组的最大值
        const arr = [1, 2, 3, 4, 5]
        const max = Math.max.apply(null, arr)   // null表示this的指向为null
        console.log(max)
        // console.log(Math.max(...arr))
  • apply()使用经常跟数组有关系

bind(thisArg,arg1,arg2...)

const obj3 = {

            uname: '关羽',
            age: 23
        }
function fn3() {

            console.log(this)
}
const fun = fn3.bind(obj3)
console.log(fun)
fun()
  • 返回的是一个函数(通过拷贝原函数得到的函数,这个函数里面的this是更改过的)

bind改变定时器内部的this指向:

		// 需求: 有一个按钮,点击后就禁用,2秒后开启
        const btn = document.querySelector('button')
        btn.addEventListener('click', function () {
            
            this.disabled = true
            setTimeout(function () {
                this.disabled = false
            }.bind(btn), 2000)  
        })
  • 定时器this原本指向window 改变this指向btn

call(),apply,bind三者的区别:

  • call()和apply()都会调用函数,bind不会调用函数

  • call()和apply()传递的参数不一样,call传递的是普通参数的形式,apply传递的参数是数组

16.性能优化

1.防抖(debounce)

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

​ 防抖(debounce): 单位时间内,频繁触发事件,只执行最后一次。

使用场景:

  • 搜索框搜索输入,手机号、邮箱验证输入等(只需要用户最后一次输入完,再发送请求)
		// 利用防抖实现性能优化
        // 需求: 鼠标在盒子上移动,里面的数字就会变化+1
        const box = document.querySelector('.box')
        let i = 1
        function mouseMove() {
            box.innerHTML = i++
        }
        // lodash库实现防抖(500毫秒之后才去+1)
        // _.debounce(func,时间) 
        box.addEventListener('mousemove', _.debounce(mouseMove, 500))
  • 如果里面存在大量消耗性能的代码,如dom操作,数据处理等可能会造成卡顿,此时就需要用到防抖

2.节流(throttle)

​ 节流指连续触发事件但是在n秒内只执行一次函数。比如在1000ms内,不管触发多少次事件,只执行一次(在这个时间内,不会被打断,只执行当前这一次)。

​ 节流(throttle):单位时间内,频繁触发事件,只执行一次。

使用场景:

  • 鼠标移动mousemove,页面尺寸缩放resize,滚动条滚动scroll
		// 利用节流实现性能优化
        // 需求: 鼠标在盒子上移动,里面的数字就会变化+1
		const box = document.querySelector('.box')
        let i = 1
        function mouseMove() {
            box.innerHTML = i++
        }
        // 利用lodash库实现节流(1000毫秒之后才去+1)
        // _.throttle(func,时间)   在wait秒内最多执行func一次
        box.addEventListener('mousemove', _.throttle(mouseMove, 1000))

节流案例:

<script src="./lodash/lodash.min.js"></script>
<video src=""></video>
<script>
		// 案例: 页面打开,就可以记录上一次的视频播放位置
        // 1.获取元素,要对视频进行操作
        const video = document.querySelector('video')
        video.ontimeupdate = _.throttle(() => {
            // 把当前的时间存储到本地
            localStorage.setItem('currentTime', video.currentTime)
        }, 1000)

        // 2.打开页面触发事件,就从本地存储里面取出记录的时间
        video.onloadeddata = () => {
            video.currentTime = localStorage.getItem('currentTime') || 0
        }
</script>
  • ontimeupdate事件触发的时候,每隔1秒钟,就记录当前时间到本地存储
  • 下次打开页面,onloadeddata事件触发,从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s

bounce`): 单位时间内,频繁触发事件,只执行最后一次。

使用场景:

  • 搜索框搜索输入,手机号、邮箱验证输入等(只需要用户最后一次输入完,再发送请求)
		// 利用防抖实现性能优化
        // 需求: 鼠标在盒子上移动,里面的数字就会变化+1
        const box = document.querySelector('.box')
        let i = 1
        function mouseMove() {
            box.innerHTML = i++
        }
        // lodash库实现防抖(500毫秒之后才去+1)
        // _.debounce(func,时间) 
        box.addEventListener('mousemove', _.debounce(mouseMove, 500))
  • 如果里面存在大量消耗性能的代码,如dom操作,数据处理等可能会造成卡顿,此时就需要用到防抖

2.节流(throttle)

​ 节流指连续触发事件但是在n秒内只执行一次函数。比如在1000ms内,不管触发多少次事件,只执行一次(在这个时间内,不会被打断,只执行当前这一次)。

​ 节流(throttle):单位时间内,频繁触发事件,只执行一次。

使用场景:

  • 鼠标移动mousemove,页面尺寸缩放resize,滚动条滚动scroll
		// 利用节流实现性能优化
        // 需求: 鼠标在盒子上移动,里面的数字就会变化+1
		const box = document.querySelector('.box')
        let i = 1
        function mouseMove() {
            box.innerHTML = i++
        }
        // 利用lodash库实现节流(1000毫秒之后才去+1)
        // _.throttle(func,时间)   在wait秒内最多执行func一次
        box.addEventListener('mousemove', _.throttle(mouseMove, 1000))

节流案例:

<script src="./lodash/lodash.min.js"></script>
<video src=""></video>
<script>
		// 案例: 页面打开,就可以记录上一次的视频播放位置
        // 1.获取元素,要对视频进行操作
        const video = document.querySelector('video')
        video.ontimeupdate = _.throttle(() => {
            // 把当前的时间存储到本地
            localStorage.setItem('currentTime', video.currentTime)
        }, 1000)

        // 2.打开页面触发事件,就从本地存储里面取出记录的时间
        video.onloadeddata = () => {
            video.currentTime = localStorage.getItem('currentTime') || 0
        }
</script>
  • ontimeupdate事件触发的时候,每隔1秒钟,就记录当前时间到本地存储
  • 下次打开页面,onloadeddata事件触发,从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值