# JS 进阶

# 一、函数

# 1、函数

函数:就是封装了一段可以被重复执行调用的代码块。
目的就是让大量代码重复使用。

function getSum(num1,num2) {
    var sum = 0;
    for (var i = num1; i <= num2; i++) {
        sum += i;
    }
    console.log(sum);
}
getSum(1,100);
getSum(10,50);

# 2、声明函数和调用函数

函数使用分为两步:声明函数 和 调用函数

// 1. 声明函数
function sayHi() {
    console.log('hi~~');
}
// 函数名一般用动词。    函数不调用 自己不执行

// 2. 调用函数
// 函数名();
sayHi();

// 3. 函数的封装:把一个或多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口。 简单理解为:封装类似于将电脑配件整合组装到机箱中(类似快递打包)

# 3、函数的参数

  1. 形参 和 实参
function 函数名(形参1, 形参2...) {    
    // 在声明函数的小括号里是 形参(形式上的参数)    形参是接收实参的
}   
函数名(实参1, 实参2...);      
// 在调用函数的小括号里是 实参 (实际的参数)
  1. 函数形参 和 实参的匹配问题:
function getSum(num1, num2) {
    console.log(num1 + num2);
}

// 1. 实参和形参个数一致 正常输出结果
getSum(1, 2);

// 1. 实参 > 形参个数 取形参的个数
getSum(1, 2, 3);

// 1. 实参 < 形参个数  多余的形参定义 undefined, 最终结果就是 NaN
getSum(1);

# 4、函数的返回值 return

function getSum(num1, num2) {
    return num1 + num2;    // return后面是需要返回的结果
}
console.log(getSum(1, 2));

# 5、arguments 的使用

arguments:只有函数才有的 arguments 对象, 而且是每个函数都内置好了这个 arguments

