事件传播:
* 当我们在子元素内触发了一个事件的时候,
* 他的对应的父元素也会触发相同的事件
* 事件传播的两种方式: 冒泡与捕获
*
* 目标: 我们点击的元素就是目标
*
* 冒泡: 从目标开始, 逐层向上传递事件, 这个过程叫做 事件冒泡
* 1. 目标
* 2. 目标的父元素
* 3. 目标的父元素的父元素
* 4. 目标的父元素的父元素.....
* 5. body
* 6. document
* 7. window
*
* 在 开发中, 默认的事件传播方式为 冒泡
*
* 注意: 事件传播的时候, 只要触发了对应的事件, 那么传播一定会传递上去,
* 哪怕中间的一些元素没有绑定对应的事件
*
* 捕获: 从页面的最顶层, 一直向下传递到 目标
* 1. window
* 2. document
* 3. body
* 4. 目标的父元素
* 5. 目标
*
* 冒泡和捕获, 只有执行顺序的区别, 其他的没有任何区别
// DOM元素.addEventListener('事件类型', 事件处理函数, 一个布尔值决定当前的传播方式) 第三个参数默认为 false, 代表 传播方式为 冒泡
box1.addEventListener(
'click',
function () {
console.log('box11111111111111111111111111111')
},
true
)
box2.addEventListener('click', function () {
console.log('box22222222222222222222222222')
}, true)
document.body.addEventListener('click', function () {
console.log('body........................')
}, true)
* 事件委托
*
* 因为 JS 中 事件的传播方式默认是 冒泡
* 所以我们触发了子元素的事件时, 会将事件传递给父级
*
* 这是冒泡的核心, 我们的事件委托就是利用了 事件冒泡完成的
*
* 其实就是将子级的事件, 委托给共同的父级
*
*
* 好处:
* 1. 简化代码量与内存空间
* 2. 后续的子元素, 都会有对应的事件
* this
function fn() {
console.log(this)
}
1. 在代码中直接调用函数 this === window
fn()
2. 将上述的函数 放在一个对象中调用 this === 当前对象 (当前调用者)
const obj = {
name: '我是obj对象的名字',
age: 10086,
objFn: fn
}
obj.objFn()
3. 将上述的函数 放在 定时器或者倒计时器内部 this === window
window.setTimeout(fn, 10)
4. 将上述函数 放在事件处理中 this === 事件源
var box = document.querySelector('div')
box.onclick = fn
5. 将函数放在自执行函数中 目前市场已经不多见 this === window
(function fn() {
console.log(this)
})()
(书写上要执行的函数)(这个小括号可以给第一个括号内的函数传参)
;(function () {
console.log('当你看到我的时候, 请你找一下, 有没有函数的调用')
})()
;(function (num) {
console.log('当你看到我的时候, 请你找一下, 有没有函数的调用', num)
})(10086)
* 是每一个函数中都有的一个 变量
*
* 因为 this 的值在书写的时候 无法确定, 只有在函数被调用的时候才能确定
function fn(num) {
console.log(num, this)
}
var obj = {
name: '我是 obj 对象'
}
fn(10086) // window
// 需求: 调用 fn 函数的时候, 内部的 this === obj
* 3. bind
*
* 语法: 函数名.bind('将函数内部的 this 更改为谁?', '传递给函数的实参1', '传递给函数的实参2', '传递给函数的实参3' .......)
*
* 区别: 刚才的两个方法调用后都会立即执行函数
* 当前方法不会立即执行, 而是会返回一个内部的this更改过的函数
*/
//var res = fn.bind(obj, 'QF001')
* 2. apply
*
* 语法: 函数名.apply('将函数内部的this更改为谁?', ['传递给函数的实参1', '传递给函数的实参2', '传递给函数的实参3', .......])
*/
// fn.apply(obj, [12345])
/**
* 1. call
*
* 在 JS 中, 每一个 函数 都自带一些方法, 其中一个就是 call
*
* 语法: 函数名.call('将函数内部的this更改为谁?', '传递给函数的实参1', '传递给函数的实参2', '传递给函数的实参3' .......)
// fn.call(obj, 10010)
* 箭头函数
*
* 只是对我们之前函数的一个语法优化
*/
// 箭头函数写法优化: 如果只有一行代码, 并且也需要返回值, 那么我们可以省略 大括号
// var fn = () => {
// return 100
// }
// var fn = () => 100
// console.log(fn())
// 基本写法
// var fn = (a) => {
// return a
// }
// 形参只有一个, 可以省略 小括号
// var fn = a => {
// return a
// }
// 要执行的代码只有一行, 所以可以省略 {}
var fn = a => a
console.log(fn) // a => a
console.log(fn()) // undefined
console.log(fn(10086)) // 10086
// 箭头函数写法优化: 如果只有一个形参, 那么小括号可以省略
var fn = (a) => { console.log(a) }
var fn = a => { console.log(a) }
fn(100)
//但是注意, 如果这一个形参有默认值, 那么必须添加 小括号
var fn = (a = 500) => { console.log(a) }
fn(100)
* 箭头函数与普通函数 this 的区别
*
* 普通函数 this 指向与调用者
* 箭头函数 this 取决于上下文
var obj = {
fn1: function () { console.log(this) },
fn2: () => {
/**
* 箭头函数内部是没有 this 的, 你在这个位置使用的 this
* 就相当于找到了上一层级最近的一个 this
*/
console.log(this)
}
}
obj.fn1() // this === obj
obj.fn2() // this === window
/**
* 解构赋值
*/
const obj = {
name: 'zs',
age: 18,
abc: 'QF001'
}
const { name, abc } = obj
console.log(name)
console.log(abc)
const arr = [1, 2, 3, 4, 5]
// 0 1 2 3 4
const [a, b, c, q, w, e] = arr
console.log(a)
console.log(b)
console.log(c)
console.log(q)
console.log(w)
console.log(e) // 没有值
/**
* 展开运算符
*
* ...
*
* 一般用于 合并数组, 或者 函数传参
*/
// 函数 --- 形参
function fn(arg) {
console.log(arg)
}
fn() // undefined
fn(100) // 100
fn(100, 200, 300) // 100
function fn(...arg) {
console.log(arg)
}
fn() // []
fn(100) // [100]
fn(100, 200, 300, 400, 500) // [100, 200, 300, 400, 500]
// 对象
const obj = {
a: 1,
b: 2,
c: 3
}
const obj1 = {
...obj,
a: 100,
b: 200,
q: 300,
w: 400,
}
console.log(obj)
console.log(obj1)
// 数组
const arr = [1, 2, 3]
console.log(arr)
console.log(...arr)
const arr1 = [100, 200, 300, ...arr]
console.log(arr1)
* Map
*
* 是 ES6 新增的一个数据结构, 类似于 对象, 但不是一个东西
*/
// 创建了一个 空的 map 集合
const oMap = new Map()
// console.log(oMap)
// 1. 向 map 中 添加数据 set()
// 下边的三行代码也能执行, 但是不建议, 如果这样写和对象没有什么区别, 建议使用对象
oMap.set('key', 'value')
oMap.set('age', 18)
oMap.set('name', '张三')
// map 数据中, key 的值类型是没有限制的, 但是对象中, 只能是字符串
const arr = [1, 2, 3]
oMap.set(arr, [4, 5, 6])
console.log(oMap)
oMap.forEach(function (value, key, origin) {
console.log(value, key)
})
// 4. delete() 删除数据中的某一个
// oMap.delete('key')
// 5. clear() 清空数据集合
// oMap.clear()
// console.log(oMap)
// 3. has() 判断数据中是否拥有指定的 key
// console.log(oMap.has('name')) // true
// console.log(oMap.has('qwe')) // false
// 2. 获取 map 中的一些数据
// console.log(oMap.get('age'))
// console.log(oMap.get('name'))
// console.log(oMap.get(arr))
* Set
*
* ES6 新增的一个数据结构, 类似于 数组
*
* Set 和 Map 都有一个特点, 内部不允许有重复的数据
* 所以就有人利用 Set 完成数组去重
*/
// 利用 set 完成数组去重
// const arr = new Set([100, 100, 2, 2, 3, 3, 4, 4, 5, 5])
// console.log(arr) // Set(5) {100, 2, 3, 4, 5}
// console.log(...arr) // 100 2 3 4 5
// console.log([...arr]) // [100, 2, 3, 4, 5]
const arr = [...new Set([100, 100, 2, 2, 3, 3, 4, 4, 5, 5])]
console.log(arr) // [100, 2, 3, 4, 5]
// const oSet = new Set([100, 100, 2, 2, 3, 3, 4, 4, 5, 5])
// console.log(oSet[0]) // set 不允许我们通过 下标访问内部的数据
// oSet.forEach(function (item, index, origin) {
// /**
// * item 和 origin 和 数组中 forEach 的含义是相同的
// *
// * 但是 index 不同, 因为 set 中 不允许使用 下标获取到内部的数据
// *
// * 所以 index 的值不是下标, 而是数组的每一个值
// *
// * 所以 在当前方法中, item 和 index 的值 永远相同
// */
// console.log(item, index, origin)
// })
// 3. delete 删除某一个
// oSet.delete(1)
// oSet.delete(2)
// oSet.delete(3)
// oSet.delete(4)
// oSet.delete(5)
// 4. clear() 清空数据集合
// oSet.clear()
// 2. 判断数据集合中是否拥有这个数据
// console.log(oSet.has(5))
// console.log(oSet.has(500))
// 1. 向集合中 添加数据
// oSet.add(10086)
// oSet.add('QF001')
// oSet.get() // oSet.get is not a function set 数据集合没有提供 get 方法
// console.log(oSet)
/**
* 对象的简写语法
*/
const a = 100
// 对象中如果一个属性的属性值是一个函数, 那么我们可以有一个简化的写法
const obj = {
fn1: function () {
console.log(123)
},
fn2 () {
console.log(456)
}
}
obj.fn1()
obj.fn2()
// 现在对象中有一个属性和属性值是相同的拼写, 并且 属性值是一个变量, 那么此时可以有一个简化的写法
// 1. 提前获取到要发送给后端的一些数据, 比如说账号和密码
// const username = 'QF001'
// const password = '123456'
// 2. 将收集好的信息组装成一个对象, 发送给后端
// const userInfo = {
// username: username,
// password: password
// }
// const userInfo = {
// username,
// password
// }
// const obj = {
// name: '张三',
// // a: a
// a,
// b: 'QF001'
// }
// console.log(obj)
* 作用域
*
* 就是一个变量生效的范围
*
*
* * 全局作用域
* 在 script 标签中直接书写的变量或者函数, 就是在全局作用域书写的
* 那么在这个文件中的任意位置, 都可以使用
*
* * 局部/函数 作用域
* 在函数内部会有一个独立的作用域, 这个作用域我们称之为 局部/函数 作用域
* 在当前作用域声明的变量或者函数, 一旦超过了当前作用域的范围, 那么就不能使用了
*
* * 块级作用域
* 必须使用 let 或者 const 声明变量的时候, 才会出现块级作用域
* {} 之内的区域就是块级作用域
* 在当前作用域声明的变量或者函数, 一旦超过了当前作用域的范围, 那么就不能使用了
*
* * 文件作用域
* 必须使用模块化开发才能出现所谓的文件作用域
* 每一个文件都是一个独立的作用域, 不能访问文件之外的其他文件内的变量或者函数
* 除非利用模块化的导入导出
*
*
*
* 作用域链
* 是一个纯虚构的概念性知识点
*
* 访问一个变量的时候会在当前作用域先查找访问, 如果有直接使用, 并停止查找
* 如果没有会继续去上一层作用域查找, 直到找到为止或者找到全局
* 这个层层链接的结构, 我们起了个名字叫做作用域链
*
* 1. 查找规则
* 会在当前作用域先查找访问, 如果有直接使用, 并停止查找
* 如果没有会继续去上一层作用域查找, 直到找到为止或者找到全局
* 如果全局也没有, 那么会报错
*
* 2. 赋值规则
* 会在当前作用域先查找访问, 如果有直接使用, 并停止查找
* 如果没有会继续去上一层作用域查找, 直到找到为止或者找到全局
* 如果全局也没有, 那么会在全局定义一个变量, 并赋值
*
*
* 将来还会有一个 原型链
*/
* 预解析
*
* JS 在运行前 会整体读取一遍我们的代码
*
* 这个一遍读取, 会将我们的变量声明和函数声明, 提升到页面最开始的为止(提升到当前作用域最开始的位置)
* 切记: 变量声明只会发生在 利用 var 声明的时候, 如果使用的是 let 或者 const 那么不会有所谓的变量提升
*
* 变量提升
* 只会提升变量的定义/声明, 但是变量的赋值不会提升, 也就是说我们能够在 变量定义前使用变量, 但是没有值, 实际的值是一个 undefined
*
* 函数提升
* 会将函数提升到当前作用域的最顶端, 所以我们可以在函数定义前去调用
*
* 在 JS 中 函数是一等公民
*/
// 演示 1
// console.log(a) // a is not defined
// 演示 2
// console.log(a) // undefined
// var a = 100
// 演示 3
// console.log(a) // Cannot access 'a' before initialization
// let a = 100