作用域

全局作用域

作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。

局部作用域

作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域。

JS 没有块级作用域

块作用域由 { } 包括。在其他编程语言中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循环语句中使用,如下面的 Java 代码。

java 有块级作用域:

1
2
3
4
5
if(true){
int num = 123;
system.out.print(num); // 123
}
system.out.print(num); // 报错

以上 java 代码会报错,是因为代码中 { } 即一块作用域,其中声明的变量 num,在 “{ }” 之外不能使用;

而与之类似的 JavaScript 代码,则不会报错:

Js 中没有块级作用域(在 ES6 之前)

1
2
3
4
5
if (true) {
var num = 123;
console.log(num); //123
}
console.log(num); //123

变量的作用域

在 JavaScript 中,根据作用域的不同,变量可以分为两种:

  • 全局变量
  • 局部变量

全局变量

在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。

  • 全局变量在代码的任何位置都可以使用
  • 在全局作用域下 var 声明的变量 是全局变量
  • 特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用)

局部变量

在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)

  • 局部变量只能在该函数内部使用
  • 在函数内部 var 声明的变量是局部变量
  • 函数的形参实际上就是局部变量

全局变量和局部变量的区别

  • 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
  • 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间

作用域链

只要是代码都一个作用域中,写在函数内部的局部作用域,未写在任何函数内部即在全局作用域中;如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链

案例分析:

1
2
3
4
5
6
7
8
9
var num = 456;
function f1() {
var num = 123;
function f2() {
console.log(num); //123
}
f2();
}
f1();

预解析

预解析的相关概念:

JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。

  1. 预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。
  2. 代码执行: 从上到下执行 JS 语句。

变量预解析

预解析也叫做变量、函数提升。量提升(变量预解析): 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。

1
2
3
4
5
6
7
console.log(num); // 结果是多少?
var num = 10; // ?
// 相当于执行了以下代码
var num;
console.log(num);
num = 10;
// 结果:undefined

变量提升只提升声明,不提升赋值

函数预解析

函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。

1
2
3
4
5
fn();
function fn() {
console.log("打印");
}
// 结果:控制台打印字符串 --- “打印”

注意:函数声明代表函数整体,所以函数提升后,函数名代表整个函数,但是函数并没有被调用!

函数表达式声明函数问题

函数表达式创建函数,会执行变量提升,此时接收函数的变量名无法正确的调用:

1
2
3
4
5
6
7
8
9
10
11
fn();
var fn = function () {
console.log("想不到吧");
};
// 相当于执行了以下代码
var fn;
fn();
fn = function () {
console.log("想不到吧");
};
// 结果:报错提示 "fn is not a function"

解释:该段代码执行之前,会做变量声明提升,fn 在提升之后的值是 undefined;而 fn 调用是在 fn 被赋值为函数体之前,此时 fn 的值是 undefined,所以无法正确调用

对象

例如,将“张三疯”的个人的信息保存在数组中的方式为:

1
var arr = ["张三疯", "男", 128, 154];

为了让更好地存储一组数据,对象应运而生:对象中为每项数据设置了属性名称,可以访问数据更语义化,数据结构清晰,表意明显,方便开发者使用。

使用对象记录上组数据为:

1
2
3
4
5
6
var obj = {
name: "张三疯",
sex: "男",
age: 128,
height: 154,
};

JS 中的对象表达结构更清晰,更强大。

创建对象的三种方式

利用字面量创建对象

就是花括号 { } 里面包含了表达这个具体事物(对象)的属性和方法;{ } 里面采取键值对的形式表示

  • 键:相当于属性名

  • 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)

代码如下:

1
2
3
4
5
6
7
8
var star = {
name: "pink",
age: 18,
sex: "男",
sayHi: function () {
alert("大家好啊~");
},
};

上述代码中 star 即是创建的对象。

对象的使用

  1. 对象里面的属性调用 : 对象.属性名 ,这个小点 . 就理解为“ 的 ”
  2. 对象里面属性的另一种调用方式 : 对象['属性名'],注意方括号里面的属性必须加引号
  3. 对象里面的方法调用:对象.方法名() ,注意这个方法名字后面一定加括号

示例代码如下:

1
2
3
console.log(star.name); // 调用名字属性
console.log(star["name"]); // 调用名字属性
star.sayHi(); // 调用 sayHi 方法,注意,一定不要忘记带后面的括号

利用 new Object 创建对象

  • 创建空对象
