JavaScript从入门到精通--学习笔记

JavaScript

1、JavaScript介绍

1.1、JavaScript是什么

JavaScript是一种运行在客户端(浏览器)的编程语言,实现人机交互效果。可以实现一些网页特效、验证表单、数据交互、服务端编程(node.js)。

JavaScript主要是有以下内容组成的:

  • ECMAScript:JavaScript语言的基础语法核心知识
  • Web APIs:
    • DOM 操作文档,比如页面元素的移动、修改等。
    • BOM 操作浏览器,比如页面弹窗、数据监测、数据存储等。

JavaScript权威网站:MDN

1.2、JavaScript书写位置

  • 内部

    直接写在body内部,在script标签内书写。

    <body>
      <!-- 内部js -->
      <script>
        alert('你好!')
      </script>
    </body>
    
  • 外部

    通过script:src引用文件外部的js文件。

    <!-- 外部js -->
    <script src="./js/demo02.js"></script>
    
  • 内联

    代码写在标签内部。

    <!-- 外部js -->
    <!-- <script src="./js/demo02.js"></script> -->
    

1.3、JavaScript输入输出语法

输入和输出可以理解为人机的交互,用户通过输入设备输入相关信息,计算机通过处理后把结果展示给用户,这便是一次输入和输出的过程。

  • 常用的输出

    // 向body内输出内容
    document.write('输出的内容')	
    //页面弹出警告框
    alert('输出的内容')
    //控制台输出
    console.log('输出的内容')
    
  • 常用的输入

    //显示一个对话框,根据输入的文本内容来输入
    prompt('输入内容')
    
  • 弹窗

    confirm('是否确定')
    

2、变量

2.1、变量是什么

变量是用来存储数据的容器,它不是数据本身,仅仅用来存储数据的盒子。它可以用于数据的存储,并且存储的数据可以反复使用,增加了代码的灵活性。

2.2、变量的基本使用

1.声明变量:要想使用变量,首先就是创建、定义或者声明变量,声明变量的关键字有const、let、var

  • var

    ①存在变量提升:意思是在JavaScript运行时并不是严格按照上下顺序运行的,使用var进行变量申明时,JavaScript引擎会将变量声明部分提到前面,但是他只提升声明,不提升赋值,同时同一变量出现多次重复声明,后面的声明会覆盖前面的声明。

    a = 1
    console.log(a)
    //输出结果为undefined
    var a
    
    var b = 1
    var b = 2
    console.log(b)
    //输出结果为2
    

    ②在函数中声明变量时,该变量是局部变量,未使用var重新声明的情况下,该变量是全局变量

    var a = 1
    function test(){
    	var a =2
    	console.log(a)
    	//输出结果为2
    }
    console.log(a)
    //输出结果为1
    
  • let

    ①会出现死区

    console.log(a)	//	ReferenceError: a is not define
    let a = 1
    

    ②在局部的let声明作用域只在局部有效

    function test(){
    	let a = 1
    }
    console.log(a)
    //ReferenceError: a is not define
    

    ③在相同作用域下不能够重复声明

    let a = 1
    let a = 2
    //Uncaught SyntaxError: Identifier 'a' has already been declared
    
  • const

    ①const声明后就不能在改变,为只读变量,用来定义一些常量, 例如:const PI = 3.14

    const a = 1
    a = 2
    // TypeError: Assignment to constant variable.
    

    ②const声明必须初始化

    const a
    a = 2
    // SyntaxError: Missing initializer in const declaration
    

    ③const声明的变量并不是值不能够改变,而是变量所指向的内存地址不能改变

    const obj{
    	age: 18
    }
    
    obj.age = 20
    
    
    obj = {
    	age:20
    }
    //  SyntaxError: Identifier 'obj' has already been declared
    

2.赋值变量:将数据存放在定义的变量内容,用符号=

let age
age = 18

3.变量的本质:是程序在内存中申请的一块用来存放数据的小空间

4.变量命名规则与规范:

  • 规则

    ①不能使用关键字作为变量名

    ②只能用下划线、字母、数字、$组成,且数字不能开头

    ③字母严格区分大小写,如Age和age时不同变量

  • 规范

    ①起名要有意义

    ②遵守小驼峰命名法:例如:userName

2.3、数组

数组是一种将一组数据存储在单个变量名下的方式,数组是按顺序保存的,所以每个数据都有自己的编号,计算机中的是从0开始的。在数组中,数据的编号也叫索引或下标,数组可以存储任意类型的数据。

let arr = [10,20,30,40]

console.log(arr)

3、数据类型

数据类型主要分为两大类:

  • 基本数据类型

    number 数字

    string 字符串

    boolean 布尔

    undefined 未定义

    null 空

  • 引用数据类型

    object 对象

3.1、Number

数字类型即我们数学中所学到的数字,可以是整数、小数、正数、负数。数字型通常与算术运算符一起使用。注:在运算中有时候可能出现NaN(Not a Number),它代表计算错误,它是一个不正确的或者一个未定义的数学操作所得到的结果。

console.log('老师' - 2)	//NaN

3.2、String

通过单引号(‘’)、双引号(“”)或者反引号(``)包裹的数据都叫字符串,推荐使用单引号。字符串之间可以使用+把两个字符串连接起来。

let name = '小米'
let age = '22'
console.log(name + age)	//小米22

模板字符串:主要作用是简化拼接字符串和变量,字符串必须使用反引号``内容凭借变量时,用${}包住变量。

let age = 20
//没有使用模板字符串之前拼接字符串和变量
document.write(`我今年` + age + `岁了`)
//模板字符串
document.write(`我今年${age}岁了`)

更高效,更常用的字符串拼接写法,利用map()和join()数组方法实现字符串拼接

  • map可以遍历数组处理数据,并且返回新的数组,map也称映射,两个元素集合之间存在相互对应的关系
const arr = ['red','blue','green']
const newArr = arr.map(function (ele, index){
	console.log(ele)
	console.log(index)
	return ele + '颜色'
})
console.log(newArr)
  • join方法用于把数组中的所有元素转化为一个字符串
const arr = ['red','blue','green']
console.log(arr.join())			//red,blue,green	默认用,做分隔
console.log(arr.join(''))			//redbluegreen

3.3、Boolean、undefined、null

Boolean:表示肯定或者否定时在计算机中对应的数据就是布尔型数据,它有两个固定的值true和false,表示肯定的数据用true(真),否定的数据用false(假)。其中‘’、0、undefined、null、false、NaN转化为布尔型都是false,其余则为true

undefined: 未定义是比较特殊的类型,只有一个值undefined。在变量只声明但未赋值的情况下,变量的默认值为undefined,通常在开发中我们通过声明一个变量来接受后端传送过来的数据时,我们在检验数据是否传递过来时,可以查看变量是不是undefined,就可以判断数据是否传递成功。

null:表示无、空或者值未知。它和undefined不同,undefined表示没有赋值,而null表示赋值了,但内容为空。

console.log(undefined + 1)	//NaN
console.log(null + 1)	//1

3.4、检查数据类型

通过typeof关键字检测数据类型,type运算符可以返回被检测的数据类型,它支持两种语法:1、作为运算符:typeof x (常用)2、函数形式:typeof(x)。