function fn() {
    console.log(arguments);
    console.log(arguments.length);
    console.log(arguments[1]);
    // 可以按照数组的方式遍历arguments
    for (var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}
fn(1, 2, 3);
// arguments 是伪数组 并不是真正意义上的组
// 1. 具有数组的 length 属性
// 2. 按照索引的方式进行存储的
// 3. 发没有真正数组的一些方法 pop() pus() 等等

# 6、函数的调用

function fn1() {
    console.log(111);            // 111
    fn2();
    console.log('fn1');          // 222
}
function fn2() {
    console.log(222);            // fn2
    console.log('fn2');          // fn1
}
fn1();

# 7、函数的两种声明方式

  1. 利用函数关键字自定义函数(命名函数)
function fn() {
}
fn();
  1. 函数表达式(匿名函数)
// var 变量名 = function(){};
var fun = function() {
    console.log('我是函数表达式');
}
fun();

# 二、JS 的作用域、预解析

# 1、JS 的作用域

  1. js的作用域:就是代码名字(变量)在某个范围内起作用和效果 目的就是为了提高程序的可靠性 更重要的是减少命名冲突。
  2. js的作用域(ES6)之前: 全局作用域、局部作用域。
  3. 全局作用域: 整个script标签 或者是个单独的js文件。
var num = 30;
console.log(num);
  1. 局部作用域(函数作用域):在函数内就是局部作用域 这个代码的名字只在函数内部起效果和作用。
function fn() {
    // 局部作用域
    var num = 20;
    console.log(num);
}
fn();

# 2、全局变量和局部变量

变量的作用域:根据作用域的不同变量分为 全局变量 和 局部变量

  1. 局部变量:在全局作用域下的变量 在全局下都可以使用
    注意:如果在函数内部 没有声明直接赋值的变量也是与全局变量
var num = 10;           // num就是一个全局变量
console.log(num);
function fn() {
    console.log(num);
}
fn(); 
// console.log(aru);
  1. 局部变量:在局部作用域下的变量 后者函数内部的变量就是局部变量
    注意:函数的形参也可以看做是局部变量
function fun(aru) {
    var num1 = 10;      // num1就是局部变量 只能在函数内部使用
    num2 = 20;
}
fun();
console.log(num2);
  1. 从执行效率来看全局变量和局部变量
    (1)全局变量只有浏览器关闭的时候才会毁,比较占内存资源
    (2)局部变量 程序执行完毕就会销毁,比节约内存资源

# 3、作用域链

作用域链:内部函数访问外部函数,采取链式查找的方式 来决定取哪个值的结构。

var num = 10;
function fn() {              // 外部函数
    var num = 20;
    function fun() {         // 内部函数
        console.log(num);    // 输出 20
    }
    fun();
}
fn();

# 4、预解析

  1. js 引擎运行 js 分两步:预解析 代码执行
    (1)预解析:js 引擎会把 js 里面所有的 var 还有 function 提升到当前作用域的最前面
    (2)代码执行:按照代码书写的顺序从上往下执行

  2. 预解析分为 变量预解析(变量提升) 和 函数预解析(函数提升)
    (1)变量提升:把所有的变量声明提升到当前作用域的最前面 不提升赋值操作
    (2)函数提升:把所有的函数声明提升到当前作用域的最前面 不调用函数。

// 1问
console.log(sum);     
// 报错
// 2问
console.log(sum);    
// undefined  坑1
var sum = 10;

// 相当于执行了以下代码
var sum;
console.log(num);
num = 10; 
// 3问
fn();
function fn() {
    console.log(11);     // 11
}
// 4问
fun();                  // 报错
var fun = function() {
    console.log(22); 
}
// 函数表达式 调用必须写在函数表达式的下面

// 相当于执行了以下代码
var fun;
fun();
fun = function() {
    console.log(22);
} 

# 三、对象

# 1、对象的概念

在 js 中,对象是一组无序的相关属性和方法的集合
对象由 属性 和 方法 组成。
属性:事物的特征,在对象中用属性来表示(如手机的颜色、大小、重量)。
方法:事物的行为,在对象中用方法来表示 (如手机可以打电话、发短信、玩游戏)。

# 2、利用对象字面量创建对象

var obj = {
    uname: '安迪',
    age: 18,
    sex: '女',
    sayHi: function() {
        console.log('hi~');
    }
}

(1) 里面的属性或者方法我们采取键值对的形式 键 属性名 : 值 属性值
(2) 多个属性或者方法中间用逗号(,)隔开
(3) 方法冒号后面跟的是一个匿名函数

  1. 使用对象
    (1) 调用对象的属性 我们采取 对象名.属性名 .我们理解为 的
console.log(obj.uname);
console.log(obj.sex);

(2) 调用属性还有一种方法 对象名['属性名']

console.log(obj['age']);

(3) 调用对象的方法 sayHi 对象名.方法名() 千万不能忘了添加小括号

obj.sayHi();

# 3、变量、属性、函数、方法的区别

  1. 变量和属性相同点: 都是用来存储数据的。
    变量: 单独声明并赋值 使用的时候直接写变量名 单独存在。 属性: 在对象里面的不需要声明的 使用的时候必须是 对象.属性。

  2. 函数和方法的相同点:都是实现某种功能 做某件事。
    函数: 单独声明的 并且调用的 函数名() 单独存在的。
    方法: 在对象里面 调用的时候 对象.方法。

# 4、利用 new Object 创建对象

var obj = new Object();       // 创建了一个空的对象
    obj.uname = '张三';
    obj.age = 18;
    obj.sex = '男';
    obj.sayHi = function() {
        console.log('hi~');
    }
    // (1) 利用等号 =  赋值的方法 添加对象的属性和方法
    // (2) 每个属性和方法之间用分号;结束 
    console.log(obj.uname);
    console.log(obj['sex']);
    obj.sayHi();

# 5、构造函数

构造函数:可以利用函数的方法 重复相同的代码 里面封装的不是普通代码,而是 对象。
就是把对象里面一些相同的 属性 和 方法 抽象出来封装到函数里面。

# 6、构造函数创建对象

相同的对象: 名字、年龄、性别 相同的方法:唱歌

function 构造函数名() {
    this.属性 =;
    this.方法 = function() {}
}
new 构造函数名(); 

(一)构造函数: 类似于 java 的 类 (class)

function Star(uname, age, sex) {   // 1.构造函数的名首字母大写 Star
    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);             // object
console.log(ldh.name);
ldh.sing('冰雨');
var zxy = new Star('张学友', 20, '男');
console.log(zxy.name);
ldh.sing('李香兰');
  1. 构造函数不需要 return 就可以返回结果
  2. 调用构造函数必须用 new
  3. 只要 new Star() 调用函数就创建一个对象 如:ldh
  4. 属性和方法前面必须添加 this

(三) 利用构造函数创建对象的过程称为 对象的实例化。

# 7、new 关键字

  1. 在内存中创建一个新的对象
  2. 让 this 指向新的对象
  3. 执行构造函数里的代码,给这个新对象添加属性和方法
  4. 返回这个新对象(所以构造函数里面不需要 return)

# 8、遍历对象

var obj = {
    name: 'andy',
    age: 18,
    sex: '女'
}
console.log(obj.name);
console.log(obj.age);
console.log(obj.sex);

# 9、用 for in 遍历对象

for (变量 in 对象) {
} 
for (var k in obj) {
    console.log(k);          // k 变量 输出得到的是 属性名
    console.log(obj[k]);     // obj[k] 得到的是 属性值
}

# 四、数学对象 Math

# 1. Math 数学对象

Math 数学对象 不是一个构造函数, 所以不需要 new 来调用 而是直接使用里面的属性和方法即可

console.log(Math.PI);                // 一个属性 圆周率  3.141592653589793
console.log(Math.max(1, 99, 3));     // 99
console.log(Math.max(1, 'andy'));  // NaN
console.log(Math.max());             // -Infinity

# 2. Math 绝对值和三个取整方法

⑴ 绝对值方法 | | Math.abs

console.log(Math.abs(-1));        // 1
console.log(Math.abs('-1'));      //  隐式转换 会把字符串型'-1' 转换为字型 -1 所以结果是 1 
console.log(Math.abs('pink'));    // NaN

⑵ 三个取整方法
① Math.floor() floor(地板) 向下取整(往最小了取值)

console.log(Math.floor(1.1));      // 1
console.log(Math.floor(1.9));      // 1

② Math.ceil() ceil(天花板) 向上取整(往最大了取值)

console.log(Math.ceil(1.1));       // 2
console.log(Math.ceil(1.9));       // 2

③ Math.round() 四舍五入 其他数字都是四舍五入, .5特殊往大了取

console.log(Math.round(1.1));      // 1
console.log(Math.round(1.5));      // 2
console.log(Math.round(1.9));      // 2
console.log(Math.round(-1.1));     // -1
console.log(Math.round(-1.5));     // -1(取大的)

# 3. Math 随机数方法

①. Math随机数方法 random() 返回一个随机的小数 0 =< X < 1 [0,1)
②. 这个方法里面不跟参数
③. 代码验证

console.log(Math.random());

④. 想要得到两个数之间的随机整数 并且 包含这2个整数

// 公式: Math.floor(Math.random() * (max - min + 1)) + min;

function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
} 
console.log(getRandom(1, 10));