1
var andy = new Obect();

通过内置构造函数 Object 创建对象,此时 andy 变量已经保存了创建出来的空对象

  • 给空对象添加属性和方法
1
2
3
4
5
6
andy.name = "pink";
andy.age = 18;
andy.sex = "男";
andy.sayHi = function () {
alert("大家好啊~");
};

注意:

  • Object() :第一个字母大写
  • new Object() :需要 new 关键字
  • 使用的格式:对象.属性 = 值;

利用构造函数创建对象

构造函数:是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 运算符一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// new 构造函数名();
function Star(uname, age, sex) {
this.name = uname;
this.age = age;
this.sex = sex;
this.sing = function (sang) {
console.log(sang);
};
}
var ldh = new Star("刘德华", 18, "男"); // 调用函数返回的是一个对象
// console.log(typeof ldh);
console.log(ldh.name);
console.log(ldh["sex"]);
ldh.sing("冰雨");
var zxy = new Star("张学友", 19, "男");
console.log(zxy.name);
console.log(zxy.age);
zxy.sing("李香兰");

注意事项

  1. 构造函数约定首字母大写
  2. 函数内的属性和方法前面需要添加 this ,表示当前对象的属性和方法。
  3. 构造函数中不需要 return 返回结果
  4. 当我们创建对象的时候,必须用 new 来调用构造函数

其他

构造函数,如 Stars(),抽象了对象的公共部分,封装到了函数里面,它泛指某一大类(class)
创建对象,如 new Stars(),特指某一个,通过 new 关键字创建对象的过程我们也称为对象实例化

new 关键字的作用

  1. 在构造函数代码开始执行之前,创建一个空对象;
  2. 修改 this 的指向,把 this 指向创建出来的空对象;
  3. 执行函数的代码
  4. 在函数完成之后,返回 this---即创建出来的对象

遍历对象

for...in 语句用于对数组或者对象的属性进行循环操作。

1
2
3
4
5
6
7
8
9
10
11
var obj = {
name: "pink老师",
age: 18,
sex: "男",
fn: function () {},
};
for (var k in obj) {
console.log(k); // k 变量 输出 得到的是 属性名
console.log(obj[k]); // obj[k] 得到是 属性值
}
// 我们使用 for in 里面的变量 我们喜欢写 k 或者 key

内置对象

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
2
3
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

日期对象

Date 对象和 Math 对象不一样,Date 是一个构造函数,所以使用时需要实例化后才能使用其中具体方法和属性。Date 实例用来处理日期和时间

使用 Date 实例化日期对象

  • 获取当前时间必须实例化:
1
var now = new Date();
  • 获取指定时间的日期对象
1
2
3
4
var date1 = new Date(2019, 10, 1); // 返回的是 11月 不是 10月
console.log(date1); // Fri Nov 01 2019 00:00:00 GMT+0800 (中国标准时间)
var date2 = new Date("2019-10-1 8:8:8");
console.log(date2); // Tue Oct 01 2019 08:08:08 GMT+0800 (中国标准时间)

注意:如果创建实例时并未传入参数,则得到的日期对象是当前时间对应的日期对象

  • 使用 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
2
3
4
5
6
7
8
9
10
// 获得Date总的毫秒数(时间戳)  不是当前时间的毫秒数 而是距离1970年1月1号过了多少毫秒数
// 1. 通过 valueOf() getTime()
var date = new Date();
console.log(date.valueOf()); // 就是 我们现在时间 距离1970.1.1 总的毫秒数
console.log(date.getTime());
// 2. 简单的写法 (最常用的写法)
var date1 = +new Date(); // +new Date() 返回的就是总的毫秒数
console.log(date1);
// 3. H5 新增的 获得总的毫秒数
console.log(Date.now());

格式化日期年月日:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 格式化日期 年月日
var date = new Date();
console.log(date.getFullYear()); // 返回当前日期的年 2019
console.log(date.getMonth() + 1); // 月份 返回的月份小1个月 记得月份+1 呦
console.log(date.getDate()); // 返回的是 几号
console.log(date.getDay()); // 3 周一返回的是 1 周六返回的是 6 但是 周日返回的是 0
// 我们写一个 2019年 5月 1日 星期三
var year = date.getFullYear();
var month = date.getMonth() + 1;
var dates = date.getDate();
var arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
var day = date.getDay();
console.log('今天是:' + year + '年' + month + '月' + dates + '日 ' + arr[day]);