let num = 10
let str = 'string'
let flag = false
let un
let obj = null
console.log(type num)	\\number
console.log(type str)	\\string
console.log(type flag)	\\boolean
console.log(type un)	\\undefined
console.log(type obj)	\\null

3.4、类型转换

由于JavaScript时弱数据类型,它之间也不知道变量属于哪种数据类型,只有赋值了才清楚。例如,当我们使用表单或prompt获取过来的数据默认是字符串类型的,此时就不能直接进行简单的加法运算,它会直接将两个数字字符串拼接,如100+100输出结果为100100,因此我们需要对数据进行转换。转换主要分为隐式转换和显式转换。

  • 隐式转换

    在某些运算符被执行时,系统内部自动将数据类型进行转换,这种转换就成为隐式转换。其中需要注意,+作为正好解析可以把字符串转化为数字,例如:+‘123‘,任何数据和字符串相加结果都是字符串。

    console.log('pink' + 1)	//pink1
    console.log('2' + 2)	//22
    console.log(2 - '2')	//0
    
  • 显式转换

    把数据转化成为自己需要的数据类型。

    • Number():把数据转化成为数字类型,如果字符串内容非数字结果为NaN。

      let str = '123'
      console.log(Number(str))	//123
      console.log(Number('pink'))	//NaN
      
    • parseInt():只保留整数

      console.log(parseInt('12px'))	//12
      console.log(parseInt('12.52px'))	//12
      
    • parseFloat():可以保留小数

      console.log(parseFloat('12px'))	//12
      console.log(parseFloat('12.56px'))	//12.56
      console.log(parseFloat('abc12px'))	//NaN
      

4、运算符

4.1、赋值运算符

对变量进行赋值的运算符:=、+=、-=、*=、/=、%=

let num = 1
num += 1
console.log(num)	//2
num -= 1
console.log(num)	//1
num *= 2
console.log(num)	//2
num /= 2
console.log(num)	//1
num %= 2
console.log(num)	//1

4.2、一元运算符

自增运算符就是一元运算符:++、–

let num = 1
num++	//num=2	先参加运算再自增
++num	//num=3	先自增再参与运算

4.3、比较运算符

比较两个数据的大小的运算符:>、<、>=、<=、=、!==

4.4、逻辑运算符

用于多重条件的组合的运算符:&&(与)、| |(或)、!(非)

4.5、运算符优先级

优先级运算符顺序
1小括号()
2一元运算符++ – !
3算术运算符先* / % 后 + -
4关系运算符> >= < <=
5相等运算符== != === !==
6逻辑运算符先&& 后||
7赋值运算符=
8逗号运算符,

5、语句

5.1、表达式与语句

表达式就是可以被求值的代码,JavaScript引擎会将其计算出一个结果。语句是一段可以执行的代码。

5.2、分支语句

可以让我们有选择性的执行我们想要执行的代码

  • if分支语句

    if语句有单分支、双分支、多分支

    //单分支
    if ('判断条件'){
    	console.log('true')
    }
    //双分支
    if ('判断条件'){
      console.log('true')
    }else{
      console.log('false')
    }
    //多分支
    if ('判断条件1'){
      console.log('1')
    }else if ('判断条件2'){
      console.log('2')
    }else if ('判断条件3'){
      console.log('3')
    }else ('判断条件4'){
      console.log('4')
    }
    
  • 三元运算符

    一般用来取值用

    条件 ? 满足条件执行的代码 : 不满足条件执行的代码
    
  • switch语句

    switch (数据) {
    	case1:
    		代码
    		break
    	case2:
    		代码
    		break
    	case3:
    		代码
    		break
    		...
      deefault:
        ...
    }
    

5.3、循环语句

重复执行一些操作,循环三要素:变量起始值、终止条件、变量变化量

  • while循环

    while (循环条件){
    	需要重复执行的代码(循环体)
      
    }
    
  • for循环

    for(变量起始值;终止条件;变量变化量){
    	循环体
    }
    

5.5、break和continue

  • break是直接跳出整个循环,一般用于结果已经得到,后续的循环不需要的时候可以使用
  • continue是退出本次循环,一般用于排除或者跳过某个喧嚣的时候,可以使用continue
for(let i = 0;i < 5;i++){
	if(i == 3){
		break
	}
	console.log(i)
}
//打印结果为0,1,2
for(let i = 0;i < 5;i++){
	if(i == 3){
		continue
	}
	console.log(i)
}
//打印结果为0,1,2,4,5

5.5、嵌套循环

一个循环里再套一个循环,一般用于for循环中

for(外部声明记录循环次数的变量;循环条件;变化量){
	for(内部声明记录循环次数的变量;循环条件;变化量){
		循环体
	}
}

6、数组

6.1、声明数组

有两种声明数组的方式;

let 数组名 = [数据1,数据2...,数据n]	//推荐

let 数组名 = new Array(数据1,数据2,。。数据n)

6.2、操作数组

数组本质是数据集合,操作数据无非就是增、删、改、查

  • let arr = [1,2,5,6,4]
    arr.push('8')	//从数组最后添加,puah返回值是数组的长度
    arr.unshift('0')	//从数组开头添加
    
  • let arr = [1,2,3,4,5]
    arr.pop()	//删除数组最后的元素,同时返回最后一个元素
    arr.shift()	//删除数组第一个元素,同时返回第一个元素
    arr.splice(start,deleteCount)	//常用
    //start:指定起始位置(从0开始)	deleteCount:表示要移除的数组元素的个数,如果省略就从起始 位置删除到最后
    
  • let arr = ['pink','red','green']
    arr[0] = 'blue'
    console.log(arr)	//['blue','red','green'] 
    
    let arr = [3,1,5,4,2]
    arr.sort(function(a,b){
      return a - b
    })//升序排列
    
    arr.sort(function(a,b){
      return b - a
    })//降序排列
    
  • let arr = ['pink','red','green']
    console.log(arr[0])	//pink
    //遍历
    for(let i = 0; i < arr.length; i++){
      console.log(arr[i])
    }
    //不推荐,k的数据类型是字符串
    for(let k in arr){
      console.log(arr[k])
    }
    

7、函数

7.1、函数是什么

function。是被设计为执行特定任务的代码块,把需要重复使用的代码封装起来,需要使用时就调用相应的函数,他能够使代码复用率更高

7.2、函数的使用

函数是先声明后使用,其命名与变量命名基本一致采用小驼峰命名法,前缀尽可能为动词。函数参数的传递,最好是在形参的位置赋予初始值,防止实参为空时造成出错。其中return后面的代码不会被执行。

//函数声明
function 函数名(){
	代码
}
function sayHi(){
  console.log("hi!")
}
//函数调用
函数名()
sayHi()
//函数传参
function getSum(start = 0,end = 0){
  let sum = 0
  for(let i = start; i =< end; i++){
    sum += i
  }
  console.log(sum)
}
getSum(1,50)
//函数的返回值
function fn(){
  return 20
}
let num = fn()
console.log(num)
console.log(fn())	//打印结果为20

7.3、作用域

作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突,目前主要有全局作用域和局部作用域

  • 全局作用域:全局有效,作用于所有代码执行的环境
  • 局部作用域:函数内部的let声明的变量,只能在当前函数内部访问和修改

变量访问原则:就近原则,在能够访问到的情况下先局部,局部没有再找全局