⑤. 随机点名

var arr = ['张三', '王红', '李四', '叶子', '妮妮'];
// console.log(arr[0]);
console.log(arr[getRandom(0, arr.length - 1)]);


# 五、日期对象 Date

Date( ) 日期对象 是一个构造函数 必须用 new 来调用创建日期对象

var arr = new Array();       // 创建了一个数组对象
var obj = new Object();      // 创建了一个对象实例

① 使用 Date 如果没有参数 返回当前系统的当前时间

var date = new Date();
console.log(date);              // 系统当前时间

② 参数常用的写法:数字型 2021, 12, 12 或者是 字符串'2021-12-12 10:00:00'

var date1 = new Date(2021, 12, 12);
console.log(date1);             // 2022年01月13日
var date2 = new Date('2021-12-12 10:00:00');
console.log(date2);             // 2021年12月12日 10:00:00

⑴格式化年月日星期

var date = new Date();
console.log(date.getFullYear());      // 返回当前日期的年 2021
console.log(date.getMonth() + 1);     // 月份 12 返回的月份小一个月 记得月份 + 1
console.log(date.getDate());          // 返回的是 几号 13号    
console.log(date.getDay());           // 周一    周一返回的是 1, 周六 6, 周日 0

⑵格式化日期时分秒

console.log(date.getHours());       // 时
console.log(date.getMinutes());     // 分
console.log(date.getSeconds());     // 秒