格式化日期时分秒:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 格式化日期 时分秒
var date = new Date();
console.log(date.getHours()); // 时
console.log(date.getMinutes()); // 分
console.log(date.getSeconds()); // 秒
// 要求封装一个函数返回当前的时分秒 格式 08:08:08
function getTimer() {
var time = new Date();
var h = time.getHours();
h = h < 10 ? '0' + h : h;
var m = time.getMinutes();
m = m < 10 ? '0' + m : m;
var s = time.getSeconds();
s = s < 10 ? '0' + s : s;
return h + ':' + m + ':' + s;
}
console.log(getTimer());

数组对象

创建数组

  • 利用数组字面量
1
var arr = [1, "test", true];
  • 利用 new Array()
1
2
3
var arr1 = new Array();  // 创建了一个空的数组
var arr2 = new Array(2); // 这个2 表示 数组的长度为 2 里面有2个空的数组元素
var arr3 = new Array(2, 3); // 等价于 [2,3] 这样写表示 里面有2个数组元素 2和3

参数传递规则如下:

  1. 如果只传入一个参数,则参数是数组的长度
  2. 如果传入了多个参数,则参数是数组的元素

检测是否为数组

  • instanceof 运算符

instanceof 可以判断一个对象是否是某个构造函数的实例

1
2
3
4
var arr = [1, 23];
var obj = {};
console.log(arr instanceof Array); // true
console.log(obj instanceof Array); // false
  • Array.isArray()

Array.isArray()用于判断一个对象是否为数组,isArray() 是 HTML5 中提供的方法

1
2
3
4
var arr = [1, 23];
var obj = {};
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(obj)); // false

添加删除数组元素的方法

  • 数组中有进行增加、删除元素的方法,部分方法如下表
方法名说明返回值
push(参数 1, ..)末尾添加一个或多个元素返回新的长度
pop()删除数组最后一个元素返回它删除的元素的值
unshift(参数 1, ..)向数组的开头添加一个或更多元素返回新的长度
shift()删除数组的第一个元素返回它删除的元素的值

注意:push、unshift 为增加元素方法;pop、shift 为删除元素的方法(pop、shift 中没有参数)

数组排序

  • 数组中有对数组本身排序的方法,部分方法如下表
方法名说明是否修改元数组
reverse()颠倒数组中元素的顺序, 无参数该方法会改变原来的数组, 返回新数组
sort()对数组的元素进行排序该方法会改变原来的数组, 返回新数组
1
2
3
4
5
6
var arr1 = [13, 4, 77, 1, 7];
arr1.sort(function(a, b) {
// return a - b; 升序的顺序排列
return b - a; // 降序的顺序排列
});
console.log(arr1);

注意:sort 方法需要传入参数来设置升序、降序排序

数组索引方法

  • 数组中有获取数组指定元素索引值的方法,部分方法如下表
方法名说明返回值
indexOf('要查找的字符', 开始的位置)数组中查找给定元素的第一个索引如果存在返回索引号,如果不存在返回-1
lastIndexOf()在数组中的最后一个索引如果存在返回索引号,如果不存在返回-1
1
2
3
4
5
6
7
var arr = ['red', 'green', 'blue', 'pink', 'blue'];
console.log(arr.indexOf('purple')); // -1,默认从arr[0]开始找
console.log(arr.indexOf('blue')); // 2
console.log(arr.indexOf('blue', 3)); // 4,表示从arr[3]开始找
// 从后面开始查找
var arr = ['red', 'green', 'blue', 'pink', 'blue'];
console.log(arr.lastIndexOf('blue')); // 4

数组转换为字符串

  • 数组中有把数组转化为字符串的方法,部分方法如下表
方法名说明返回值
toString()把数组转换成字符串,逗号分隔每一项返回一个字符串
join('分隔符')方法用于把数组中的所有元素转换为一个字符串返回一个字符串
1
2
3
4
5
6
7
8
// 1. toString() 将我们的数组转换为字符串
var arr = [1, 2, 3];
console.log(arr.toString()); // 1,2,3
// 2. join(分隔符)
var arr1 = ['green', 'blue', 'pink'];
console.log(arr1.join()); // green,blue,pink
console.log(arr1.join('-')); // green-blue-pink
console.log(arr1.join('&')); // green&blue&pink

其他方法

  • 数组中还有其他操作方法,同学们可以在课下自行查阅学习
