作用域
全局作用域
作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。
局部作用域
作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域。
JS 没有块级作用域
块作用域由 { } 包括。在其他编程语言中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循环语句中使用,如下面的 Java 代码。
java 有块级作用域:
1 | if(true){ |
以上 java 代码会报错,是因为代码中 { } 即一块作用域,其中声明的变量 num,在 “{ }” 之外不能使用;
而与之类似的 JavaScript 代码,则不会报错:
Js 中没有块级作用域(在 ES6 之前)
1 | if (true) { |
变量的作用域
在 JavaScript 中,根据作用域的不同,变量可以分为两种:
- 全局变量
- 局部变量
全局变量
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。
- 全局变量在代码的任何位置都可以使用
- 在全局作用域下 var 声明的变量 是全局变量
- 特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用)
局部变量
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
- 局部变量只能在该函数内部使用
- 在函数内部 var 声明的变量是局部变量
- 函数的形参实际上就是局部变量
全局变量和局部变量的区别
- 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
- 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间
作用域链
只要是代码都一个作用域中,写在函数内部的局部作用域,未写在任何函数内部即在全局作用域中;如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链
案例分析:
1 | var num = 456; |
预解析
预解析的相关概念:
JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。
- 预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。
- 代码执行: 从上到下执行 JS 语句。
变量预解析
预解析也叫做变量、函数提升。变量预解析: 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。
1 | console.log(num); // 结果是多少? |
变量提升只提升声明,不提升赋值
函数预解析
函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
1 | fn(); |
注意:函数声明代表函数整体,所以函数提升后,函数名代表整个函数,但是函数并没有被调用!
函数表达式声明函数问题
函数表达式创建函数,会执行变量提升,此时接收函数的变量名无法正确的调用:
1 | fn(); |
解释:该段代码执行之前,会做变量声明提升,fn 在提升之后的值是 undefined;而 fn 调用是在 fn 被赋值为函数体之前,此时 fn 的值是 undefined,所以无法正确调用
对象
例如,将“张三疯”的个人的信息保存在数组中的方式为:
1 | var arr = ["张三疯", "男", 128, 154]; |
为了让更好地存储一组数据,对象应运而生:对象中为每项数据设置了属性名称,可以访问数据更语义化,数据结构清晰,表意明显,方便开发者使用。
使用对象记录上组数据为:
1 | var obj = { |
JS 中的对象表达结构更清晰,更强大。
创建对象的三种方式
利用字面量创建对象
就是花括号 { } 里面包含了表达这个具体事物(对象)的属性和方法;{ } 里面采取键值对的形式表示
键:相当于属性名
值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)
代码如下:
1 | var star = { |
上述代码中 star 即是创建的对象。
对象的使用
- 对象里面的属性调用 : 对象.属性名 ,这个小点 . 就理解为“ 的 ”
- 对象里面属性的另一种调用方式 : 对象['属性名'],注意方括号里面的属性必须加引号
- 对象里面的方法调用:对象.方法名() ,注意这个方法名字后面一定加括号
示例代码如下:
1 | console.log(star.name); // 调用名字属性 |
利用 new Object 创建对象
- 创建空对象
1 | var andy = new Obect(); |
通过内置构造函数 Object 创建对象,此时 andy 变量已经保存了创建出来的空对象
- 给空对象添加属性和方法
1 | andy.name = "pink"; |
注意:
- Object() :第一个字母大写
- new Object() :需要 new 关键字
- 使用的格式:对象.属性 = 值;
利用构造函数创建对象
构造函数:是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 运算符一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
1 | // new 构造函数名(); |
注意事项
- 构造函数约定首字母大写。
- 函数内的属性和方法前面需要添加 this ,表示当前对象的属性和方法。
- 构造函数中不需要 return 返回结果。
- 当我们创建对象的时候,必须用 new 来调用构造函数。
其他
构造函数,如 Stars(),抽象了对象的公共部分,封装到了函数里面,它泛指某一大类(class)
创建对象,如 new Stars(),特指某一个,通过 new 关键字创建对象的过程我们也称为对象实例化
new 关键字的作用
- 在构造函数代码开始执行之前,创建一个空对象;
- 修改 this 的指向,把 this 指向创建出来的空对象;
- 执行函数的代码
- 在函数完成之后,返回 this---即创建出来的对象
遍历对象
for...in 语句用于对数组或者对象的属性进行循环操作。
1 | var obj = { |
内置对象
JavaScript 中的对象分为 3 种**:自定义对象 、内置对象、 浏览器对象**
前面两种对象是 JS 基础 内容,属于 ECMAScript; 第三个浏览器对象属于 JS 独有的, JS API 讲解内置对象就是指 JS 语言自带的一些对象,这些对象供开发者使用,并提供了一些常用的或是最基本而必要的功能(属性和方法),内置对象最大的优点就是帮助我们快速开发
查文档
查找文档:学习一个内置对象的使用,只要学会其常用对象的使用即可,我们可以通过查文档学习。
Math 对象
Math 对象不是构造函数,它具有数学常数和函数的属性和方法。跟数学相关的运算(求绝对值,取整、最大值等)可以使用 Math 中的成员。
属性、方法名 | 功能 |
---|---|
Math.PI | 圆周率 |
Math.floor() | 向下取整 |
Math.ceil() | 向上取整 |
Math.round() | 四舍五入版 就近取整 注意 -3.5 结果是 -3 |
Math.abs() | 绝对值 |
Math.max()/Math.min() | 求最大和最小值 |
Math.random() | 获取范围在[0,1)内的随机值 |
注意:上面的方法使用时必须带括号
获取指定范围内的随机整数:
1 | function getRandom(min, max) { |
日期对象
Date 对象和 Math 对象不一样,Date 是一个构造函数,所以使用时需要实例化后才能使用其中具体方法和属性。Date 实例用来处理日期和时间
使用 Date 实例化日期对象
- 获取当前时间必须实例化:
1 | var now = new Date(); |
- 获取指定时间的日期对象
1 | var date1 = new Date(2019, 10, 1); // 返回的是 11月 不是 10月 |
注意:如果创建实例时并未传入参数,则得到的日期对象是当前时间对应的日期对象
- 使用 Date 实例的方法和属性
方法名 | 说明 | 代码 |
---|---|---|
getFullYear() | 获取当年 | dObj.getFullYear() |
getMonth() | 获取当月(0-11) | dObj.getMonth() |
getDate() | 获取当天日期 | dObj.getDate() |
getDay() | 获取星期几(周日 0 到周六 6) | dObj.getDay() |
getHours() | 获取当前小时 | dObj.getHours() |
getMinutes() | 获取当前分钟 | dObj.getMinutes() |
getSeconds() | 获取当前秒钟 | dObj.getSeconds() |
通过 Date 实例获取总毫秒数
总毫秒数的含义
基于 1970 年 1 月 1 日(世界标准时间)起的毫秒数
获取总毫秒数
1 | // 获得Date总的毫秒数(时间戳) 不是当前时间的毫秒数 而是距离1970年1月1号过了多少毫秒数 |
格式化日期年月日:
1 | // 格式化日期 年月日 |
格式化日期时分秒:
1 | // 格式化日期 时分秒 |
数组对象
创建数组
- 利用数组字面量
1 | var arr = [1, "test", true]; |
- 利用 new Array()
1 | var arr1 = new Array(); // 创建了一个空的数组 |
参数传递规则如下:
- 如果只传入一个参数,则参数是数组的长度
- 如果传入了多个参数,则参数是数组的元素
检测是否为数组
- instanceof 运算符
instanceof 可以判断一个对象是否是某个构造函数的实例
1 | var arr = [1, 23]; |
- Array.isArray()
Array.isArray()用于判断一个对象是否为数组,isArray() 是 HTML5 中提供的方法
1 | var arr = [1, 23]; |
添加删除数组元素的方法
- 数组中有进行增加、删除元素的方法,部分方法如下表
方法名 | 说明 | 返回值 |
---|---|---|
push(参数 1, ..) | 末尾添加一个或多个元素 | 返回新的长度 |
pop() | 删除数组最后一个元素 | 返回它删除的元素的值 |
unshift(参数 1, ..) | 向数组的开头添加一个或更多元素 | 返回新的长度 |
shift() | 删除数组的第一个元素 | 返回它删除的元素的值 |
注意:push、unshift 为增加元素方法;pop、shift 为删除元素的方法(pop、shift 中没有参数)
数组排序
- 数组中有对数组本身排序的方法,部分方法如下表
方法名 | 说明 | 是否修改元数组 |
---|---|---|
reverse() | 颠倒数组中元素的顺序, 无参数 | 该方法会改变原来的数组, 返回新数组 |
sort() | 对数组的元素进行排序 | 该方法会改变原来的数组, 返回新数组 |
1 | var arr1 = [13, 4, 77, 1, 7]; |
注意:sort 方法需要传入参数来设置升序、降序排序
数组索引方法
- 数组中有获取数组指定元素索引值的方法,部分方法如下表
方法名 | 说明 | 返回值 |
---|---|---|
indexOf('要查找的字符', 开始的位置) | 数组中查找给定元素的第一个索引 | 如果存在返回索引号,如果不存在返回-1 |
lastIndexOf() | 在数组中的最后一个索引 | 如果存在返回索引号,如果不存在返回-1 |
1 | var arr = ["red", "green", "blue", "pink", "blue"]; |
数组转换为字符串
- 数组中有把数组转化为字符串的方法,部分方法如下表
方法名 | 说明 | 返回值 |
---|---|---|
toString() | 把数组转换成字符串,逗号分隔每一项 | 返回一个字符串 |
join('分隔符') | 方法用于把数组中的所有元素转换为一个字符串 | 返回一个字符串 |
1 | // 1. toString() 将我们的数组转换为字符串 |
其他方法
- 数组中还有其他操作方法,同学们可以在课下自行查阅学习
方法名 | 说明 | 返回值 |
---|---|---|
concat() | 连接两个或多个数组,不影响原数组 | 返回一个新的数组 |
slice() | 数组截取 slice(begin, end) | 返回被截取项目的新数组 |
splice() | 数组删除 splice(第几个开始, 要删除的个数) | 返回被删除项目的新数组, 影响原数组 |
字符串对象
基本包装类型
1 | // 基本包装类型 |
字符串的不可变
当重新给字符串变量赋值的时候,变量之前保存的字符串不会被修改,依然在内存中重新给字符串赋值,会重新在内存中开辟空间,这个特点就是字符串的不可变。
根据位置返回字符
方法名 | 说明 | 使用 |
---|---|---|
charAt(index) | 获取指定位置处字符 | str.charAt(i) |
charCodeAt(index) | 获取指定位置处字符的 ASCII 码 | str.charCodeAt(i) |
str[index] | 获取指定位置处字符 | HTML5,IE8+支持,和 charAt()等效 |
案例:判断一个字符串中出现次数最多的字符,并统计其次数
1 | // 判断一个字符串 'abcoefoxyozzopp' 中出现次数最多的字符,并统计其次数。 |
字符串操作方法
1 | // 1. concat('字符串1','字符串2'....) |
数据类型
简单数据类型
在存储时变量中存储的是值本身,包括 string ,number,boolean,undefined,null
函数的形参也可以看做是一个变量,当我们把一个值类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到的外部变量。
1 | // 简单数据类型传参 |
复杂数据类型
在存储时变量中存储的仅仅是地址(引用),通过 new 关键字创建的对象,如 Object、Array、Date 等;
函数的形参也可以看做是一个变量,当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象。
1 | // 复杂数据类型传参 |