函数内部没有声明的变量属于全局变量(不建议这样做)

7.4、匿名函数

没有名字的函数,无法直接使用,匿名函数只能通过函数表达式或立即执行的方式进行使用,他与具名函数不同,他的使用必须先申明后调用。

//函数表达式
let fn = function(){
	console.log('我是函数表达式')
}
fn()

//立即执行函数
(function(){
  console.log('立即执行函数')
})();

(function(x , y){
  console.log(x + y)
})(1,2);

(function(){}());	//这是立即执行函数的第二种写法
//立即执行函数的前后必须加上分号;

7.5、逻辑中断

中断只存在于&&和||中,当满足一定条件会让右边代码不执行。逻辑与左边为假就中断返回左边的值,逻辑或左边为真就中断

function fn(x , y){
	x = x || 0
  y = y || 0
  console.log(x + y)
}

console.log(true || false)	//输出为true,或是一真则真,所以不会输出false
console.log(false || true)	//输出为true
console.log(true && false)	//输出为false,与是一假为假,所以会看后面的值
console.log(11 || 22)		//输出11,找到真就不再看后面的值
console.log(false || 11)	//输出为11
console.log(11 && 22)		//输出为22,与会输出最后一个真值

8、对象

8.1、什么是对象

对象是JavaScript里的一种数据类型,可以理解为是一种无序的数据集合,用来描述同一个事物的不同元素,比如一个人的姓名,年龄,性别等信息。对象由属性和方法组成,属性和属性之间用逗号隔开

let 对象名 = {
	属性名:属性值,
	方法名:函数
}

8.2、对象的使用

对象的属性使用主要是增删改查

//声明对象
let obj = {
	name: ''
	num: ''
	weight: ''
	address: ''
  'goods-name': ''
}
//查
console.log(obj.address)
obj['属性名']		//多用于遍历对象的过程 
obj['goods-name']
//改
obj.name = 'pink'
//增
obj.gender = '女'
//删	 
delete obj.gender

数据行为性的信息称为方法,如跑步、唱歌等,其本质就是函数

//声明对象方法
let obj = {
	uname: '刘德华'song: function(){
		console.log('冰雨')
	}
}
//调用对象方法
obj.song()

8.3、遍历对象

对象里面是无序的键值对,没有规律,没有长度属性,不能像数组一样遍历

let obj = {
	uname: '',
	age: '',
	gender: '' 
}
for (let k in obj){
  console.log(obj[k])
}

8.4、内置对象

JavaScript内部提供的对象,包含各种属性和方法给开发者调用,如document.write()、console.log()等。以下是常用的内置对象:

  • Math:是JavaScript提供的一个数字对象,该对象包含的方法由:
    • random:生成0-1之间的随机数(包含0不包含1)
    • ceil:向上取整
    • floor:向下取整
    • max:找最大数
    • min:找最小数
    • pow:幂运算
    • abs:绝对值

8.5、拓展

  • 关键字:let、var、function、if、else、switch、case、break
  • 保留字:int、short、long、char
  • 基本数据类型(简单数据类型或值类型):在存储时变量中存储的是值本身,比如string、number、boolean、undefined、null
  • 引用数据类型(复杂类型):在存储时变量中存储的仅仅是地址(引用),比如通过关键字new创建的对象、Object、Array、Date等
  • 栈:由操作系统自动分配释放存放函数的参数值、局部变量的值等,简单数据类型存放到栈里面
  • 堆:存储复杂类型(对象),一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收,引用数据类型存放到堆里面

9、Web API基本认识

9.1、作用和分类

作用:就是使用js取操作html和浏览器

分类:DOM(文档对象模型)、BOM(浏览器对象模型)

9.2、什么是DOM

DOM是用来呈现以及与任意HTML或XML文档交互的API,DOM是浏览器提供的一套专门用来操作网页内容的功能,作用是开发网页内容特效和实现用户交互

9.3、DOM树

将HTML文档以梳妆结构只管的表现出来,我们称之为文档树或DOM树,描述网页内容关系的名词,文档树直观的体现了标签与标签之间的关系

9.4、DOM对象

DOM对象:浏览器根据html标签生成的JS对象

所有的标签属性都可以在这个对象上面找到,修改这个对象的属性会自动映射到标签身上,DOM的核心思想:把网页内容当作对象来处理。

document对象是DOM里提供的一个对象,所以它提供的属性和方法都是用来访问和操作网页内容的,例如:document.write(),网页所有内容都在document里面

10、获取DOM对象

10.1、根据css选择器来获取DOM元素(重要)

  • 选择匹配的第一个元素 ,CSS选择器匹配的第一个元素,一个HTMLElement对象
document.querySelector('css选择器名')
  • 选择匹配的多个元素,CSS选择器匹配的NodeList对象集合,我们得到的是一个为数组,有长度索引号,但是没有数组相应的方法,可以用for进行遍历
document.querySelectorAll('css选择器')

10.2、其他获取DOM元素的方法(了解)

  • 根据id获取一个元素
document.getElementById('nav')
  • 根据标签获取一类元素,获取页面所有的div
document.getElementByTagName('div')
  • 根据类名获取元素,获取页面所有类名为w的元素
document.getElementByClassName('w')

11、操作元素

DOM对象都是根据标签生成的,所以操作标签,本质上就是操作DOM对象。

  • innerText属性:将文本添加/更新到任意标签位置,显示纯文本,不解析标签
const info = document.querySelector('.info')
info.innerText = 'hello!'
  • innerHTML属性:将文本添加/更新到任意标签位置,会解析标签,多标签建议使用模板字符串
const info = document.querySelector('.info')
info.innerHTML = '<h1>hello!</h1>'

11.1、操作元素属性

通过DOM还可以对标签元素的属性进行设置和修改

//对象.属性 = 值
const pic = document.querySelector('')
pic.title = ''
pic.src = ''

11.2、操作元素样式

通过DOM还可以对标签元素的样式进行设置和修改

  • 通过style属性操作CSS
//对象.style.样式属性 = 值
const box = document.querySelector('.box')
box.style.width = '200px'											 //如果有-连接符,需要转换为小驼峰命名法
box.style.backgroundColor = 'pink'							//赋值时需要加CSS单位
  • 通过操作类名(className)操作CSS,他会覆盖以前的类名
//active是一个CSS类名
元素.className = 'active'
  • 通过classList操作类控制CSS,我们可以通过classList方式追加和删除类名
//追加一个类
元素.classList.add('类名')
//删除一个类
元素.classList.remove('类名')
//切换一个类
元素.classList.toggle('类名')
//查看是否存在某个类
元素.classList.contains('类名')

11.3、操作表单元素属性

表单属性中添加就有效果,移除就没有效果,一律使用布尔值表示,如果为true代表添加了该属性,如果是false代表移除了该属性

11.4、自定义属性

在html5中推出了专门的data-自定义属性,在标签上一律以data-开头的都是自定义属性,在DOM对象上一律以dataset对象方式获取的都是自定义属性

<body>
  <div class="box" data-id="10">盒子</div>
  <script>
  	const box = document.querySelector('.box')
    console.log(box.dataset.id)
  </script>
</body>

12、定时器-间歇函数

定时器函数可以开启和关闭定时器,在定时器中调用外部的函数不需要加小括号,直接使用函数名,定时器返回的是一个id数字

  • 开启定时器