方法名说明返回值
concat()连接两个或多个数组,不影响原数组返回一个新的数组
slice()数组截取 slice(begin, end)返回被截取项目的新数组
splice()数组删除 splice(第几个开始, 要删除的个数)返回被删除项目的新数组, 影响原数组

字符串对象

基本包装类型

1
2
3
4
5
6
7
8
9
10
11
12
// 基本包装类型
var str = 'andy';
console.log(str.length);
// 对象 才有 属性和方法 复杂数据类型才有 属性和方法
// 简单数据类型为什么会有length 属性呢?
// 基本包装类型:就是把简单数据类型 包装成为了 复杂数据类型
// (1) 把简单数据类型包装为复杂数据类型
var temp = new String('andy');
// (2) 把临时变量的值 给 str
str = temp;
// (3) 销毁这个临时变量
temp = null;

字符串的不可变

当重新给字符串变量赋值的时候,变量之前保存的字符串不会被修改,依然在内存中重新给字符串赋值,会重新在内存中开辟空间,这个特点就是字符串的不可变。

根据位置返回字符

方法名说明使用
charAt(index)获取指定位置处字符str.charAt(i)
charCodeAt(index)获取指定位置处字符的 ASCII 码str.charCodeAt(i)
str[index]获取指定位置处字符HTML5,IE8+支持,和 charAt()等效

案例:判断一个字符串中出现次数最多的字符,并统计其次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//  判断一个字符串 'abcoefoxyozzopp' 中出现次数最多的字符,并统计其次数。
// o.a = 1
// o.b = 1
// o.c = 1
// o.o = 4
// 核心算法:利用 charAt() 遍历这个字符串
// 把每个字符都存储给对象, 如果对象没有该属性,就为1,如果存在了就 +1
// 遍历对象,得到最大值和该字符
var str = 'abcoefoxyozzopp';
var o = {};
for (var i = 0; i < str.length; i++) {
var chars = str.charAt(i); // chars 是 字符串的每一个字符
if (o[chars]) { // o[chars] 得到的是属性值
o[chars]++;
} else {
o[chars] = 1;
}
}
console.log(o);
// 2. 遍历对象
var max = 0;
var ch = '';
for (var k in o) {
// k 得到是 属性名
// o[k] 得到的是属性值
if (o[k] > max) {
max = o[k];
ch = k;
}
}
console.log(max);
console.log('最多的字符是' + ch);

字符串操作方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. concat('字符串1','字符串2'....)
var str = 'andy';
console.log(str.concat('red'));

// 2. substr('截取的起始位置', '截取几个字符');
var str1 = '改革春风吹满地';
console.log(str1.substr(2, 2)); // 第一个2 是索引号的2 从第几个开始 第二个2 是取几个字符

// 3. 替换字符 replace('被替换的字符', '替换为的字符') 它只会替换第一个字符
var str = 'andyandy';
console.log(str.replace('a', 'b'));
// 有一个字符串 'abcoefoxyozzopp' 要求把里面所有的 o 替换为 *
var str1 = 'abcoefoxyozzopp';
while (str1.indexOf('o') !== -1) {
str1 = str1.replace('o', '*');
}
console.log(str1);

// 4. 字符转换为数组 split('分隔符') 前面我们学过 join 把数组转换为字符串
var str2 = 'red, pink, blue';
console.log(str2.split(','));
var str3 = 'red&pink&blue';
console.log(str3.split('&'));

数据类型

简单数据类型

在存储时变量中存储的是值本身,包括 string ,number,boolean,undefined,null

函数的形参也可以看做是一个变量,当我们把一个值类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到的外部变量。

1
2
3
4
5
6
7
8
// 简单数据类型传参
function fn(a) {
a++;
console.log(a);
}
var x = 10;
fn(x); // 11
console.log(x); // 10

复杂数据类型

在存储时变量中存储的仅仅是地址(引用),通过 new 关键字创建的对象,如 Object、Array、Date 等;

函数的形参也可以看做是一个变量,当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 复杂数据类型传参
function Person(name) {
this.name = name;
}
function f1(x) { // x = p
console.log(x.name); // 2. 这个输出什么 ? 刘德华
x.name = "张学友";
console.log(x.name); // 3. 这个输出什么 ? 张学友
}
var p = new Person("刘德华");
console.log(p.name); // 1. 这个输出什么 ? 刘德华
f1(p);
console.log(p.name); // 4. 这个输出什么 ? 张学友