# JavaScript 进阶 - 第1天
> 学习作用域、变量提升、闭包等语言特征,加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法,降低代码的冗余度。
- 理解作用域对程序执行的影
- 能够分析程序执行的作用域范围
- 理解闭包本质,利用闭包创建隔离作用域
- 了解什么变量提升及函数提升
- 掌握箭头函数、解析剩余参数等简洁语法
## 作用域
> 了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
### 局部作用域
局部作用域分为函数作用域和块作用域。
#### 函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
```html
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
const s = x + y
console.log(s)
// 18 }
// 设用 counter 函数
counter(10, 8)
// 访问变量 s
console.log(s)// 报错
```
总结:
1. 函数内部声明的变量,在函数外部无法被访问
2. 函数的参数也是函数内部的局部变量
3. 不同函数内部声明的变量无法互相访问
4. 函数执行完毕后,函数内部的变量实际被清空了
#### 块作用域
在 JavaScript 中使用 `{}` 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
```html
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常 }
// 超出了 age 的作用域
console.log(age) // 报错
let flag = true; if(flag) {
// str 只能在该代码块中被访问
let str = 'hello world!'
console.log(str); // 正常 }
// 超出了 age 的作用域
console.log(str); // 报错
for(let t = 1; t <= 6; t++) {
// t 只能在该代码块中被访问
console.log(t);// 正常
// 超出了 t 的作用域
console.log(t); // 报错
```
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
```html
// 必须要有值
const version = '1.0.0';
// 不能重新赋值 // version = '1.0.1';
// 常量值为对象类型
const user = { name: '小明', age: 18 }
// 不能重新赋值 user = {};
// 属性和方法允许被修改
user.name = '小小明'; user.gender = '男';
```
总结:
1. `let` 声明的变量会产生块作用域,`var` 不会产生块作用域
2. `const` 声明的常量也会产生块作用域
3. 不同代码块之间的变量无法互相访问
4. 推荐使用 `let` 或 `const`
注:开发中 `let` 和 `const` 经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 `const` 声明成常量。
### 全局作用域````
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
```html
// 全局变量
name const name = '小明'
// 函数作用域中访问全局 function sayHi() {
// 此处为局部
console.log('你好' + name) }
// 全局变量 flag 和 x
const flag = true let x = 10
// 块作用域中访问全局 if(flag) { let y = 5 console.log(x + y)
// x 是全局的
}```
总结:
1. 为 `window` 对象动态添加的属性默认也是全局的,不推荐!
2. 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
3. 尽可能少的声明全局变量,防止全局变量被污染JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
### 作用域链
在解释什么是作用域链前先来看一段代码:
```html
// 全局作用域
let a = 1 let b = 2
// 局部作用域
function f() {
let c
// 局部作用域
function g() { let d = 'yo' } }
```
函数内部允许创建新的函数,`f` 函数内部创建的新函数 `g`,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。
如下图所示,父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
```html
// 全局作用域
let a = 1 let b = 2
// 局部作用域
function f() {
let c
// let a = 10;
console.log(a) // 1 或 10
console.log(d) // 报错
// 局部作用域
function g() {
let d = 'yo'
// let b = 20;
console.log(b) // 2 或 20 }
// 调用 g 函数 g() }
console.log(c) // 报错
console.log(d) // 报错
f();
```
总结:
1. 嵌套关系的作用域串联起来形成了作用域链
2. 相同作用域链中按着从小到大的规则查找变量
3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域### 闭包闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
1. 闭包 : 内层函数 + 外层函数变量
function outer() {
const a = 1
function f() {
console.log(a) }
f() }
2. 闭包的应用: 实现数据的私有。统计函数的调用次数
let count = 1
function fn() {
count++
console.log(`函数被调用${count}次`) }
3. 闭包的写法
统计函数的调用次数 function outer() {
let count = 1
function fn() {
count++
console.log(`函数被调用${count}次`) }
return fn }
const re = outer()
const re = function fn() {
count++
console.log(`函数被调用${count}次`) }
re() re()
const fn = function() { } 函数表达式
4. 闭包存在的问题: 可能会造成内存泄漏
总结:
1.怎么理解闭包?
- 闭包 = 内层函数 + 外层函数的变量
2.闭包的作用?
- 封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
3.闭包可能引起的问题?
- 内存泄漏
### 变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问,
```html
// 访问变量 str
console.log(str + 'world!');
// 声明变量
str var str = 'hello ';
```
总结:
1. 变量在未声明即被访问时会报语法错误
2. 变量在声明之前即被访问,变量的值为 `undefined`
3. `let` 声明的变量不存在变量提升,推荐使用 `let`
4. 变量提升出现在相同作用域当中
5. 实际开发中推荐先声明再访问变量
## 函数
> 知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异。
### 函数提升函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
```html
// 调用函数
foo()
// 声明函数
function foo() { console.log('声明之前即被调用...') }
// 不存在提升现象
bar() // 错误
var bar = function () { console.log('函数表达式不存在提升现象...') }
```
总结:
1. 函数提升能够使函数的声明调用更灵活
2. 函数表达式不存在提升的现象
3. 函数提升出现在相同作用域当中
### 函数参数
函数参数的使用细节,能够提升函数应用的灵活度。
#### 默认值
```html
// 设置参数默认值
function sayHi(name="小明", age=18) { document.write(`
大家好,我叫${name},我今年${age}岁了。
`); }// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
```
总结:
1. 声明函数时为形参赋值即为参数的默认值
2. 如果参数未自定义默认值时,参数的默认值为 `undefined`
3. 调用函数时没有传入对应实参时,参数的默认值被当做实参传入
#### 动态参数`arguments` 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
```html
// 求生函数,计算所有参数的和
function sum() {
// console.log(arguments)
let s = 0
for(let i = 0; i < arguments.length; i++) { s += arguments[i] } console.log(s) }
// 调用求和函数
sum(5, 10)
// 两个参数 sum(1, 2, 4)
// 两个参数
```
总结:
1. `arguments` 是一个伪数组
2. `arguments` 的作用是动态获取函数的实参
#### 剩余参数
```html
function config(baseURL, ...other) { console.log(baseURL) // 得到 'http://baidu.com'
console.log(other) // other 得到 ['get', 'json'] }
// 调用函数
config('http://baidu.com', 'get', 'json');
```
总结:
1. `...` 是语法符号,置于最末函数形参之前,用于获取多余的实参
2. 借助 `...` 获取的剩余实参,是个真数组### 箭头函数箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
```html
```总结:
1. 箭头函数属于表达式函数,因此不存在函数提升
2. 箭头函数只有一个参数时可以省略圆括号 `()`
3. 箭头函数函数体只有一行代码时可以省略花括号 `{}`,并自动做为返回值被返回
#### 箭头函数参数
箭头函数中没有 `arguments`,只能使用 `...` 动态获取实参
~~~html
~~~#### 箭头函数
this箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
~~~html
// 以前this的指向: 谁调用的这个函数,this 就指向谁
// console.log(this) // window
// 普通函数
// function fn() {
// console.log(this)
// window
// }
// window.fn()
// 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this)
// obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的this 是上一层作用域的this 指向
// const fn = () => {
// console.log(this)
// window
// }
// fn()
// 对象方法箭头函数 this
// const obj = {
// uname: 'pink老师',
// sayHi: () => {
// console.log(this)
// this 指向谁? window
// } // }
// obj.sayHi() const obj = {
uname: 'pink老师',
sayHi: function () {
console.log(this) // obj
let i = 10
const count = () => {
console.log(this)
// obj }
count() } }
obj.sayHi()
~~~
## 解构赋值
> 知道解构的语法及分类,使用解构简洁语法快速为变量赋值。解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
### 数组解构数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法,如下代码所示:
```html
// 普通的数组 let arr = [1, 2, 3]
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr console.log(a);
// 1 console.log(b)
// 2 console.log(c);
// 3```
总结:
1. 赋值运算符 `=` 左侧的 `[]` 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
2. 变量的顺序对应数组单元值的位置依次进行赋值操作
3. 变量的数量大于单元值数量时,多余的变量将被赋值为 `undefined`
4. 变量的数量小于单元值数量时,可以通过 `...` 获取剩余单元值,但只能置于最末位
5. 允许初始化变量的默认值,且只有单元值为 `undefined` 时默认值才会生效
注:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
### 对象解构对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法,如下代码所示:
```html
// 普通对象
const user = { name: '小明', age: 18 };
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量name age
const {name, age} = user
console.log(name)
// 小明 console.log(age) // 18
```
总结:
1. 赋值运算符 `=` 左侧的 `{}` 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
2. 对象属性的值将被赋值给与属性名相同的变量
3. 对象中找不到与变量名一致的属性时变量值为 `undefined`
4. 允许初始化变量的默认值,属性不存在或单元值为 `undefined` 时默认值才会生效
注:支持多维解构赋值
~~~html
~~~### forEach遍历数组
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
>注意:
>>1.forEach 主要是遍历数组>>
2.参数当前数组元素是必须要写的, 索引号可选。
~~~html
~~~### filter筛选数组
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
~~~html
JavaScript 进阶 - 第2天
> 了解面向对象编程的基础概念及构造函数的作用,体会 JavaScript 一切皆对象的语言特征,掌握常见的对象属性和方法的使用。
- 了解面向对象编程中的一般概念
- 能够基于构造函数创建对象
- 理解 JavaScript 中一切皆对象的语言特征
- 理解引用对象类型值存储的的特征
- 掌握包装类型对象常见方法的使用
## 深入对象
> 了解面向对象的基础概念,能够利用构造函数创建对象。
### 构造函数构造函数是专门用于创建对象的函数,如果一个函数使用 `new` 关键字调用,那么这个函数就是构造函数。
```html
// 定义函数
unction foo() {
console.log('通过 new 也能调用函数...'); }
// 调用函数 new foo;
```
总结:
2. 使用 `new` 关键字调用函数的行为被称为实例化
3. 实例化构造函数时没有参数时可以省略 `()`
4. 构造函数的返回值即为新创建的对象
5. 构造函数内部的 `return` 返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
### 实例成员通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
```html
// 构造函数
function Person() {
// 构造函数内部的 this 就是实例对象
// 实例对象中动态添加属性 this.name = '小明'
// 实例对象动态添加方法
this.sayHi = function () {
console.log('大家好~') } }
// 实例化,p1 是实例对象
// p1 实际就是 构造函数内部的 this
const p1 = new Person()
console.log(p1)
console.log(p1.name)
// 访问实例属性 p1.sayHi()
// 调用实例方法
```
总结:
1. 构造函数内部 `this` 实际上就是实例对象,为其动态添加的属性和方法即为实例成员
2. 为构造函数传入参数,动态创建结构相同但值不同的对象注:构造函数创建的实例对象彼此独立互不影响。
### 静态成员在 JavaScript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员。
```html
// 构造函数
function Person(name, age) {
// 省略实例成员 }
// 静态属性
Person.eyes = 2
Person.arms = 2
// 静态方法
Person.walk = function () {
console.log('^_^人都会走路...')
// this 指向 Person
console.log(this.eyes) }
```
总结:
1. 静态成员指的是添加到构造函数本身的属性和方法
2. 一般公共特征的属性或方法静态成员设置为静态成员
3. 静态成员方法中的 `this` 指向构造函数本身
## 内置构造函数
> 掌握各引用类型和包装类型对象属性和方法的使用。在 JavaScript 中**最主要**的数据类型有 6 种,分别是字符串、数值、布尔、undefined、null 和 对象,常见的对象类型数据包括数组和普通对象。其中字符串、数值、布尔、undefined、null 也被称为简单类型或基础类型,对象也被称为引用类型。在 JavaScript 内置了一些构造函数,绝大部的数据处理都是基于这些构造函数实现的,JavaScript 基础阶段学习的 `Date` 就是内置的构造函数。
```html
// 实例化
let date = new Date();
// date 即为实例对象
console.log(date);
```
甚至字符串、数值、布尔、数组、普通对象也都有专门的构造函数,用于创建对应类型的数据。
### Object
`Object` 是内置的构造函数,用于创建普通对象。
```html
// 通过构造函数创建普通对象
const user = new Object({name: '小明', age: 15})
// 这种方式声明的变量称为【字面量】
let student = {name: '杜子腾', age: 21}
// 对象语法简写
let name = '小红';
let people = {
// 相当于 name: name
name,
// 相当于 walk: function () {}
walk () { console.log('人都要走路...'); } }
console.log(student.constructor);
console.log(user.constructor);
console.log(student instanceof Object);
```。
总结:
1. 推荐使用字面量方式声明对象,而不是 `Object` 构造函数
2. `Object.assign` 静态方法创建新的对象
3. `Object.keys` 静态方法获取对象中所有属性
4. `Object.values` 表态方法获取对象中所有属性值
### Array
`Array` 是内置的构造函数,用于创建数组。
```html
// 构造函数创建数组
let arr = new Array(5, 7, 8);
// 字面量方式创建数组
let list = ['html', 'css', 'javascript']
```
数组赋值后,无论修改哪个变量另一个对象的数据值也会相当发生改变。
总结:
1. 推荐使用字面量方式声明数组,而不是 `Array` 构造函数
2. 实例方法 `forEach` 用于遍历数组,替代 `for` 循环 (重点)
3. 实例方法 `filter` 过滤数组单元值,生成新数组(重点)
4. 实例方法 `map` 迭代原数组,生成新数组(重点)
5. 实例方法 `join` 数组元素拼接为字符串,返回字符串(重点)
6. 实例方法 `find` 查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined(重点)
7. 实例方法`every` 检测数组所有元素是否都符合指定条件,如果**所有元素**都通过检测返回 true,否则返回 false(重点)
8. 实例方法`some` 检测数组中的元素是否满足指定条件 **如果数组中有**元素满足条件返回 true,否则返回 false
9. 实例方法 `concat` 合并两个数组,返回生成新数组
10. 实例方法 `sort` 对原数组单元值排序
11. 实例方法 `splice` 删除或替换原数组单元
12. 实例方法 `reverse` 反转数组
13. 实例方法 `findIndex` 查找元素的索引值
### 包装类型
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法,如下代码举例:
```html
// 字符串类型
const str = 'hello world!'
// 统计字符的长度(字符数量)
console.log(str.length)
// 数值类型
const price = 12.345
// 保留两位小数
price.toFixed(2)
// 12.34
```
之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。
#### String
`String` 是内置的构造函数,用于创建字符串。
```html
// 使用构造函数创建字符串
let str = new String('hello world!');
// 字面量创建字符串 let str2 = '你好,世界!';
// 检测是否属于同一个构造函数
console.log(str.constructor === str2.constructor);
// true console.log(str instanceof String);
// false
```
总结:
1. 实例属性 `length` 用来获取字符串的度长(重点)
2. 实例方法 `split('分隔符')` 用来将字符串拆分成数组(重点)
3. 实例方法 `substring(需要截取的第一个字符的索引[,结束的索引号])` 用于字符串截取(重点)
4. 实例方法 `startsWith(检测字符串[, 检测位置索引号])` 检测是否以某字符开头(重点)
5. 实例方法 `includes(搜索的字符串[, 检测位置索引号])` 判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点)
6. 实例方法 `toUpperCase` 用于将字母转换成大写
7. 实例方法 `toLowerCase` 用于将就转换成小写
8. 实例方法 `indexOf` 检测是否包含某字符
9. 实例方法 `endsWith` 检测是否以某字符结尾
10. 实例方法 `replace` 用于替换字符串,支持正则匹配
13. 实例方法 `match` 用于查找字符串,支持正则匹配注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
#### Number
`Number` 是内置的构造函数,用于创建数值。
```html
// 使用构造函数创建数值
let x = new Number('10') let y = new Number(5)
// 字面量创建数值 let z = 20
```
总结:
1. 推荐使用字面量方式声明数值,而不是 `Number` 构造函数
2. 实例方法 `toFixed` 用于设置保留小数位的长度