//setInterval(函数,间隔时间)
setInterval(function (){
  console.log('一秒执行一次')
}1000)	//每间隔一秒调用一次函数
  • 关闭定时器
let 变量名 = setInterval(函数,间隔时间)
clearInterval(变量名)

let timer = setInterval(function(){
  console.log('hi~~')
},1000)
clearInterval(timer)

13、事件监听

  • 什么是事件

事件是在编程时系统内发生的动作或者发生的事情,比如用户在网页上单击一个按钮

  • 什么是事件监听

就是设计一个程序检测某事件是否发生,一旦发生就立刻对该事件做出相应的响应,也称绑定事件或注册事件,比如点击按钮、点击播放轮播图等

  • 事件监听三要素:
    • 事件源:哪个元素被事件触发,要获取该元素
    • 事件类型:用什么方式触发,比如点击click,鼠标经过mouseover等
    • 事件的调用函数:事件触发后要做什么
//元素对象.addEventListener('事件类型', 要执行的函数)

const btn = document.querySelector('.btn')
btn.addEventListener('click', function(){
  alert('点击了' )
})

13.1、事件类型

  • 鼠标事件:鼠标触发

    • click 鼠标点击
    • mouseenter 鼠标经过,不存在事件冒泡
    • mouseleave 鼠标离开
    • mouseover 鼠标经过,存在事件冒泡
  • 焦点事件:表单获得光标

    • focus 获得焦点
    • blur 失去焦点
  • 键盘事件:键盘触发

    • Keydown 键盘按下触发
    • Keyup 键盘抬起触发
  • 文本事件:表单输入触发

    • input 用户输入事件
    • change 用户在输入框内的内容发生变化时触发

13.2、环境对象

环境对象是指函数内部特殊的变量this,它代表着当前函数运行时所处的环境,作用时弄清楚this的指向,可以让我们代码更简洁。函数的调用者是谁,this就指向谁。

const btn = document.querySelector('.btn')
btn.addEventListener('click', function(){
  console.log(this)	//button
})

13.3、回调函数

如果将函数A作为参数传递给函数B时,A就是回调函数,比如定时器中的函数和监听器中的函数都是回调函数。

14、事件流

14.1、事件流和两个阶段说明

事件流是指事件完整执行过程章的流动路径,主要有捕获和冒泡两个阶段,捕获是从大往小或者是从父到子找到指定目标,冒泡是从小到大或者从子到父的过程,在实际开发中主要中岛冒泡

14.2、事件捕获

事件捕获是从DOM的根元素开始去执行对应的事件(从外到里) ,对于同名事件的触发先后顺序,例如监听事件的第三个参数为true时会开启事件捕获

14.3、事件冒泡

事件冒泡是当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发,事件冒泡是被默认开启的

14.4、阻止冒泡

因为事件冒泡是默认发生的, 所有很容易影响到父级元素,如果想要吧事件限制在当前元素内,就需要组织事件冒泡,这里补充一个阻止元素的默认行为

事件对象.stopPropagetion()
e.preventDefault()

14.5、解绑事件

在某些特定的情况下,我们对于绑定的事件后面可能不在使用需要解除绑定事件时就会用到相应的解绑方法removeEventListener(),注意匿名函数不能解绑

const btn = document.querySelector('.btn')
function fn(){
  console.log(this)	//button
}
btn.addEventListener('click', fn)
btn.removeEventListener('click',fn)

15、事件委托

事件委托是利用事件流的特征解决一些开发需求的知识技巧,能够减少注册次数,提高程序性能,主要是利用事件冒泡的特点,把事件绑定到父元素身上,从而通过触发子元素时,传递到父元素触发事件

<ul>
  <li>第一个</li>
  <li>第二个</li>
  <li>第三个</li>
  <li>第四个</li>
  <li>第五个</li>
  <p>我不需要变色</p>
</ul>
<script>
	const ul = document.querySelector('ul')
  ul.addEventListener('click'. function(e){
  	if(e.target.tagName === 'LI'){
    	e.target.style.color = 'red'
  	}
	})
</script>

16、其他事件

16.1、页面加载事件

等待外部资源加载完毕时触发的事件,如果某些事件需要等待页面资源全部加载完才处理就需要用到页面加载事件。

window.addEventListener('load', function () {
	console.log('页面加载事件')
})

有时候无需等待样式表、图像等完全加载,我们就可以使用DOMContentLoaded事件触发。

document.addEventListener('DOMContentLoaded', function () {
	
})

16.2、页面滚动事件

滚动条在滚动的时候持续触发的事件,比如固定的导航栏,返回顶部标签等

window.addEventListener('scroll', function () {

})

在有些时候,我们需要知道页面滚动的尺寸时,就需要使用scrollTop和scrollLeft属性可读写

document.documentElement.scrollTop = 800
window.addEventListener('scroll', function () {
	console.log(document.documentElement.scrollTop)	
})

16.3、页面尺寸事件

会在窗口尺寸发生改变的时候触发的事件

window.addEventListener('resize', function () {
  
})

检测屏幕宽度和高度

window.addEventListener('resize', function () {
	let w = document.documentElement.clientWidth
  let h = document.documentElement.clientHeight
  console.log(w)
})

16.4、元素尺寸与位置

当我们需要页面滚动具体距离位置时,我们就需要获取元素的具体高度和位置

offsetWidth和offsetHeight用于获取宽高

const div = document.querySelector('.div')
console.log(div.offsetHeight)

offsetLeft和offsetTop用于获取位置(只读)

const header = document.querySelector('.header')
const sk = document.querySelector('.sk')
window.addEventListener('scroll', function () {
  const n = document.documentElement.scrollTop
  header.style.top = n >= sk.offsetTop ? 0 : '-80px'
})

element,getBoundingClientRect()获取相对位置

17、日期对象

日期对象是用来表示时间的对象,可以得到当前系统时间

17.1、实例化

在代码中发现一个用new创建对象时,这个操作就是实例化

//获得当前时间
const date = new Date()
//获取指定时间
const date1 = new Date('2022-5-1 08:30:00')

17.2、日期对象方法

因为日期对象返回的数据我们不能直接使用,所以需要转换为实际开发中常用的格式

方法作用说明
getFullYear()获得年份获取四位年份
getMonth()获得月份取值0~11(月份需要+1)
getDate()获得月份中的某一天不同月份取值也不同
getDay()获取星期取值为0~6(星期天是0)
getHours()获取小时取值0~23
getMinutes()获取分钟取值为0~59
getSeconds()获取秒取值为0~59
const date = new Date()

17.3、时间戳

我们可以用时间戳来完成计算倒计时的效果,时间戳是指1970年01月01日起到现在的毫秒数,它是一种特殊的计量时间的方式。获取时间戳的三种方式:

  • 使用getTime()方法
const date = new Date()
console.log(date.getTime())
  • 简写+new Date(),无需实例化
console.log(+new Date())
  • 使用Date.now(),无需实例化,但是只能得到当前时间戳,而前两种可以返回指定的时间戳
console.log(Date.now())

18、节点操作

18.1、DOM节点

DOM树中的每一个内容都是一个节点,节点分为元素节点也就是所有标签(重点),属性节点也就是每个标签的属性,文本节点也就是所有的文本

18.2、查找节点