⑶获得 Date 总的毫秒数(时间戳) 不是当前时间的毫秒数 而是距离1970年1月1日过了多少毫秒数
① 通过 valueOf() getTime()

var date = new Date();
console.log(date.valueOf());   // 就是现在的时间  距离1970.1.1 总的毫秒数
console.log(date.getTime());

② 简单的写法(最常用的写法)

var date1 = +new Date();       // +new Date()  返回的就是总的毫秒数
console.log(date1);

③ H5 新增的 获得总的毫秒数

console.log(Date.now());

⑷ 倒计时计算公式

d = parseInt(总秒数 / 60 / 60 / 24);   // 计算天数
h = parseInt(总秒数 / 60 / 60 % 24);   // 计算小时
m = parseInt(总秒数 / 60 % 60);        // 计算分数
s = parseInt(总秒数 % 60);             // 计算秒数

# 六、数组对象 Array

Array数组对象

# 1. 创建数组的两种方式

⑴ 利用数组字面量

var arr = [2, 1, 5];
console.log(arr[0]);

⑵ 利用 new Array( )

var arr1 = new Array();      // 创建了了一个空的数组
var arr1 = new Array(2);     // 这个2表示 数组的长度为2 里面有2个空的数组元素 
var arr1 = new Array(2, 1);  // 等价于[2, 1] 这么写表示 里面有2个数组元素 是2和1
console.log(arr1);

# 2. 检测是否为数组

⑴ instanceof 运算符 它可以用来检测是否为数组

var arr = [];
var obj = {};
console.log(arr instanceof Array);     // true
console.log(obj instanceof Array);     // false

⑵ Array.isArray(参数); H5新增的方法 ie9 以上版本支持
Array.isArray(参数) 的优先级比 instanceof 高

console.log(Array.isArray(arr));     // true
console.log(Array.isArray(obj));     // false

# 3. 添加数组元素

⑴ push() 在数组的末尾 添加一个或多个数组元素 push 推
① push 是可以给数组后面 追加新的元素
② push() 参数直接写 数组元素即可
③ push 完毕之后,返回的结果是 新数组的长度
④ 原数组也会发生变化

var arr = [2, 5 ,3];
// arr.push(7, 'andy');
console.log(arr.push(7, 'andy'));   // 长度 5
console.log(arr);                     //  [2, 5, 3, 7, 'andy']

⑵ unshift() 在数组的开头 添加一个或多个数组元素
① unshift 是可以给数组前面 追加新的元素
② unshift() 参数直接写 数组元素即可
③ unshift 完毕之后,返回的结果是 新数组的长度
④ 原数组也会发生变化

console.log(arr.unshift('red', 'purple'));  // 长度 7
console.log(arr);      // ['red', 'purple', 2, 5, 3, 7, 'andy']

# 4. 删除数组元素

⑴ pop( ) 在数组的末尾删除一个数组元素
① pop 是可以删除最后一个数组元素 一次只删一个
② pop() 没有参数
③ pop 完毕之后,返回的结果是 删除的元素
④ 原数组也会发生变化

console.log(arr.pop());
console.log(arr);

⑵ shift() 在数组的开头删除一个数组元素
① shift 是可以删除第一个数组元素 一次只删一个
② shift() 没有参数
③ shift 完毕之后,返回的结果是 删除的元素
④ 原数组也会发生变化

console.log(arr.shift());
console.log(arr);

# 5. 翻转数组

var arr = ['andy', 'red', 'blue'];
arr.reverse();
console.log(arr);

# 6. 数组排序(冒泡排序)★

var arr1 = [13, 5, 66, 54, 1, 6];
arr1.sort(function(a, b) {
    // return a - b;      升序排列
    return b - a;      // 降序排列
});
console.log(arr1);

# 7. 获取数组元素的索引(下标)的方法

⑴ indexOf(数组元素) 作用就是返回该数组元素的索引号 (从前往后找)

var arr = ['red', 'green', 'blue'];
console.log(arr.indexOf('blue'));       // 2

它只返回第一个满足条件的索引号

var arr1 = ['red', 'blue', 'green', 'blue'];
console.log(arr1.indexOf('blue'));      // 1

它如果在该数组里面找不到元素,则返回的是 -1