在有些时候我们需要操作的节点比较多,但是节点与节点之间存在一定的父子关系或者兄弟关系时,我们就只需要获取一个节点就可以找到其他相应的节点

  • 父节点查找
子元素.parentNode
  • 子节点查找
父元素.childNodes	//获得所有子节点、包括文本节点(空格、换行)、注释节点等
父元素.childern		//重点,仅获得的所有元素节点,返回值是一个伪数组
  • 兄弟节点
兄弟元素.previousElementSibling
兄弟元素.nextElementSibling

18.3、增加节点

很多情况下我们需要在页面中增加元素,增加一个元素我们就需要先创建一个新的元素节点,然后再把新的节点放入指定元素内

  • 创建节点
document.createElement('标签名')
  • 追加节点
父元素.appendChild(节点名)	//插入到子元素最后 
父元素.insertBefore(节点名)	//插入到子元素最前
  • 克隆节点
克隆节点.cloneNode(true)

18.4、删除节点

若一个节点在页面中不在需要时,我们就可以把他删除,在DOM操作中我们要删除某个元素必须通过其父元素

父元素.removeChild(删除的元素)	

19、M端事件

在移动端访问时会触发触屏事件touch(也称触摸事件),Android和ios都有

const div = document.querySelector('div')
div.addEventListener('touchstart', function(){})	
div.addEventListener('touchend', function(){})
div.addEventListener('touchmove', function(){})

20、Window对象

20.1、BOM

BOM(Browser Object Model)就浏览器对象模型,window对象是一个全局对象,是JavaScript中的顶级对象,像aler()、document这些都是window的属性,所有通过var定义在全局作用域的变量、函数都会变成window对象的属性和方法。

20.2、定时器-延时函数

它与间歇函数不同,间歇函数是每隔一定时间执行一次,而延时函数是多少秒以后执行一次后便不再执行

let timer = setTimeout(回调函数, 等待的毫秒数)	//开启延时函数
clearTimer(timer)		//清楚延时函数

20.3、JS执行机制

JavaScript语言的一大特点就是单线程,就是同一个时间只能做一件事情。。单线程即意味着所有任务都需要排队,必须等待上一个任务完成才能够执行下一个任务,这就会导致JS执行时间过长,页面渲染不流畅,导致页面渲染出现卡顿的状态。为了解决这个问题,JS中出现了同步和异步。

  • 同步

所有任务都是按照顺序执行,即便任务之间可以并行执行,同步任务都是在主线程上执行,形成一个执行栈。

  • 异步

任务与任务之间可以进行并行执行,JS的异步是通过回调函数实现的。异步任务添加任务队列中。

JS会先执行执行栈中的同步任务,异步任务放入任务队列中,一旦执行栈中所有同步任务执行完毕,系统就会依次读取任务队列中的异步任务。

20.4、location对象

location的数据类型是对象,它拆分并保存了URL地址的各个组成部分

  • href属性获取完整的URL地址,对其赋值时用于地址的跳转
console.log(location.href)
location.href = 'http://www.baidu.com'
  • search属性获取地址中携带的参数,符号?后面的部分
console.log(location.search)
  • hash属性获取地址中的哈希值,符号#后面的部分
cosole.log(location.hash)
  • reload方法用来刷新当前页面,传入参数true时表示强制刷新
location.reload(true)

20.5、navigator对象

navigator的数据类型时对象,记录了浏览器自身的相关信息,对于我们后面解决不同端兼容问题会有一定的作用。

!(function () {
	const userAgent = navigator.userAgent
  const android = userAgent.match(/(Android);?[\S\/]+([\d.])?/)
  const iphone = userAgent.match(/(iPhone\sOS)\S([\d_]+)/)
  if (android || iphone){
    location.href = '手机端网页地址'
  }
})();

20.6、history对象

history的数据类型是对象,主要管理历史记录,该对象与浏览器地址栏的操作相对应,如前进、后退、历史记录等,常用的方法和属性:

history对象方法作用
back()后退功能
forward()前进功能
go(参数)前进后退功能,如果参数是1前进一个页面;如果是-1后退一个页面

21、本地存储

本地存储是HTML5提出的解决本地存储的大量数据的方案,数据存储在用户浏览器中,设置、读取方便,甚至页面刷新数据也不会丢失,容量较大,sessionStorage和localStorage约5M左右。

21.1、localStorage

可以将数据永久存储在本地(用户电脑),除非手动删除,否则关闭页面也会存在,可以多窗口(页面)共享,以键值对的形式存储使用,数据类型是字符串

  • 存储数据
localStorage.setItem('键', '值')
  • 获取数据
localStorage.getItem('键')
  • 删除数据
localStorage.removeItem('键')   

21.2、sessionStorage

生命周期为关闭浏览器窗口,在同一窗口(页面)下数据可以共享,以键值对的形式存储使用,用法和localStorage基本相同

21.3、存储复杂数据类型

本地存储只能存储字符串类型,需要将复杂的数据类型转化为JSON字符串,再存储到本地

localStorage.setItem('obj', JSON.stringify(对象))

把JSON字符串转化为复杂数据类型

let str = localStorage.getItem('obj')
console.log(JSON.parse(str))

22、正则表达式

22.1、什么是正则表达式

正则表达式是用于匹配字符串中字符组合的模式,在JavaScript中,正则表达式也是对象类型,通常用来查找、替换那些符合正则表达式的文本,许多语言都支持正则表达式。比如在密码、邮箱需要有格式要求的数据就需要正则表达式过滤。

22.2、语法

首先定义规则,其次根据规则去查找

  • 定义正则表达式语法:
const reg = /变量/
const str = '字符串'
reg.test(str)	//在字符串中寻找是否存在我查找的变量,有就返回true,反之则false
reg.exec(str)	//返回的是一个数组

22.3、元字符

  • 普通字符:大多数的字符仅仅智能描述本身,这些被称为普通字符,也就是他们智能匹配与他们自身相同的字符。

  • 元字符:是一些具有特殊含义的字符,极大的提高了灵活性和强大的匹配功能。

    • 边界符(表示位置,开头和结尾,必须用什么开头,用什么结尾)用来提示字符所处位置
      • ^ 表示匹配行首的文本(以谁开始)
      • $ 表示匹配行尾的文本(一谁结束)
    • 量词(表示重复次数)用来表示某个模式出现的次数
      • ​ * 重复零次或多次
      • + 重复一次或更多次
      • ? 重复零次或一次
      • {n} 重复n次
      • {n,} 重复n次或更多次
      • {n,m} 重复n到m次
    • 字符类(比如 \d 表示 0~9)
      • [] 匹配字符集合,只要字符串包含括号内的任意一个字符都为真
      • ​ ^ 表示取反
      • . 匹配除了换行符之外的任何单个字符
      • 预定义:指的是某些常见模式的简写方式
        • \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]

22.4、修饰符

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

/表达式/修饰符

i是ignore,表示匹配字母不区分大小写

g是global,表示匹配所有满足正则表达式的结果

replace()方法

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

ES6+

1、作用域

作用域规定了变量够被访问的范围,离开了这个范围变量就不能被访问,主要分为局部作用域和全局作用域

1.1、局部作用域