var arr2 = ['blue', 'green', 'blue'];
console.log(arr2.indexOf('red'));       // -1

⑵ lastIndexOf(数组元素) 作用就是返回该数组元素的索引号 (从后往前找)

var arr3 = ['red', 'blue', 'green', 'yellow'];
console.log(arr3.lastIndexOf('yellow'));    // 3

# 8. 数组转换为字符串

⑴ toString() 将数组转换为字符串

var arr = [1, 2, 3];
console.log(arr.toString());   // 1, 2, 3

⑵ 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(第几个开始,要删除个数) 返回被删除项目的数组,注意,这个会影响原数组

# 七、字符串对象 String

# 1. 基本包装类型

就是把简单数据类型 包装成了 复杂数据类型

var str = 'andy';
console.log(str.length);     // 长度为 4 

复杂数据类型才有 属性和方法
⑴ 把简单数据类型包装为复杂数据类型

var temp = new String('andy');

⑵ 把临时变量的值 给 str

str = temp;

⑶ 销毁临时变量

temp = null;

# 2. 字符串的不可变性

因为字符串不可变 所以不要大量的拼接字符串

var str = 'andy';
console.log(str);
str = 'red';
console.log(str);

指的是里面的值不可变, 虽然看上去可以改变内容, 但其实是地址变了, 内存中新开辟了一个内存空间。

var str = '';
for (var i = 1; i <= 100; i++) {
    str += i;
}
console.log(str);

# 3. 根据字符返回位置

字符串对象 根据字符返回位置 str.indexOf('要查找的字符',[起始的位置])

var str = '改革春风吹满地,春天来了';
console.log(str.indexOf('春'));      // 2
console.log(str.indexOf('春', 3));   // 8 从索引号是 3 的位置开始往后查找
console.log(str.lastIndexOf('春'));   // 从后往前找,只找第一个匹配的

# 4. 根据位置返回字符

⑴ charAt(index) 根据位置返回字符

var str = 'andy';
console.log(str.charAt(3));       // y
// 遍历所有的字符
for (var i = 0; i < str.length; i++) {
    console.log(str.charAt(i));    //  a  n  d  y  
}

⑵ charCodeAt(index) 返回相应索引号的字符 ASCII 值 目的: 判断用户按下了那个键

console.log(str.charCodeAt(0));   // 97

⑶ str[index] H5 新增的

console.log(str[0]);              // a

# 5. 拼接以及截取字符串

字符串的操作方法
⑴ concat('字符串1', '字符串2'....)

var str = 'andy';
console.log(str.concat('red'));    // andyred

⑵ substr('截取的起始位置', '截取几个字符');

var str1 = '改革春风吹满地';
console.log(str1.substr(3, 2));  // 第一个是 索引号,  第二个是 取几个字符

# 6. 替换字符串以及转换为数组

⑴ 替换字符 replace('被替换的字符', '替换为的字符') 它只会替换第一个字符

var str = 'andyandy';
console.log(str.replace('a', 'b'));    // bndyandy

⑵ 字符转换为数组 split('分隔符') 跟 join 相反

var str2 = 'red, blue, pink';
console.log(str2.split(','));
var str3 = 'red&blue&pink';
console.log(str3.split('&'));

# 八、简单数据类型

# 1、简单数据类型

简单数据类型:又叫做 基本数据类型或者值类型, 复杂数据类型又叫做引用类型

  • 值类型:string、number、boolear、undefined、null

  • 引用类型:通过new创建的对象(系统对象、自定义对象),如:Object、Array、Date等

简单数据类型 null 返回的是一个空的对象 object
如果有个变量以后打算存储为对象,暂时没想好放啥,这个时候就给 null

var timer =  null;
console.log(typeof timer);   // object

# 2、堆和栈空间分配区别

⑴ 栈:简单数据类型存放到栈里面。里面直接开辟一个空间存放值。
⑵ 堆:复杂数据类型存放到堆里面,首先在栈里存放地址 十六进制表示 然后这个地址指向堆里面的数据。

# 3、简单数据类型传参

function fn(a) {
    a++;
    console.log(a);           // 11
}
var x = 10;
fn(x);   
console.log(x);                // 10

# 4、复杂数据类型传参

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.   输出 张学友