局部作用域分为函数作用域和块作用域

  • 函数作用域:在函数内部声明的变量只能在函数内声明的,外部无法直接访问,函数执行完后,函数内部的变量实际被清空了
  • 块作用域:在JavaScript中被{ }包裹的变量都叫块作用域,代码块内部声明的变量外部有可能无法被访问,用let和const声明的变量会产生块作用域,var声明的变量不会产生块作用域,外部就有可能能够访问到其变量

1.2、全局作用域

<script>标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在其他作用域也可以被访问,window对象动态添加的属性也是全局的,不加关键字的变量也是全局变量,尽可能的减少全局变量的声明,防止全局变量被污染。

1.3、作用域链

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

1.4、垃圾回收机制

垃圾回收机制检测GC,JS中内存分配和回收的都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收

  • 内存的生命周期

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

①内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存

②内存使用:即读写内存,也就是使用变量、函数等

③内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存

全局变量只有在页面被关闭时才会回收,局部变量一般使用完不再使用时就立刻自动回收

内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏

  • 引用计数法:当对象的引用次数为0时就回收该对象,但如果两个对象相互引用,尽管他们已经不再使用,垃圾回收器也不会对其进行回收,导致内存泄漏。(已不再使用)
  • 标记清除法: 就是从JS中的全局对象开始定时扫描内存中的对象,只要全局对象能都到达的对象,都是还需要使用的,那些无法由全局对象触及的对象被标记为不再使用,稍后进行回收。(目前使用)

1.5、闭包

一个函数对周围状态的引用捆绑在一起,内存函数中访问到其外层函数的作用域,简单理解:闭包 = 内存函数 + 外层函数的变量

闭包中的作用:封闭数据,提供操作,外部也可以访问函数内部的变量。

闭包应用:实现数据的私有,但是闭包会存在内存泄漏的问题

2、函数进阶

2.1、函数提升

函数提升与变量提升比较类似,是指函数在声明之前就被调用,函数提升能够是函数的声明调用更加灵活,但是函数表达式不存在提升现象, 函数提升出现在相同作用域当中。

2.2、函数参数

  • 动态参数

在有些情况下我们不知函数需要传递多少参数时,我们就需要使用arguments动态的存储参数,是函数内部内置的为数组变量

function sum(){
  let s = 0
  for (let i = 0; i < arguments.length; i++){
    s += arguments[i]
  }
  console.log(s)
}

sum(1,2,3)
sum(2,3,4,5,6,7,8)
  • 剩余参数(推荐使用)

允许我们将一个不定数量的参数表示为一个数组,剩余参数获取的是一个真数组,用于获取多余的参数

function sum(...arr){
  let s = 0
  for (let i = 0; i < arr.length; i++){
    s += arr[i]
  }
  console.log(s)
}

sum(1,2,3)
sum(2,3,4,5,6,7,8)
  • 展开运算符:(…)可以将一个数组进行展开,不会修改原数组,他可以配合Math对象求最值、合并数组等
const arr = [1,2,3]
console.log(Math.max(...arr))	//3
console.log(Math.mim(...arr))	//1

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

2.3、箭头函数(重要)

箭头函数属于表达式函数,依次不存在函数提升,箭头函数更适用于那些需要匿名函数的地方

  • 基本语法
const fn = () => {
  console.log(123)
}
fn()
//只有一个参数时,可以省略小括号
const fn = x => {
  console.log(x)
}
fn(1)
//如果函数体只有一行代码,可以写到一行上,并且无需写return直接返回值
const fn = (x,y) => x + y
console.log(fn(1,2))
//直接返回一个对象
const fn = (uname) => ({uname:uname})
console.log(fn('xia'))
  • 箭头函数参数

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

const getSum = (...arr) => {
  let sum = 0
  for(let i = 0; i < arr.length; i++){
    sum += i
  }
  return sum
}
console.log(getSum(2,3,4))

2.4、箭头函数this(重要)

箭头函数不会创建自己的this,他会根据自己的作用域链去找上一层的this,在事件回调函数中不太推荐使用箭头函数,因为事件回调函数中的this为全局的window

3、解构赋值

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

3.1、数组解构

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

const arr = [1,2,3]
const [a,b,c] = arr
console.log(a)//1
console.log(b)//2
console.log(c)//3

//还可以交换变量
let a = 1
let b = 2;	//这里必须加分号
[a, b] = [b, a]
  • 变量多,单元值少的情况,多的变量的值就为undefined
  • 利用剩余参数解决变量少 单元值多的情况
  • 支持多维数组

3.2、对象解构

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

 const obj = {
   uname: 'xia',
   age: 18
 }
 
 const {uname, age} = obj		//属性名和变量名尽量一致可以建立相应的联系
 //可以给新的变量名赋值
 const {uname:username, age} = obj 
 console.log(username)

//解构数组对象
const pig = [{
  uname: 'piky'
  age: 2
}]

const [{uname, age}] = pig 

//多级对象解构
const pig = {
  name: '佩奇',
  family: {
    mother: '猪妈妈'
    father:	'猪爸爸'
    sister: '猪姐姐'
  },
  age: 6
}

const {name, family:{mother,father,sister},age} = pig

3.3、遍历数组forEach方法(重点)

forEach()方法用于调用数组的每个元素,并将元素传递给回调函数,主要适用于遍历数组的每个元素,他没有返回值

被遍历的数组.forEach(funciton (当前数组元素, 当前元素索引号){
//函数体
})

3.4、筛选数组filter方法(重点)

filter()方法创建一个新的数组,新数组中的元素时通过检查指定数组中符合条件的所有元素

const arr = [10,20,30]
const newArr = arr.filter(item =>{
  return item > 10
})

4、深入对象

4.1、创建对象的三种方式

  • 利用对象字面量创建对象
const o = {
	name: '佩奇'
}
  • 利用new Object创建对象
const obj = new Object()
  • 利用构造函数创建对象

4.2、构造函数

时一种特殊的函数,主要用来初始化对象的,他们的命名比大写字母开头,并且只能通过new关键字来操作,用new关键字调用函数的行为被称为实例化,内部不需要return,构造函数自动返回创建的新对象

function People(uname, age){
  this.name = uname
  this.age = age
}
const p1 = new People('xia', 18)
console.log(p1) 

4.3、实例成员&静态成员

  • 实例成员:通过构造函数创建的对象称为实例对象,实例对象中的属性和方法成为实例成员(实例属性和实例方法),也就是构造函数内部通过this添加的成员,它可以通过实例化对象的形式进行访问
  • 静态成员:构造函数的属性和方法被称为静态成员(静态属性和静态方法)静态方法中的this指向构造函数,也就是我们通过构造函数对其进行后期添加上去的属性和方法,它只能通过实例化对象的形式进行访问,并且不会添加到原本的构造函数中去
function Person(name, age){
  this.name = name
  this.age = age
  this.talk = function() {
    console.log('talk')
  }
}

const p = new Person('xia', 18)
//实例属性
console.log(p.name)//xia
//实例方法
p.talk()

//静态属性
Person.eyes = 2
console.log(Person.eyes)
//静态方法
Person.walk = function () {
  console.log(this.eyes)//可以通过静态方法去访问实列属性
}

5、内置构造函数

在JavaScript中常见的数据类型分为三种:**基本数据类型:**字符串、数值、布尔、undefined、null;**引用类型:**对象Array、RegExp、Date等;**包装类型:**String、Number、Boolean等

5.1、Object

Object是内置的构造函数,用于创建普通对象,常用的静态方法:

  • Object.keys()静态方法获取对象中所有属性值
const o = {uname: 'xia', age: 18}
console.log(Object.keys(o))		//获取属性名,返回数组['uname','age']
console.log(Object.values(o))	//获取属性值,返回数组['xia', '18']
  • Object.assign()静态方法用于对象的拷贝,常用于增加属性
const o = {uname: 'xia', age: 18}
const obj = {}
Object.assign(obj, o)

5.2、Array

Array是内置的构造函数,用于创建数组

方法作用说明
forEach遍历数组不返回数组,经常用于查找遍历数组元素
filter过滤数组返回新数组,返回的是筛选满足条件的数组元素
map迭代数组返回新数组,返回的是处理之后的数组元素,想要使用返回的新数组
reduce累计器返回累计处理的结果,经常用于求和等
join元素拼接返回字符串,将数组元素拼接成为字符串
find查找元素返回符合测试条件的第一个数组元素,如果没有符合条件的就返回undefined
every检测数组检测数组元素中符合指定条件的元素,如果所有元素都符合就返回true,反之则false
arr.reduce(function(上一个值, 当前的值){}, 起始值)

const arr = [1,2,3]

const total = arr.reduce(function(prev, current){
  return prev + current
})
console.log(total)	//6

const total = arr.reduce(function(prev, current){
  return prev + current
}, 4)
console.log(total)	//10

//箭头函数写法
const total = arr.reduce((prev, current) => prev + current)

5.3、String

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

属性或方法作用说明
length获取字符串长度返回的类型是数值Number
split(‘分隔符’)拆分字符串将字符串拆分成数组,返回的是数组
substring()截取字符串根据参数截取字符串的部分
startsWith()检测字符开头检测字符是否以某字符开头
includes()判断数组包含判断一个字符串是否包含在另一个字符串内,返回值为布尔型

5.4、Number

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

  • toFixed() 设置保留小数位数
const num = 12.345
console.log(num.toFixed(2))//12.34

6、深入面向对象

6.1、编程思想

  • 面向过程:面向过程就是分析出解决问题所需要的步骤,然后用函数吧这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了

    • 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用面向过程编程。
    • 缺点:没有面向对象易维护、易复用、易拓展
  • 面向对象:面向对象就是把事物分成一个一个对象,然后由对象之间分工与合作,在面向对象的程序开发思想中,每一个对象都是功能中心,具有明确分工,具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目,具有封装性、继承性、多态性

    • 优点:易维护、易复用、易拓展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,是系统更加灵活、更加易于维护
    • 缺点:性能比面向过程低

6.2、构造函数

封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装,但是我们在使用构造函数时,会存在浪费内存的问题,我们每创建一个封装对象,我们就会在栈(存放地址)和堆(存放对象)里面产生一块内存空间去存放对象,但是对于封装对象内的不会对结果产生影响的方法在每一块内存中都会出现,相同的内容重复出现这样就会造成内存的浪费

7、原型

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

function Star(uname, age){	//公共的属性写构造函数内
	this.uname = uname
  this.age = age
}
console.log(Star.prototype)	//返回一个原型对象
Star.prototype.sing = function(){		//公共的方法写原型对象内
  console.log('唱歌')
}
const ldh = new Star('刘德华', 20)
const zxy = new Star('张学友', 30)
console.log(ldh.sing === zxy.sing) //返回结果为true,说明sing函数是共享的

7.1、constructor属性

每个原型对象中都有constructor属性,该属性指向该原型对象的构造函数,在我们需要创建多个对象方法时,我们可以采用给原型对象采用对象的形式赋值,但是修改后我们的原型对象constructot就不再指向当前构造函数了,所以我们需要再修改后的原型对象中,添加一个constructor指向原来的构造函数

function Star(name) {
  this.name = name
}

Star.prototype = {
  constructor: Star,	//这里我们需要将constructor指向Star构造函数,这样才不会修改原型对象
  sing: function(){console.log('唱歌')}
  dance: function(){console.log('跳舞')}
}
console.log(Star.prototype.constructor)	//指向Star

7.2、对象原型

对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在,__proto__是JS非标准属性,[[prototype]]和__proto__意义是相同的,都是用来表明当前实例对象指向那个原型对象prototype,__proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了更好的去理解构造函数、原型对象、对象原型之间的含义,可以通过以下代码结合上图进行理解

function Star(name) {
	this.name = name
}

const ldh = new Star('刘德华')
console.log(Star);
console.log('-----------------------');
// 构造函数的prototype属性,返回的是一个对象,这个对象就是原型对象
// 在原型对象中我们可以看到两个属性,一个是constructor它是指向构造函数的,另一个[[prototype]]和__proto__意义相同
console.log(Star.prototype);
console.log('-----------------------');
// 我们通过使用原型对象的constructor属性又指向了构造函数
console.log(Star.prototype.constructor);
console.log(Star.prototype.constructor === Star);
console.log('-----------------------');
// 我们通过new实例化对象
console.log(ldh);
console.log('-----------------------');
// 通过实例化对象的constructor属性指向构造函数
console.log(ldh.constructor);
console.log(ldh.constructor === Star);
console.log('-----------------------');
// 实例化对象通过__proto__属性(对象原型)指向原型对象prototype
console.log(ldh.__proto__);
console.log(ldh.__proto__ === Star.prototype);
console.log('-----------------------');

7.3、原型继承

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

//常规的继承
const People = {
  eyes: 2,
  head: 1,
  talk: function () {
    console.log('说话')
  }
}

function Man() { }
Man.prototype = People
Man.prototype.constructor = Man
const xia = new Man()
console.log(xia)

function Women() { }
Women.prototype = People
Women.prototype.constructor = Women
//这不是对于Women来说是单独的方法,Man不应该具有的方法,但是因为prototype是大家共享的,所以Man也能调用该方法
Women.prototype.baby = function () {
  console.log('孩子');
}
const red = new Women()
console.log(red)

对于上面的问题,当我们声明了多个构造函数并且这多个构造函数之间只有部分不相同时,我们就可以通过原型继承的形式共享

function People() {
  this.eyes = 2
  this.head = 1
  this.talk = function () {
    console.log('说话')
  }
}

function Man() { }
Man.prototype = new People()
Man.prototype.constructor = Man
const xia = new Man()
console.log(xia)

function Women() { }
Women.prototype = new People()
Women.prototype.constructor = Women
Women.prototype.baby = function () {
  console.log('孩子');
}
const red = new Women()
console.log(red)

7.4、原型链

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

原型链其实就是一个查找规则,当访问一个对象的属性或者方法时,首先查找对象本身有没有这个属性或方法,如果没有我们就查找他的原型对象,也就是__proto__指向的prototype原型对象,如果在原型对象中也没有,我们还可以通过原型对象的原型,也就是Object的原型对象,依此类推直到找到null为止,__proto__原型对象的意义就是为对象成员查找机制提供了一条查找路线,可以时用instanceof运算符来检测构造函数的prototype属性是否出现在某个实例对象的原型链上

function Obj() {}
const o = new Obj()
console.log(o.__proto__ === Obj.prototype)
console.log(o.__proto__.__proto__);
console.log(Obj.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__)
console.log(o instanceof Obj);
console.log(o instanceof Object);
console.log(o instanceof Array);
console.log(Array instanceof Object);

8、深浅拷贝

开发中我们经常需要复制一个对象,但是由于对象的数据类型是引用类型,我们通过普通的赋值方式只是赋值了地址,并没有把数据一并赋值,这时候我们就需要使用拷贝,浅拷贝和深拷贝只针对引用类型

8.1、浅拷贝

浅拷贝:是拷贝的地址

  • 拷贝对象:Object.assign() / 展开运算符{…obj}拷贝对象
  • 拷贝数组:Array.prototype.concat()或者[…arr]

但是如果是简单的数据类型拷贝值,引用数据类型拷贝的就是地址(因此单层拷贝不会出现问题,但是嵌套的对象拷贝就会有问题)

const obj = {
	name: 'xia',
	age: 18
}

const o = {...obj}
o.age = 20
console.log(obj)
console.log(o)

const o = {}
Object.assign(o, obj)
o.age = 20
console.log(obj)
console.log(o)

8.2、深拷贝

深拷贝:拷贝的是对象,不是地址

  • 通过递归实现深拷贝

    如果一个函数在内部可以调用其本身,那么这个函数就是递归函数,但是递归很容易发生“栈溢出”的问题,所以必须要加退出条件

    const Obj = {
      name: 'xia',
      age: 18,
      hobby: ['篮球', '乒乓球'],
      family: {
        dad: 'D',
        mom: 'M'
      }
    }
    
    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)
    console.log(Obj);
    o.age = 20
    o.hobby[1] = '足球'
    o.family.dad = 'X'
    console.log(o);
    
  • lodash/cloneDeep

    js库loadsh里面的cloneDeep内部实现了深拷贝,我们只需要应用loadsh的库调用这个方法既可实现深拷贝

    <script src="./lodash.min.js"></script>
    <script>
      const Obj = {
        name: 'xia',
        age: 18,
        hobby: ['篮球', '乒乓球'],
        family: {
          dad: 'D',
          mom: 'M'
        }
      }
    
      const o = _.cloneDeep(Obj)
      console.log(Obj);
      o.family.dad = 'X'
      console.log(o);
    </script>
    
  • 通过JSON实现

    在JSON中我们可以把对象转化为JSON类型的字符串,同样的我们也可以把JSON类型的字符串转化为对象,通过这种方式进行深拷贝

    const Obj = {
      name: 'xia',
      age: 18,
      hobby: ['篮球', '乒乓球'],
      family: {
        dad: 'D',
        mom: 'M'
      }
    }
    
    const o = JSON.parse(JSON.stringify(Obj))
    console.log(Obj);
    o.family.dad = 'X'
    console.log(o);
    

9、异常处理

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

9.1、throw抛异常

throw会抛出异常信息,后面跟的是错误提示信息,程序也会终止执行,Error对象配合throw使用,能够设置更详细的错误信息。

function fn(x, y) {
  if (!x || !y) {
    throw new Error('没有参数')
  }
  return x + y
}
console.log(fn());

9.2、try/catch捕获异常

我们可以通过try/catch捕获错误信息(浏览器提供的错误信息),将预估可能发生错误的代码写在try代码段中,如果try代码段中出现错误后,会执行catch代码段,并截获错误信息,finally不管是否错误,都会执行

<p>123</p>
<script>
  function fn() {
    try {
      const p = document.querySelector('.p')
      p.style.color = 'red'
    } catch (err) {
      console.log(err.message);
      return
    } finally {
      alert('')
    }
    console.log(11);
  }
  fn()

9.3、debugger

debugger是代码打断点,用于代码调试使用

10、处理this

this是JavaScript最具“魅惑”的知识点,不同的应用场合this的取值可能会有意想不到的结果,在此我们对以往学习过的关于【this默认的取值】情况进行归纳总结

3.1、this的指向

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

箭头函数中的this与普通函数不同,事实上箭头函数中并不存在this,箭头函数会默认帮我们绑定外层的his的值,引用的就是最近作用域中的this,他会向外层作用域中一层一层的查找,直到有this的定义

3.2、改变this

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

  • call()

    使用call方法调用函数,同时指定被调用函数中this的值

    const obj = {
      name: 'xia'
    }
    
    function fn(x, y) {
      console.log(this);
      console.log(x + y);
    }
    
    fn.call(obj, 1, 2)
    
  • apply()

    使用apply方法调用函数,同时指定被调用函数中this的值,第二个参数必须时数组

    const obj = {
      name: 'xia'
    }
    
    function fn(x, y) {
      console.log(this);
      console.log(x + y);
    }
    
    fn.apply(obj, [1, 2])
    
  • bind( )

    bind方法不会调用函数,但是能够改变函数内部this指向,他不能调用函数,但是他的返回值是一个新函数

    const obj = {
      name: 'xia'
    }
    
    function fn(x, y) {
      console.log(this);
    }
    
    const fun = fn.bind(obj)
    fun()
    
  • 相同点:都可以改变函数内部的this指向

  • 区别点

    • call和apply会调用函数,并且改变函数内部this指向
    • call和apply传递的参数不一样,call传递参数aru1,aru2…形式apply必须数组形式[arg]
    • bind不会调用函数,可以改变函数内部this指向
  • 应用场景

    • call调用函数并且可以传递参数
    • apply经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
    • bind不调用函数,但是还想改变this指向,比如改变定时器内部的this指向

11、性能优化

11.1、防抖

单位时间内,频繁触发事件,只执行最后一次

  • 第一种是利用loadsh库中的_.debounce()
<div style="width: 500px;height: 500px;background-color: gray;font-size: 100px;color: #fff;text-align: center;">
</div>
<script src="./lodash.min.js"></script>
<script>
  const div = document.querySelector('div')
  let i = 1
  div.addEventListener('mousemove', _.debounce(() => {
    div.innerHTML = i++
  }, 500))
</script>
  • 第二种是利用seTtimeout
<div style="width: 500px;height: 500px;background-color: gray;font-size: 100px;color: #fff;text-align: center;">
</div>
<script>
  const div = document.querySelector('div')
  let i = 1

  function fd(t) {
    let timer
    return function () {
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        div.innerHTML = i++ //这个位置也可以封装成函数
      }, t)
    }
  }
  div.addEventListener('mousemove', fd(500))
</script>

11.2、节流

单位时间内,频繁触发事件,只执行一次

  • 第一种是利用loadsh库中的_.throttle()
<div style="width: 500px;height: 500px;background-color: gray;font-size: 100px;color: #fff;text-align: center;">
</div>
<script src="./lodash.min.js"></script>
<script>
const div = document.querySelector('div')
let i = 1

div.addEventListener('mousemove', _.throttle(() => { div.innerHTML = i++ }, 2000))
</script>
  • 第二种是利用seTtimeout
<div style="width: 500px;height: 500px;background-color: gray;font-size: 100px;color: #fff;text-align: center;">
</div>
<script>
  const div = document.querySelector('div')
  let i = 1

 
  function fd(t) {
    let timer = null
    return function () {
      if (!timer) {
        timer = setTimeout(() => {
          div.innerHTML = i++ //这个位置也可以封装成函数
          timer = null
        }, t)
      }
    }
  }
  div.addEventListener('mousemove', fd(500))
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值