文章目录
- 为什么使用 ES6 ?
- 新增语法
- 内置对象扩展的方法
- 正则表达式
- JS 面向对象
- 编程思想
- ES6 对象与类
- 类的继承
- 面向对象版 tab 栏切换
- 构造函数
- 原型对象:star::star::star:
- 原型链与对象成员的查找规则
- 原型对象的应用
- 继承
- 类的本质
- ES5 新增方法
- 函数的定义和调用
- this:star::star::star:
- 严格模式
- 高阶函数
- 闭包:star::star::star:
- 递归:star::star::star:
- 深拷贝-浅拷贝:star::star::star:
- 案例
- JS 高级
- 作用域
- JS 垃圾回收机制
- 闭包
- 变量提升
- 函数
- 展开运算符
- 箭头函数:star:
- 解构赋值:star:
- forEach()方法
- filter()方法
- 深入对象
- 内置构造函数
- 面向对象
- 深浅拷贝
- 异常处理
- 处理this
- 节流
- 防抖
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的 一项脚本语言的标准化规范。
![image-20220125091600162](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220125091600162.png)
2016 ,6 月后
ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。
为什么使用 ES6 ?
每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript 语言本身也有一些令人不满意的地方。
- 变量提升特性增加了程序运行时的不可预测性
- 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码
新增语法
let(★★★)
ES6 中新增了用于声明变量的关键字 let。
1. 具有块级作用域
块级作用域:一对大括号{}产生的作用域。
注意:使用 let 关键字声明的变量才具有块级作用域,使用 var 声明的变量不具备块级作用域。
if (true) {
let a = 10;
var abc = 200;
}
console.log(abc); // 200
console.log(a); // a is not defined
防止循环计数变量变为全局变量
/* -------防止循环变量变成全局变量--------- */
for (let i = 0; i < 2; i++) {}
console.log(i); // i is not defined
2. 不存在变量提升
console.log(a);
var a = 100; // undefined
console.log(b);
let b = 200; //'b' before initialization 初始化前不能访问 b
![image-20220125111006980](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220125111006980.png)
3. 暂时性死区
只要在块级作用域里面使用了 let 命令声明变量 , 那这个变量就会绑定这个区域 , 不再受外部的影响
var tmp = 123;
if (true) {
tmp = 'abc'; // tmp is not defined
let tmp;
}
/*
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
4. 不能重复声明一个变量
let 不允许重复声明一个变量 ( 在同一作用域下 )
let num = 100;
let num = 200; // 'num' has already been declared num 已声明
var
- 不能生成块级作用域
- 有变量提升
经典面试题
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
};
}
arr[0](); // 函数执行时 循环早就结束了, i=2, 函数不满足 , 输出 i,两次输出的都是全局变量i
arr[1]();
![image-20220125121740520](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220125121740520.png)
经典面试题图解:此题的关键点在于变量 i 是全局的,函数执行时输出的都是全局作用域下的 i 值。i++是全局最后为 2 ,
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
};
}
arr[0]();
arr[1]();
![image-20220125121832277](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220125121832277.png)
经典面试题图解:此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的 i 值.
小结
- let 关键字就是用来声明变量的
- 使用 let 关键字声明的变量具有块级作用域
- 在一个大括号中 使用 let 关键字声明的变量才具有块级作用域 var 关键字是不具备这个特点的
- 防止循环变量变成全局变量
- 使用 let 关键字声明的变量没有变量提升
- 使用 let 关键字声明的变量具有暂时性死区特性
const(★★★)
声明常量,常量就是值**(内存地址)不能变化的量**
1. 具有块级作用域
块级作用域:一对大括号{}产生的作用域。
if (true) {
const a = 10;
}
console.log(a); // a is not defined
2. 声明常量时必须赋值
const PI; // Missing initializer in const declaration 丢失初始值
const PI = 3.14;
3. 常量赋值后,值不能修改
作用:声明常量,常量就是值(内存地址)不能变化的量。
常量的值是基本数据类型:
const PI = 3.14;
PI = 100; // Assignment to constant variable. 不能分配一个值 给常量
常量的值是复杂数据类型:
const ary = [100, 200];
ary[0] = "a"; // 地址没有更改 , 修改了值
ary[1] = "b";
console.log(ary); // ['a', 'b'];
ary = ["a", "b"]; // Assignment to constant variable. 不能直接给常量赋值 , 更改了地址
4. 不能重复声明一个变量
const num = 10;
const num = 20;
console.log(num); // 'num' has already been declared num 已声明
const 不存在变量提升 , 先声明后使用
let、const、var 的区别
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
ES6 对象的简化写法
ES6 允许在大括号里面,直接写入变量函数,作为对象的属性和方法,这样写更简洁
let name = "萌小七";
let age = 18;
let sayHi = function () {
console.log("hi~");
};
// 普通写法
const school = {
name: name,
age: age,
sayHi: function () {
console.log("hi~");
},
};
// 简介写法
const school = {
name,
age,
sayHi() {
console.log("hi~");
},
};
console.log(school);
// { name: '萌小七', age: 18, sayHi: [Function: sayHi] }
解构赋值(★★★)
ES6 中允许从数组中提取值,按照对应位置,对变量赋值,对象也可以实现解构
数组解构
语法
// let [变量1, 变量2, 变量3...] = 数组; ~~~~~~~~等号左边的[]是操作符~~~~~~~
// let arr=[1,2,3]
// let [a, b, c] = arr;
let [a, b, c] = [1, 2, 3];
console.log(a); //1
console.log(b); //2
console.log(c); //3
let arr = [1, 2, 3];
let [a, ...c] = arr;
场景
var arr = ["zhangsan", "lisi", "wangwu"];
// 把数组中的元素提取出来,赋值给一些变量
var a = arr[0];
var b = arr[1];
var c = arr[2];
解构不成功的情况:
var [a, b, c, d] = ["zhangsan", "lisi", "wangwu"];
//如果解构不成功,变量的值为undefined
console.log(d);
交换两个变量
let x = 1;
let y = 2;
[x, y] = [y, x]; // 1,2 = 2,1
console.log(x); // 2
console.log(y); // 1
function exchange(num1, num2) {
// 解构赋值
[num1, num2] = [num2, num1];
return `${num1},${num2}`;
}
对象解构
语法
// let {属性名:变量1, 属性名:变量2} = 对象;
let person = { name: "zhangsan", age: 20 };
let { name, age } = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
场景一
用 person 里面的 uname 变量去匹配 person 对象当中的 uname 属性
let person = { uname: "申龙飞", age: 19, sex: "男" };
let { uname, sex, age } = person; // 匹配前后顺序没有关系
console.log(uname); // 申龙飞
console.log(age); // 19
console.log(sex); // 男
变量名和属性名不一致的情况:
键值对情况下 , 冒号左边的 uname 用来属性匹配 k: key
场景二
person 大括号里面 uname 的属性和 person 对象当中的 uname 属性匹配 , 匹配到把 uname 的属性值赋值给 myName
let person = { uname: "申龙飞", age: 19, sex: "男" };
let { uname: myName, age: myAge } = person; // myName myAge 这两个变量名和属性名就不一致
console.log(myName); // 申龙飞
console.log(myAge); // 19
箭头函数(★★★)
语法
ES6 中新增的定义函数的方式。
语法
() => {}; //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
// 以前定义函数:
let fun = function () {
console.log(123);
};
fun(); // 123
// ES6中新增的定义函数的方式。
// 箭头函数
let fun = () => {
console.log(123);
};
fun(); // 123
特点 1
函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
function sum(num1, num2) {
return num1 + num2;
}
//箭头函数写法
const sum = (num1, num2) => num1 + num2;
特点 2
如果形参只有一个,可以省略小括号
function fn(v) {
return v;
}
//箭头函数写法
const fn = (v) => v;
setTimeout(function () {}, 2000);
setTimeout(() => {}, 2000);
数组去重
// 方法四 箭头函数
let unique2 = (arr2) => Array.from(new Set(arr2));
console.log(unique2([11, 33, 22, 11, 22, 33]));
箭头函数中的 this📝
箭头函数不绑定 this 关键字,箭头函数中的 this,this 始终指向函数声明所在作用域下 this 的值 , 箭头函数没有自己的 this
const obj = { name: '张三'}
function fn () {
console.log(this);//this 指向 是obj对象
return () => {
console.log(this);//this 指向的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
}
}
const resFn = fn.call(obj);
resFn();
call() 方法
// 定义:调用一个对象的一个方法,以另一个对象替换当前对象
小结
- 箭头函数中不绑定 this,箭头函数中的 this 指向是它所定义的位置,可以简单理解成,定义箭头函数中的作用域的 this 指向谁,它就指向谁
- 箭头函数的优点在于解决了 this 执行环境所造成的一些问题。比如:解决了匿名函数 this 指向的问题(匿名函数的执行环境具有全局性),包括 setTimeout 和 setInterval 中使用 this 所造成的问题
箭头函数注意点
- (1)箭头函数没有自己的
this
对象 - (2)不可以当作构造函数,也就是说,不可以对箭头函数使用
new
命令,否则会抛出一个错误。 - (3)不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
面试题
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age);
},
};
obj.say(); //箭头函数this指向的是被声明的作用域里面,而对象没有作用域的,所以箭头函数虽然在对象中被定义,但是this指向的是全局作用域
![image-20220125144954167](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220125144954167.png)
剩余参数(★★)
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
实参个数不固定时,使用一个形参变量【在形参前加…】接收剩余的所有参数。
ES6 引入 rest 参数(形式为...变量名
),用于获取函数的多余参数,这样就不需要使用arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function sum(first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sum(10, 20, 30);
剩余参数和解构配合使用
实参传给形参,是赋值操作。
解构操作,也是赋值操作。
let students = ["wangwu", "zhangsan", "lisi"];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
const sum = (...args) => {
let total = 0;
console.log(args); // [10, 20] // [10, 20, 30]
};
sum(10, 20);
sum(10, 20, 30);
let test = (num1, num2, ...args) => {
// 把剩余参数3-7给 args
console.log(args); // [3, 4, 5, 6, 7]
};
test(1, 2, 3, 4, 5, 6, 7);
注意: 剩余参数后面不能再跟形参
// 剩余参数必须是最后一个参数不能是 num8
let test1 = (num1, num2, ...args,num8) => {
// 把剩余参数3-7给 args
console.log(args1); // [3, 4, 5, 6, 7]
};
test1(1, 2, 3, 4, 5, 6, 7,8);
注意:
1、rest 是一个真正的数组
2、rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
内置对象扩展的方法
数组的扩展方法(★★)
![image-20220127110547235](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220127110547235.png)
扩展运算符(扩展语法)
扩展运算符可以将数组或者对象转为用逗号分隔的参数序列
扩展运算符(展开语法)和剩余参数相反 参数转数组 , 数组转参数
let ary = ["a", "b", "c"];
console.log(...ary); // "a","b","c"
console.log("a", "b", "c");
// console 默认会去掉 ,
作用一 合并数组
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2); // 相当于 ary1.push(3,4,5);
作用二 拷贝数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXPmsNtJ-1674049675213)(https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/202203071034020.png)]
作用三 将类数组或可遍历对象转换为真正的数组(伪数组转真数组)
类数组不具有数组的所有方法 , 转为真数组后可以调用数组下的方法
var oDivs = document.getElementsByTagName("div");
console.log(oDivs);
var ary = [...oDivs];
ary.push("a");
console.log(ary);
构造函数方法:Array.from()
将伪数组或可遍历对象转换为真正的数组
//定义一个集合
let arrayLike = {
0: "a",
1: "b",
2: "c",
length: 3,
};
//转成数组
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
方法还可以接受第二个参数,作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组
let arrayLike = {
0: 1,
1: 2,
length: 2,
};
let newAry = Array.from(arrayLike, (item) => item * 2); //[2,4]
数组扩展方法:find()
作用 : 用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let ary = [
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
];
let target = ary.find((item, index) => item.id == 2); //找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个
数组扩展方法:findIndex()
作用 : 用于找出第一个符合条件的元素的索引,如果没有找到则返回-1
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
let index1 = ary1.findIndex((item) => item > 20);
console.log(index1); // -1
数组扩展方法:includes()
作用 : 判断数组是否包含给定的值,返回布尔值。
let ary = ["a", "b", "c"];
let result = ary.includes("a");
console.log(result); // true
result = ary.includes("e");
console.log(result); // false
数组扩展方法 : filter()
方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
// 方法三 filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
// 数组筛选
let arr1 = [1300, 2100, 2800, 3000, 1900];
let res = arr1.filter((value) => value > 2000);
console.log(res);
includes() 数组去重
function repeat(arr) {
// 声明空数组
var newArr = [];
for (var i = 0; i < arr.length; i++) {
// 方法二 includes() 判断数组是否包含给定的值,返回布尔值。
if (newArr.includes(arr[i]) !== true) {
newArr.push(arr[i]);
}
}
return newArr;
}
字符串的扩展方法
模板字符串(★★★)
ES6 新增的创建字符串的方式,使用反引号定义
作用 : 模板字符串中可以解析变量
let name = "张三";
let sayHello = `hello,my name is ${name}`; // hello, my name is zhangsan
作用 : 模板字符串中可以换行
let result = {
name: "zhangsan",
age: 20,
sex: "男",
};
let html = ` <div>
<span>${result.name}</span>
<span>${result.age}</span>
<span>${result.sex}</span>
</div> `;
作用 : 在模板字符串中可以调用函数
在调用函数的位置返回函数的返回值
const fn = () => {
return "我是fn 函数";
};
let html1 = `我是模版字符串${fn()}`;
console.log(html1); // 我是模版字符串我是fn 函数
字符串扩展方法:startsWith() 和 endsWith()
- startsWith():表示参数字符串是否在原字符串的头部,返回布尔值 , 判断是否以某个字符开头
- endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值 , 判断是否以某个字符结尾
let str = "Hello ECMAScript 2015";
let re1 = str.startsWith("Hello");
let re2 = str.endsWith("2015");
console.log(re1);
console.log(re2);
字符串扩展方法:repeat(n)
repeat 方法表示将原字符串重复 n 次,返回一个新字符串
"x".repeat(3); // "xxx"
"hello".repeat(2); // "hellohello"
Set 数据结构(★★)
ES6 提供了新的数据结构 Set(数据的集合或者值的集合)。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构
const s = new Set();
Set 函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4, 5, 5]); //{1, 2, 3, 4 , 5}
Set 方法
- add(value):添加某个值,返回 Set 结构本身
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为 Set 的成员
- clear():清除所有成员,没有返回值
const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值
s.delete(2); // 删除 set 结构中的2值
s.has(1); // 表示 set 结构中是否有1这个值 返回布尔值
s.clear(); // 清除 set 结构中的所有值
//注意:删除的是元素的值,不是代表的索引
set 数组去重
function unique(arr1) {
// set 数据结构 , 类似于数组 ,值是唯一的
// 利用Array.from()将set 结构转为数组
return Array.from(new Set(arr1));
}
console.log((res3 = unique([11, 33, 22, 11, 22, 33])));
})();
案例 - 数组去重
// 方法二 Array.from() set
function unique(arr1) {
// set 数据结构 , 类似于数组 ,值是唯一的
// 利用Array.from()将set 结构转为数组
return Array.from(new Set(arr1));
}
console.log((arr1 = unique([11, 33, 22, 11, 22, 33])));
// 方法三 ...new Set(arr)
function unique3(arr3) {
return [...new Set(arr3)]; // 返回的是去重的数组
}
console.log(unique3([11, 33, 22, 11, 22, 33]));
// 方法四 箭头函数
// let unique2 = (arr2) => Array.from(new Set(arr2));
let unique2 = (arr2) => [...new Set(arr2)];
console.log(unique2([11, 33, 22, 11, 22, 33]));
遍历
Set 结构的方法与数组一样,也拥有 forEach 方法,用于对每个成员执行某种操作,没有返回值。
// 遍历set 数据结构从中取值
const s5 = new Set(["a", "b", "c"]);
s5.forEach((value) => {
console.log(value);
});
s.forEach((value) => console.log(value));
遍历数组
// 方法三 filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
let arr1 = [1300, 2100, 2800, 3000, 1900];
let res = arr1.filter((value) => value > 2000);
console.log(res);
// 遍历数组
res.forEach((element) => console.log(element));
正则表达式
正则表达式 :就是用特殊字符描述字符串规律的式子
作用 : 匹配字符组合 , 替换 , 提取 (表单验证)
正则表达式在 js 中的使用
正则表达式的创建
- 利用 RegExp 对象来创建 正则表达式(很少用)
var regexp = new RegExp(/123/);
console.log(regexp);
- 利用字面量创建 正则表达式(推荐)
var rg = /123/;
测试正则表达式
- 正则对象.test(被校验的字符串) 方法用来检测字符串是否符合正则表达式要求的规范
- 返回值 true false
console.log(rg.test(123));
只要包含有123的这个字符串就是true;
console.log(rg.test("abc"));
正则表达式中的特殊字符
特殊字符非常多,可以参考:
jQuery 手册:正则表达式部分
[正则测试工具]( <http://tool.oschina.net/regex)
- /123/ 验证字符串中是否包含指定字符
- /^abc/ 判断字符串是否以指定字符开头
- /abc$/ 判断字符串是否以指定字符结尾
- /^abc$/ 精确查找
边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
如果 ^和 $ 在一起,表示必须是精确匹配。
var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型
// /abc/ 只要包含有abc这个字符串返回的都是true
console.log(rg.test("abc"));
console.log(rg.test("abcd"));
console.log(rg.test("aabcd"));
console.log("---------------------------");
var reg = /^abc/;
console.log(reg.test("abc")); // true
console.log(reg.test("abcd")); // true
console.log(reg.test("aabcd")); // false
console.log("---------------------------");
var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
console.log(reg1.test("abc")); // true
console.log(reg1.test("abcd")); // false
console.log(reg1.test("aabcd")); // false
console.log(reg1.test("abcabc")); // false
字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
[] 方括号
-
[-] 方括号内部 范围符-
-
[^] 方括号内部 取反符^
注意和边界符 ^ 区别,边界符写到方括号外面。
表示有一系列字符可供选择,只要匹配其中一个就可以了
- /[abc]/多选一 只要包含其中一个字符就返回 true
- /1KaTeX parse error: Expected group after '^' at position 9: / 多选一 , ^̲精确匹配 a b c abc
- /2$/
- /3$/
- /4$/
var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
console.log(rg.test('andy'));//true
console.log(rg.test('baby'));//true
console.log(rg.test('color'));//true
console.log(rg.test('red'));//false
var rg1 = /^[abc]$/; // 三选一 只有是a 或者是 b 或者是c 这三个字母才返回 true
console.log(rg1.test('aa'));//false
console.log(rg1.test('a'));//true
console.log(rg1.test('b'));//true
console.log(rg1.test('c'));//true
console.log(rg1.test('abc'));//true
----------------------------------------------------------------------------------
var reg = /^[a-z]$/ //26个英文字母任何一个字母返回 true - 表示的是a 到z 的范围
console.log(reg.test('a'));//true
console.log(reg.test('z'));//true
console.log(reg.test('A'));//false
-----------------------------------------------------------------------------------
//字符组合
var reg1 = /^[a-zA-Z0-9]$/; // 26个英文字母(大写和小写都可以)任何一个字母返回 true
------------------------------------------------------------------------------------
//取反 方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
var reg2 = /^[^a-zA-Z0-9]$/;
console.log(reg2.test('a'));//false
console.log(reg2.test('B'));//false
console.log(reg2.test(8));//false
console.log(reg2.test('!'));//true
量词符
量词符用来设定某个模式出现的次数。
量词 | 说明 |
---|---|
* | 重复 0 次或更多次{0,} |
+ | 重复 1 次或更多次{1,} |
? | 重复 0 次或 1 次{0,1} |
{n} | 重复 n 次 |
{n,} | 重复 n 次或更多次 |
{n,m} | 重复 n 到 m 次 |
![image-20220209114037492](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220209114037492.png)
// 量词符: 用来设定某个模式出现的次数
// var reg = /^a$/;
// console.log(reg.test('a'));
// console.log(reg.test('aa'));
// 1. * 相当于 >= 0 可以出现0次或者很多次
// var reg = /^a*$/;
// console.log(reg.test(''));
// console.log(reg.test('a'));
// console.log(reg.test('aa'));
// console.log(reg.test('aaaaaa'));
// 2. + 相当于 >= 1 可以出现1次或者很多次
// var reg = /^a+$/;
// console.log(reg.test(''));
// console.log(reg.test('a'));
// console.log(reg.test('aa'));
// console.log(reg.test('aaaaaa'));
// 3. ? 相当于 1 || 0
// var reg = /^a?$/;
// console.log(reg.test(''));
// console.log(reg.test('a'));
// console.log(reg.test('aa'));
// console.log(reg.test('aaaaaa'));
// 4. {3 } 就是重复3次
// var reg = /^a{3}$/;
// console.log(reg.test(''));
// console.log(reg.test('a'));
// console.log(reg.test('aa'));
// console.log(reg.test('aaaaaa'));
// console.log(reg.test('aaa'));
// 5. {3, } 大于等于3
var reg = /^a{3,}$/;
console.log(reg.test(""));
console.log(reg.test("a"));
console.log(reg.test("aa"));
console.log(reg.test("aaaaaa"));
console.log(reg.test("aaa"));
// 6. {3, 6} 大于等于3 并且 小于等于6
var reg = /^a{3,6}$/;
console.log(reg.test(""));
console.log(reg.test("a"));
console.log(reg.test("aa"));
console.log(reg.test("aaaaaa"));
console.log(reg.test("aaa"));
console.log(reg.test("aaaaaaaaaaaaaaaaaaaaa"));
var reg = /^[a-zA-Z0-9_-]{6,16}$/;
// {6,16} 中间不要有空格
console.log(reg.test("")); // false
console.log(reg.test("aaa")); // false
console.log(reg.test("andy-rede")); // true
console.log(reg.test("andy_rede")); // true
console.log(reg.test("andy$rede")); // false
案例 - 用户名验证
// 量词是设定某个模式出现的次数
var reg = /^[a-zA-Z0-9_-]{6,16}$/; // 这个模式用户只能输入英文字母 数字 下划线 短横线但是有边界符和[] 这就限定了只能多选1
// {6,16} 中间不要有空格
var uname = document.querySelector(".uname");
var span = document.querySelector("span");
uname.onblur = function () {
if (reg.test(this.value)) {
console.log("正确的");
span.className = "right";
span.innerHTML = "用户名格式输入正确";
} else {
console.log("错误的");
span.className = "wrong";
span.innerHTML = "用户名格式输入不正确";
}
};
括号总结
1.大括号 量词符. 里面表示重复次数
2.中括号 字符集合。匹配方括号中的任意字符.
3.小括号表示优先级
// 中括号 字符集合.匹配方括号中的任意字符.
var reg = /^[abc]$/;
// a 也可以 b 也可以 c 可以 a ||b || c
// 大括号 量词符. 里面表示重复次数
var reg = /^abc{3}$/; // 它只是让c重复三次 abccc
console.log(reg.test("abc"));
console.log(reg.test("abcabcabc"));
console.log(reg.test("abccc"));
// 小括号 表示优先级
var reg = /^(abc){3}$/; // 它是让abc重复三次
console.log(reg.test("abc"));
console.log(reg.test("abcabcabc"));
console.log(reg.test("abccc"));
预定义类
预定义类指的是某些常见模式的简写方式.
预定类 | 说明 |
---|---|
\d | 匹配 0-9 之间的任一数字 相当于[0-9] |
\D | 匹配所有 0-9 以外的 相当于[ ^ 0-9] |
\w | 匹配任意的字母 , 数字和下划线 , 相当于[A-Za-z0-9_] |
\W | 除所有字母 , 数字和下划线以外的字符 , 相当于[ ^ A-Za-z0-9_] |
\s | 匹配空格 (包括换行符, 指标符 , 空格符等) , 相当于[\t\r\n\v\f] |
\S | 匹配非空格的字符 , 相当于[ ^ \t\r\n\v\f] |
案例:验证座机号码
let reg1 = /^\d{3}-\d{8}|\d{4}-\d{7}$/;
console.log(reg1.test("010-12345678"));
console.log(reg1.test("0110-1234581"));
let reg2 = /^\d{3,4}-\d{7,8}$/;
console.log(reg2.test("010-12345678"));
console.log(reg2.test("0110-1234581"));
案例:表单验证
var regtel = /^1[3|4|5|7|8]\d{9}$/; // 手机号码的正则表达式
var regqq = /^[1-9]\d{4,}$/; // 10000
var regnc = /^[\u4e00-\u9fa5]{2,8}$/; // 昵称
var regmsg = /^\d{6}$/; // 验证码
var regpwd = /^[a-zA-Z0-9_-]{6,16}$/; // 密码验证
var tel = document.querySelector("#tel");
var qq = document.querySelector("#qq");
var nc = document.querySelector("#nc");
var msg = document.querySelector("#msg");
var pwd = document.querySelector("#pwd");
var surepwd = document.querySelector("#surepwd");
regexp(tel, regtel); // 手机号码
regexp(qq, regqq); // qq号码
regexp(nc, regnc); // 昵称
regexp(msg, regmsg); // 短信验证
regexp(pwd, regpwd); // 密码框
// 表单验证的函数
function regexp(ele, reg) {
ele.onblur = function () {
if (reg.test(this.value)) {
// console.log('正确的');
this.nextElementSibling.className = "success";
this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜您输入正确';
} else {
// console.log('不正确');
this.nextElementSibling.className = "error";
this.nextElementSibling.innerHTML = '<i class="error_icon"></i> 格式不正确,请从新输入 ';
}
};
}
// 确认密码
// 再次输入密码只需匹配与上次输入的密码值 是否一致
surepwd.onblur = function () {
if (this.value == pwd.value) {
this.nextElementSibling.className = "success";
this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜您输入正确';
} else {
this.nextElementSibling.className = "error";
this.nextElementSibling.innerHTML = '<i class="error_icon"></i> 两次密码输入不一致';
}
};
正则替换 replace()
var str = "abcabc";
var nStr = str.replace(/a/, "哈哈");
console.log(nStr); // 哈哈bcabc
var str = "把人杀了,人就死,钱就被抢走了";
var nstr = str.replace(/死|杀|钱/g, "**");
console.log(nstr); // 把人**了,人就**,**就被抢走了
replace(被替换的字符串|正则表达式 , 替换成的字符串)
replace(/死|杀|钱/g, "**")E
- /表达式/[switch]
有三种值
- g:全局匹配
- i:忽略大小写
- gi:全局匹配 + 忽略大小写
var text = document.querySelector("textarea");
var btn = document.querySelector("button");
var div = document.querySelector("div");
btn.onclick = function () {
div.innerHTML = text.value.replace(/死|杀|钱/g, "**");
};
特殊字符(元字符)
字符 | 含义 |
---|---|
^ | 以哪个字符开始 |
$ | 以哪个字符结束 |
[] | 字符集合 , 匹配方括号中的任意字符(多选一) |
[^] | 取反 , 匹配任何没有包括在方括号中的字符 |
{n} | n 是正整数 , 匹配了前一个字符出现了几次 |
{n,} | n 是正整数 , 匹配了前一个字符至少出现了几次 |
{n,m} | n,m 是正整数 , 匹配前一个字符出现了几次到几次 |
* | 匹配前一个表达式出现了 0 次或多次 . 等价于{0,} |
+ | 匹配前一个表达式出现了 1 次或多次 . 等价于{1,} |
? | 匹配前一个表达式出现了 0 次或 1 次 . 等价于{0,1} |
() | 优先级 |
预定义类
预定义类 | 含义 |
---|---|
\d | 匹配一个数字.等价于[0-9] |
\D | 匹配一个非数字字符 . 等价于 [ ^ 0-9] |
\w | 匹配一个单子字符(字母,数字下划线) . 等价于[a-zA-Z0-9_] |
\W | 匹配一个非单子字符.等价于[ ^ a-zA-Z0-9_] |
\s | 匹配一个空白字符,包括空格,制表符, 换页符和换行符 |
\S | 匹配一个非空白字符 |
JS 面向对象
![image-20220124095518557](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220124095518557.png)
![image-20220124095538504](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220124095538504.png)
编程思想
1. 面向过程
POP(Process-oriented programming)
-
面向过程(重在分析过程,按过程进行开发)
-
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
![image-20220124214003706](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220124214003706.png)
面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
2. 面向对象
OOP (Object Oriented Programming)
-
面向对象(重在划分对象,完成对象中的功能)
-
面向对象是把事物分解成为一个个对象,然后由对象之间分工与合作。
举个栗子:将大象装进冰箱,面向对象做法。【先找出对象,并写出这些对象的功能】
- 大象对象
- 进去
- 冰箱对象
- 打开
- 关闭
- 使用大象和冰箱的功能
面向对象是以对象功能来划分问题,而不是步骤。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
-
封装性
-
继承性
-
多态性
3. 面向过程与面向对象对比
面向过程 | 面向对象 | |
---|---|---|
优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 |
缺点 | 不易维护、不易复用、不易扩展 | 性能比面向过程低 |
用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。
ES6 对象与类
面向对象的思维特点:
1.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
2.对类进行实例化, 获取类的对象
面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点,不断的创建对象,使用对象,指挥对象做事情.
1. 对象
具体的某一事物。由属性和方法组成
2. 类 class
泛指某一大类,抽取了对象中的公共部分,可以实例化出对象
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后用这个类来实例化对象。
-
类抽象了对象的公共部分,它泛指某一大类(class),而对象特指某一个。
-
通过类的实例化,可以得到一个具体的对象
![image-20220124221552102](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220124221552102.png)
3. 定义类和创建对象
类语法结构 : class 类名{} (class Star{})
创建对象 : 使用 new 关键字来进行创建 new 类名 (new name())
注意: 类必须使用 new 实例化对象
//步骤1 使用class关键字,类名的首字母通常会大写
class Star {
// class body
}
//步骤2使用定义的类创建实例 注意new关键字
var ldh = new Star();
new 关键字的执行:
- 创建一个空对象
- this 指向该对象
- 给对象中添加属性
- 返回该对象
4. 类的构造函数 constructor
作用:类的构造函数,用于传递参数(给成员变量赋值),返回实例对象;
特点:
-
constructor 函数是一个构造器(构造函数)
-
使用 new 关键字生成实例的时候,会自动调用constructor 函数
-
如果没有显示定义这个函数, 类内部会自动给我们创建一个 constructor()
![image-20220210103708052](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220210103708052.png)
let that;
class Star {
constructor(name, age) {
that = this;
this.name = name;
this.age = age;
}
}
let ldh = new Star("刘德华", 18);
console.log(ldh);
let zxy = new Star("张学友", 20);
console.log(zxy);
console.log(that === ldh); // 判断this 是否指向实例出来的对象
语法
class Person {
// 类的共有属性放到 constructor 里面
constructor(name, age) {
// constructor 构造方法或者构造函数,注意没有function
this.name = name;
this.age = age; // 构造函数中的this 代表,创建的实例对象
}
}
创建实例
var ldh = new Person(‘刘德华’, 18); // 实例化对象时,类中的构造函数会被自动调用
console.log(ldh.name)
一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
5. 类中添加属性和方法
注意:方法之间不需要逗号分隔;方法名前不需要加 function 关键字
//步骤1 使用class关键字
class Person {
constructor(name, age) {
// constructor 构造器或者构造函数
// 添加属性
this.name = name;
this.age = age;
} //--------------注意1,所有的方法不需要使用function。------>注意2,方法与方法之间不需要添加逗号
say() {
console.log(this.name + "你好");
}
}
//步骤2使用定义的类创建实例 注意new关键字
var ldh = new Person("刘德华", 18);
ldh.say();
6. 类里面 this 指向问题
- constructor 中的 this 指向的是 new 出来的实例对象
- 类中自定义的方法,指向方法的调用对象(一般也指向的 new 出来的实例对象)
- 事件处理函数中,this 指向的就是触发事件的事件源
![image-20220210111229126](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220210111229126.png)
<button>点击</button>;
let that;
class Star {
constructor(name, age) {
that = this;
this.name = name; // 实例对象 ldh
this.age = age;
this.btn = document.querySelector("button"); // 对象里面的属性值可以是一个元素对象或者数组和对象
this.btn.onclick = this.sayHi;
that = this;
}
sing(song) {
console.log(this.name + song);
// 类中的方法里面的this 指向方法的调用者 ldh
}
sayHi() {
// console.log(11);
console.log(this); // 方法绑定时this指向谁调用了方法this指向谁 (btn调用了方法)
console.log(that.name + "唱歌"); // that 指向constructor 中的实例对象 ldh
}
}
let ldh = new Star("刘德华", 18);
// console.log(ldh);
ldh.sing("冰雨");
let zxy = new Star("张学友", 20);
// console.log(zxy);
zxy.sing("黄昏");
console.log(that === zxy); // 判断this 是否指向实例出来的对象
类的继承
1. 基本用法
继承
程序中的继承:子类可以继承父类的一些属性和方法。
语法:
extends 关键字继承
class Father {
// 父类
}
class Son extends Father {
// 子类继承父类
}
代码实例
class Father {
constructor(surname) {
this.surname = surname;
}
say() {
console.log("你的姓是" + this.surname); // 指向方法调用者
}
}
class Son extends Father {
// 这样子类就继承了父类的属性和方法
}
var damao = new Son("刘");
damao.say();
2. super 关键字
![image-20220210114210472](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220210114210472.png)
-
调用父类的构造函数
- 没有显示定义 constructor 构造函数:在 new 子类实例时会自动调用父类的构造函数,父类构造函数中的 this 指向的是子类
class Father { constructor() { this.name = "zzz"; } money() { console.log(100); } } class Son extends Father {} var son = new Son(); // 会自动调用父类构造函数 console.log(son);
- 有显示定义 constructor 构造函数:需要在其构造函数中调用 super(),否则会报错。
class Father { constructor(x, y) { this.x = x; this.y = y; } sum() { console.log(this.x + this.y); } } class Son extends Father { constructor(x, y) { super(x, y); //调用了父类中的构造函数 } } var son = new Son(1, 2); var son1 = new Son(11, 22); son.sum(); son1.sum();
-
调用父类的普通函数
super.父类方法()
// super 关键字调用父类普通函数 class Father { say() { return "我是爸爸"; } } class Son extends Father { say() { // super.say() 就是调用父类中的普通函数 say() console.log(super.say() + "的儿子"); } } var son = new Son(); son.say();
3. 继承中属性和方法的查找规则
继承中的属性或者方法查找原则: 就近原则(自己有就用自己的,自己没有就用父亲的)
代码实例
class Father {
say() {
return "我是爸爸";
}
}
class Son extends Father {
say() {
console.log("我是儿子");
}
}
var son = new Son();
son.say();
4. 子类构造函数中的 super()必须放在 this 之前
在子类的构造函数中,只有调用super
之后,才可以使用this
关键字,否则会报错。
这是因为子类实例的构建,基于父类实例,只有super
方法才能调用父类实例。
代码实例
// 父类有加法方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
// 子类继承父类加法方法 同时 扩展减法方法
class Son extends Father {
constructor(x, y) {
// 利用super 调用父类的构造函数 super 必须在子类this之前调用,放到this之后会报错
super(x, y);
this.x = x;
this.y = y;
}
subtract() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.subtract();
son.sum();
5. 使用类的注意事项
【1】类没有变量提升
在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
【2】属性和方法前要加 this
<button>点击</button>
<script>
class Star {
constructor(uname, age) {
this.uname = uname;
this.age = age;
// 属性的值可以是任意数据类型
this.btn = document.querySelector('button'); // dom元素对象
/* 此时this.sing这个方法被当成了事件处理函数,
事件处理函数中的this指向的是事件源对象,即btn按钮 */
// 元素对象.on事件名 = 事件处理函数;
this.btn.onclick = this.sing;
}
sing() {
console.log(this.uname);
}
}
var ldh = new Star('刘德华');
// 1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
// 2. 类里面的共有的属性和方法一定要加this使用.
</script>
【3】this 指向问题
类里面的共有属性和方法,一定要加 this 使用。
- constructor(构造函数)中的 this 指向的是 new 出来的实例对象
- 类中自定义的方法,一般也指向的 new 出来的实例对象(调用者)
- 事件处理函数中,this 指向的就是触发事件的事件源(调用者 btn )
![image-20220124233314600](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220124233314600.png)
面向对象版 tab 栏切换
1. 功能分析:
1.点击 tab 栏,可以切换效果.
2.点击 + 号, 可以添加 tab 项和内容项.
3.点击 x 号, 可以删除当前的 tab 项和内容项.
4.双击 tab 项文字或者内容项文字,可以修改里面的文字内容.
2.案例准备:
第 1 步,创建 tab.js 文件
第 2 步,在 index.html 中引入 js
第 3 步,抽取 Tab 对象
- 该对象具有切换功能
- 该对象具有添加功能
- 该对象具有删除功能
- 该对象具有修改功能
第 4 步,划分模块,初始化
class Tab {
// 构造函数可以接收参数, new 的时候会自动调用 constructor 构造函数
constructor(id) {
// this 指向的是实例化对象(#tab)
// 获取元素
this.main = document.querySelector(id);
this.lis = document.querySelectorAll("li");
this.sections = document.querySelectorAll("section");
this.init();
}
// 初始化操做(页面刷新后)
init() {
// this 指向实例化对象(方法的调用者)
for (var i = 0; i < this.lis.length; i++) {
// 获取每个li 的索引
this.lis[i].index = i;
this.lis[i].onclick = function () {
console.log(this.index);
};
}
}
// 1.切换功能
toggleTab() {}
// 2.添加功能
addTab() {}
// 3.删除功能
removeTab() {}
// 4.修改功能
editTab() {}
}
new Tab("#tab");
![image-20220124235017343](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220124235017343.png)
3. 切换功能
- 为获取到的标题绑定点击事件
- 事件处理函数中使用排他
// 1.切换功能
toggleTab() {
// that 指向 constructor 实例对象
// 排他
that.clearClass();
// this 指向触发事件的事件源(li)
// 留下自己
this.className = "liactive";
// this 指向li,li里面没有 sections ,应该指向 constructor 的sections 用that
that.sections[this.index].className = "conactive";
}
// 清除类
clearClass() {
// this 指向方法的调用者(实例对象)
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].className = "";
this.sections[i].className = "";
}
}
![image-20220125000202481](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220125000202481.png)
4. 添加功能
1.点击 + 可以实现添加新的选项卡和内容
2.第一步: 创建新的选项卡 li 和 新的 内容 section
3.第二步: 把创建的两个元素追加到对应的父元素中.
4.以前的做法: 动态创建元素 createElement , 但是元素里面内容较多, 需要 innerHTML 赋值,在 appendChild 追加到父元素里面.
5.现在高级做法: 利用 insertAdjacentHTML() 可以直接把字符串格式元素添加到父元素中
6.appendChild 不支持追加字符串的子元素, insertAdjacentHTML 支持追加字符串的元素
7.insertAdjacentHTML(追加的位置,‘要追加的字符串元素’)
8.追加的位置有: beforeend 插入元素内部的最后一个子节点之后
9.该方法地址: https😕/developer.mozilla.org/zh-CN/docs/Web/API/Element/insertAdjacentHTML
解决添加后的元素不能切换和清除类样式
- 把获取 li 和 section 方法提取出来
- 初始化时调用一下
this.updateNode()
- 添加后在初始化一下
that.init()
addTab() {
// this 指向触发事件的事件源(btn)
let random = Math.random();
console.log(random);
that.clearClass();
// (1)创建新的选项卡li 和 新的 内容 section
let li = '<li class="liactive"><span>测试</span><span class="iconfont icon-guanbi"></span></li>';
let section = '<section class="conactive">测试' + random + "</section>";
// (2)把创建的两个元素追加到对应的父元素中
that.ul.insertAdjacentHTML("beforeend", li);
that.fsection.insertAdjacentHTML("beforeend", section);
// 添加后在初始化一下
that.init();
}
5. 删除功能
![image-20220211180032462](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211180032462.png)
// 3.删除功能
removeTab(e) {
// this 指向触发事件的事件源(关闭按钮)
e.stopPropagation(); // 阻止冒泡 防止触发 li 的点击切换事件
let index = this.parentNode.index;
// 根据索引删除对应的li 和section , remove方法可以直接删除指定元素
that.lis[index].remove();
that.sections[index].remove();
that.init(); // 删完后初始化一下
// 判断,当前li是不是被选中的(高亮显示)
if (document.querySelector(".liactive")) return;
// 当删除了选中转态的这个li时,让它的前一个li 处于选中转态(手动调用点击事件)
index--;
// 手动调用点击事件, 不需要鼠标触发
that.lis[index] && that.lis[index].click(); // 前面的为真(存在),就执行后面的点击事件
}
![image-20220211184423024](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211184423024.png)
6. 编辑功能
![image-20220210175225391](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220210175225391.png)
- 禁止选中文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
// 4.修改功能
editTab() {
// this 指向触发事件的事件源(span)
var val = this.innerHTML;
// 禁止选中文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
// 创建input框
this.innerHTML = '<input type="text" />';
// 获取文本框,给文本框添加 value
var input = this.children[0];
input.value = val;
input.select(); // 文本框里面的值处于选中转态
// 给文本框注册失去焦点事件
input.onblur = function () {
this.parentNode.innerHTML = this.value;
};
// 给文本框注册键盘事件
input.onkeyup = function (e) {
if (e.keyCode === 13) {
this.blur(); // 手动调用表单失去焦点事件, 不需要鼠标离开操作
}
};
}
![image-20220211195839349](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211195839349.png)
7. 案例经验
1、面向对象必须使用 this 进行属性和方法的使用
2、注意 this 指向,尤其是添加的事件中的 this
3、使用全局变量 that 辅助存储实例对象
4、双击事件 ondblclick
5、禁止双击选中文本
window.getSelection? window.getSelection().removeAllRanges(): document.selection.empty();
6、input 选中文本 input.select()
7、**insertAdjacentHTML
**方法添加元素
// 前面的为真(存在),就执行后面的点击事件
// 手动调用点击事件, 不需要鼠标触发
that.lis[index] && that.lis[index].click();
this.blur(); // 手动调用表单失去焦点事件, 不需要鼠标离开操作
流程图制作软件 DiagramDesignerSetup.1.29.5.msi
构造函数
ES6 之前是通过构造函数和原型模拟类的实现机制
1.概述
在典型的 **OOP (面向对象)**的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6 之前, JS 中并没用引入类的概念。
在 ES6 之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。
2.对象的三种创建方式(复习)
-
字面量方式
var obj = {};
-
new 关键字
var obj = new Object(); // Object也是一个构造器
-
构造函数方式
function Person(name, age) { this.name = name; this.age = age; } var obj = new Person("zs", 12);
-
类
class Star { constructor(name) { this.name = "name"; } } let obj = new Star("ldh");
3.构造函数
构造函数
1.内置构造函数 : Object Date Array
2.自定义的构造函数 : 类中的 Star 等
所有的构造函数的实例对象 , 都可以共享构造函数原型对象里面的方法
console.dir(Array); // pups()
console.dir(Object); // toStrong
console.dir(Date); // getDate
作用
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
使用构造函数时要注意以下两点:
1.构造函数用于创建某一类对象,其首字母要大写
2.构造函数要和 new 一起使用才有意义
// 3. 利用构造函数创建对象
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log("我会唱歌");
};
}
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 19);
console.log(ldh);
ldh.sing();
zxy.sing();
new 在执行时会做四件事情
① 在内存中创建一个新的空对象。
② 让 this 指向这个新的对象。
③ 执行构造函数里面的代码,给这个新对象添加属性和方法。
④ 返回这个新对象(所以构造函数里面不需要 return )。
4.静态成员和实例成员
实例成员
成员 : 属性和方法
- 实例成员:就是构造函数内部通过 this 添加的成员,只能由实例化的对象来访问,不能通过构造函来数访问
// 构造函数中的属性和方法我们称为成员, 成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log("我会唱歌");
};
}
var ldh = new Star("刘德华", 18);
console.log(ldh.uname); //实例成员只能通过实例化的对象来访问
console.log(Star.uname); // 不能通过构造函来数访问
静态成员
- 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身来访问,不能通过对象来访问
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log("我会唱歌");
};
}
// 静态成员 在构造函数本身上添加的成员 sex 就是静态成员
Star.sex = "男";
var ldh = new Star("刘德华", 18);
console.log(Star.sex); //静态成员只能通过构造函数来访问
console.log(ldh.sex); // 不能通过对象来访问
function Star(uname, age, sex) {
this.name = name;
this.age = age;
this.sex = sex; // 实例成员
}
var ldh = new Star("刘德华", 20, "男");
console.log(ldh.sex); // 实例化的对象来访问
Star.addrss = "太原"; // 静态成员,在构造函数本身上添加的成员
console.log(Star.addrss); // 静态成员只能通过对象来访问
原型对象⭐️⭐️⭐️
1.构造函数的问题
构造函数创建对象很好用,但是如果把方法定义在构造函数内,就存在浪费内存的问题。
new 创建一个内存空间 ,存储复杂数据类型, 存在浪费内存的问题
简单数据: 栈里面存的是值
复杂数据: 栈里申请一个内存存的是地址 , 在堆里面开辟一个内存 ,指向堆
![image-20220211210711715](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211210711715.png)
我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上。
检测变量是否是某个构造函数的实例
var arr = [1, 23];
console.log(arr instanceof Array); // true
2.构造函数的原型 prototype
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个函数都有一个 prototype 属性,指向另一个对象,prototype 就是一个对象 , 这个对象叫做**【原型对象】**。
我们可以把那些不变的方法(地址不变),直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
- 原型是什么 ?
一个对象,我们也称为 prototype 为原型对象。
- 原型的作用是什么 ?
共享方法。
function Star(uname, age) {
// 构造函数中定义属性
this.uname = uname;
this.age = age;
}
// 把方法定义到原型对象身上 , 对象是可以直接进行属性和方法添加 通过 ·
Star.prototype.sing = function () {
console.log("我会唱歌");
};
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 19);
ldh.sing();
zxy.sing();
// 所有的实例对象,共用一个方法,省内存
console.log(ldh.sing === zxy.sing); // true
console.log(typeof star.prototype); // Object
// 2. 一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
![image-20220212102820273](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212102820273.png)
3.对象原型
实例对象都有一个属性 __proto__
指向构造函数的 prototype
原型对象
之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在。
__proto__对象原型和原型对象 prototype 是等价的。
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype。
方法的查找规则 : 首先看 ldh 对象身上是否有 sing 方法 , 如果有就执行这个对象上的 sing , 如果没有 sing 这个方法 , 因为有 __proto__
的存在 , 就去构造数原型对象 prototype
身上查找 sing 这个方法.
![image-20220211225833776](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211225833776.png)
- 每个函数都有一个
prototype
原型对象 ,prototype
添加和共享方法 - 每个实例对象都有一个
__proto__
对象原型 ,__proto__
指向构造函数的原型对象
console.log(ldh.__proto__);
console.log(Star.prototype);
console.log(ldh.__proto__ === Star.prototype); // true
console.log(zxy.__proto__ === Star.prototype); // true
![image-20220211231144875](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211231144875.png)
面试题⭐️⭐️⭐️
![image-20220212112822253](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212112822253.png)
![image-20220212112836440](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212112836440.png)
4.constructor 构造函数
对象原型( __proto__
)和原型对象(prototype)里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
作用 : 可以让原型对象重新指向原来的构造函数。
应用场景 : 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor 指回原来的构造函数
![image-20220211232054278](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211232054278.png)
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
Star.prototype.sing = function () {
console.log("我会唱歌");
};
Star.prototype.movie = function () {
console.log("我会演电影");
};
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star,
sing: function () {
console.log("我会唱歌");
},
movie: function () {
console.log("我会演电影");
},
};
var ldh = new Star("刘德华", 18);
var zxy = new Star("张学友", 19);
console.log(Star.prototype);
console.log(ldh.__proto__);
console.log(Star.prototype.constructor);
console.log(ldh.__proto__.constructor);
5.三角关系
构造函数、实例对象和原型对象的三角关系。
1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
![image-20220211233055299](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211233055299.png)
原型链与对象成员的查找规则
1.原型链⭐️⭐️⭐️
任何原型对象也是一个对象,该对象就有 proto 属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;
// 1. 只要是对象就有__proto__ 原型, 指向上一个原型对象
console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype);
// 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Object.prototype.__proto__); // null
// 3. 我们Object.prototype原型对象里面的__proto__原型 指向为 null
![image-20220211234030653](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220211234030653.png)
面试题
function Animal(name) {
this.name = name;
}
let dog = new Animal();
console.log(dog.prototype); // undefined
console.log(dog.__proto__ === Animal.prototype); //true
console.log(Animal.prototype.__proto__ === Object.prototype); //true
console.log(Object.prototype.__proto__); // null
/*
A.__proto__===B.prototype
其中 A 是 B 的实例对象
*/
面试题⭐️⭐️⭐️
![image-20220212145102891](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212145102891.png)
![image-20220212145127379](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212145127379.png)
2.对象成员的查找机制
1.当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
2.如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
3.如果还没有就查找:原型对象的原型(Object的原型对象)。
4.依此类推一直找到 Object 为止(null)。
原型对象的应用
1.原型对象函数中 this 指向
不管构造函数中的 this,还是原型对象中的 this,都指向我们new 出来的实例对象。
原型对象里面放的是方法, 这个方法里面的 this 指向的是 这个方法的调用者, 也就是这个实例对象
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function () {
console.log("我会唱歌");
that = this;
};
var ldh = new Star("刘德华", 18);
// 1. 在构造函数中,里面this指向的是对象实例 ldh
ldh.sing(); // 调用方法
console.log(that === ldh); // true
// 2.原型对象函数里面的this 指向的是 实例对象 ldh
2.通过原型为数组扩展内置方法
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
// 系统中内置了很多构造函数:例如:Object、Array
// Array构造函数也有一个对应的原型对象
Array.prototype.sum = function () {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
// 数组的实例对象 arr
// arr 是Array的实例
var arr = [1, 2, 3];
// 调用sum方法(成员的查找规则)
console.log(arr.sum());
console.log(Array.prototype);
// 数组的实例对象 arr1
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());
继承
1.call()方法
作用 : 调用这个函数, 并且修改函数运行时的 this 指向 。
语法
fun.call(thisArg, arg1, arg2, ...)
代码实例
function fn(x, y) {
console.log("你好");
console.log(this); // this 由window 指向o
console.log(x + y);
}
var o = {
name: "andy",
age: 19,
};
// call 调用函数 , 改变this指向,传递参数
fn.call(o, 1, 2);
2.继承属性(面试)⭐️⭐️
ES6 之前通过构造函数来实现继承
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例 ldh
// 3.使用call方式实现子继承父的属性【调用父构造函数,并让其this变为ldh】
Father.call(this, uname, age);
this.score = score;
}
var ldh = new Son("刘德华", 18, 100);
console.log(son);
3.继承方法(面试)⭐️⭐️
借用原型对象继承方法!
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:
核心思路:将父构造函数的实例对象赋值给子构造函数的原型对象
① 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
② 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
③ 将子类的 constructor 从新指向子类的构造函数
- 先定义一个父构造函数
- 再定义一个子构造函数
- 修改子构造函数的原型对象(new 父构造函数得到的对象)
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化,会导致父亲和儿子都可以有这个方法
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function () {
console.log("孩子要考试");
};
var son = new Son("刘德华", 18, 100);
console.log(son);
![image-20220212225406206](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212225406206.png)
类的本质
![image-20220212163831677](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212163831677.png)
![image-20220212164158348](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220212164158348.png)
<script>
// ES6 之前通过 构造函数 + 原型 实现面向对象编程
// (1)构造函数有原型对象prototype
// (2)构造函数原型对象prototype里面有constructor 指向构造函数本身
// (3)构造函数可以通过原型对象添加方法
// (4)构造函数创建的实例对象有__proto__原型指向 构造函数的原型对象
// ES6 通过 类 实现面向对象编程
class Star {
}
console.log(typeof Star);
// 1. 类的本质其实还是一个函数,我们也可以简单的认为 类就是 构造函数的另外一种写法
// (1)类有原型对象prototype
console.log(Star.prototype);
// (2)类 也有原型对象,这个原型对象指向类本身
console.log(Star.prototype.constructor);
// (3)类 可以通过原型对象添加方法
Star.prototype.sing = function() {
console.log('冰雨');
};
// (4)类创建的实例对象,有__proto__原型,指向类的原型对象
var ldh = new Star();
console.dir(ldh);
console.log(ldh.__proto__ === Star.prototype);
</script>
ES5 新增方法
1.数组方法 forEach 遍历数组
- 作用:遍历数组
arr.forEach(function (value, index, array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
});
//相当于数组遍历的 for循环 没有返回值
代码示例
var arr = [1, 2, 3];
var sum = 0;
arr.forEach((value, index, Array) => {
console.log("每个元素是" + value);
console.log("每个元素的索引是" + index);
console.log("数组本身" + Array);
sum += value;
});
console.log(sum);
2.数组方法 filter 过滤数组
- 作用:过滤数组
- 返回值:符合条件的数组
var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function (value, index, array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value >= 20;
});
console.log(newArr); //[66,88] //返回值是一个新数组
代码示例
// return 不会结束循环,会把符合条件的放到新数组
var arr1 = [11, 33, 12, 44, 66];
// var newArray = arr1.filter((value, index) => value > 20);
var newArray = arr1.filter((value, index) => {
return value > 20;
});
console.log(newArray);
3.数组方法 some
- 作用:查找数组中是否有满足条件的元素
- 返回值:布尔值
- 特点:只要查找到满足条件的一个元素就立马终止循环
some 查找数组中是否有满足条件的元素
var arr = [10, 30, 4];
var flag = arr.some(function(value,index,array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value < 3;
});
console.log(flag);//返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环
代码示例
var arr2 = [2, 34, 5, 545, 11];
var flag = arr2.some((value) => value >= 20);
console.log(flag);
var arr3 = ["red", "green", "blue"];
var flag = arr3.some((value) => value == "green");
console.log(flag);
1. filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
2. some 也是查找满足条件的元素是否存在 返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环
4.筛选商品案例
1.把数据渲染到页面中 (forEach)
2.根据价格显示数据 (filter)
3.根据商品名称显示数据
-
定义数组对象数据
var data = [ { id: 1, pname: "小米", price: 3999, }, { id: 2, pname: "oppo", price: 999, }, { id: 3, pname: "荣耀", price: 1299, }, { id: 4, pname: "华为", price: 1999, }, ];
-
使用 forEach 遍历数据并渲染到页面中
data.forEach((item) => {
var tr = document.createElement("tr");
tr.innerHTML = `<td>${item.id}</td><td>${item.pname}</td><td>${item.price}</td>`;
tbody.appendChild(tr);
});
-
根据价格筛选数据
- 获取到搜索按钮并为其绑定点击事件
search_price.addEventListener("click", function () {});
- 使用 filter 将用户输入的价格信息筛选出来
search_price.addEventListener("click", function () { var newDate = data.filter(function (value) { //start.value是开始区间 //end.value是结束的区间 return value.price >= start.value && value.price <= end.value; }); console.log(newDate); });
-
将筛选出来的数据重新渲染到表格中
- 将渲染数据的逻辑封装到一个函数中
function setDate(mydata) { // 先清空原来tbody 里面的数据 tbody.innerHTML = ""; mydata.forEach(function (value) { var tr = document.createElement("tr"); tr.innerHTML = "<td>" + value.id + "</td><td>" + value.pname + "</td><td>" + value.price + "</td>"; tbody.appendChild(tr); }); }
- 将筛选之后的数据重新渲染
search_price.addEventListener("click", function () { var newDate = data.filter(function (value) { return value.price >= start.value && value.price <= end.value; }); console.log(newDate); // 把筛选完之后的对象渲染到页面中 setDate(newDate); });
-
根据商品名称筛选
-
获取用户输入的商品名称
-
为查询按钮绑定点击事件,将输入的商品名称与这个数据进行筛选
search_pro.addEventListener("click", function () { var arr = []; data.some(function (value) { if (value.pname === product.value) { // console.log(value); arr.push(value); return true; // return 后面必须写true } }); // 把拿到的数据渲染到页面中 setDate(arr); });
-
5.some 和 forEach 区别
- 如果查询数组中唯一的元素, 用 some 方法更合适,在 some 里面 遇到 return true 就是终止遍历 迭代效率更高
- 在 forEach 里面 return 不会终止迭代
6.trim 方法去除字符串两端的空格
作用 : trim 方法去除字符串两侧空格
返回值 : 一个新的字符串
var str = ' hello '
console.log(str.trim()) //hello 去除两端空格
var str1 = ' he l l o '
console.log(str.trim()) //he l l o 去除两端空格
案例 - 输入框
btn.onclick = function () {
var str = input.value.trim(); // trim 去除两端空格
if (str === "") {
alert("内容不能为空");
} else {
div.innerHTML = str;
console.log(str);
console.log(str.length);
}
};
7.获取对象的属性名 Object.keys()(了解)
作用 : Object.keys(对象) 获取到当前对象中的属性名
返回值 : 返回值是一个数组
var obj = {
id: 1,
pname: "小米",
price: 1999,
num: 2000,
};
var arr = Object.keys(obj);
if (arr.length == 0) {
console.log("这是一个空对象");
}
console.log(arr);
arr.forEach((item) => console.log(item));
8.Object.defineProperty
作用 : Object.defineProperty 设置或修改对象中的属性
语法
Object.defineProperty(obj, prop, descriptor);
- obj:必需。目标对象
- prop:必需。需定义或修改的属性的名字
- descriptor:必需。目标属性所拥有的特性
第三个参数配置
Object.defineProperty(obj, prop, descriptor)
- value: 设置属性的值
- writable: 值是否可以重写。true | false
- enumerable: 目标属性是否可以被枚举(遍历)。true | false
- configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false
Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable:true/false,//如果值为false 不允许修改这个属性值
enumerable: false,//enumerable 如果值为false 则不允许遍历
configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
})
// Object.defineProperty() 定义新属性或修改原有的属性
var obj = {
id: 1,
pname: "小米",
price: 1999,
};
// 1. 以前的对象添加和修改属性的方式
// obj.num = 1000;
// obj.price = 99;
// console.log(obj);
// 2. Object.defineProperty(被操作的对象, '属性名', 描述属性特征的对象)
// 【1】 添加新属性
Object.defineProperty(obj, "num", {
value: 1000,
});
console.log(obj);
// 【2】 修改已有的属性的值(value是特征)
Object.defineProperty(obj, "price", {
value: 9.9,
});
console.log(obj);
// 【3】 设置id属性的值,不可修改(值不能重写是特征)
Object.defineProperty(obj, "id", {
// 如果值为false 不允许修改这个属性值 默认值也是false
writable: false,
});
obj.id = 2; // id值没有被修改调
console.log(obj);
// 【4】 设置address属性,不可枚举(遍历)
Object.defineProperty(obj, "address", {
value: "中国山东蓝翔技校xx单元",
// 如果只为false 不允许修改这个属性值 默认值也是false
writable: false,
// enumerable 如果值为false 则不允许遍历, 默认的值是 false
enumerable: false,
// configurable 如果为false 则不允许删除这个属性 不允许在修改第三个参数里面的特性 默认为false
configurable: false,
});
console.log(obj);
console.log(Object.keys(obj)); // 没有获取到address属性名,因为enumerable被设置为了false
// delete obj.address; // 报错,因为configurable被设置为了false
delete obj.pname; // 可以删除
console.log(obj);
// address属性的特征已经被设置为了不可修改,所以下面的修改操作不会成功,报错!
Object.defineProperty(obj, "address", {
value: "中国山东蓝翔技校xx单元",
writable: true,
enumerable: true,
// configurable 如果为false 则不允许删除这个属性 默认为false
configurable: true,
});
console.log(obj.address);
函数的定义和调用
1. 函数的定义方式
-
方式 1 函数声明方式 function 关键字 (命名函数)
function fn() {}
-
方式 2 函数表达式(匿名函数)
var fn = function () {};
-
方式 3 new Function() (了解)
var f = new Function('a', 'b', 'console.log(a + b)'); f(1, 2); var fn = new Function('参数1','参数2'..., '函数体') // 4. 所有函数都是 Function 的实例(对象) console.dir(f); // 5. 函数也属于对象 console.log(f instanceof Object);
- Function 里面参数都必须是字符串格式
- 第三种方式执行效率低,也不方便书写,因此较少使用
- 所有函数都是 Function 的实例(对象)
- 函数也属于对象
![image-20220213104549016](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220213104549016.png)
2. 函数的调用
-
普通函数
-
对象的方法
-
构造函数
-
绑定事件函数
-
定时器函数
-
立即执行函数
/* 1. 普通函数 */
function fn() {
console.log("人生的巅峰");
}
fn();
/* 2. 对象的方法 */
var o = {
sayHi: function () {
console.log("人生的巅峰");
},
};
o.sayHi();
/* 3. 构造函数*/
function Star() {}
new Star();
/* 4. 绑定事件函数*/
btn.onclick = function () {}; // 点击了按钮就可以调用这个函数
/* 5. 定时器函数*/
setInterval(function () {}, 1000);
这个函数是定时器自动1秒钟调用一次(
/* 6. 立即执行函数(自调用函数)*/
function () {
console.log("人生的巅峰");
}
)();
this⭐️⭐️⭐️
1. 函数内部的 this 指向
一般指向我们的调用者.
![image-20220213111648396](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220213111648396.png)
2. 改变函数内部 this 指向的方法
this 的指向问题,常用的有 bind()、call()、apply() 三种方法。
1. call()方法
作用 : 第一个可以调用函数 第二个可以改变函数内的 this 指向
应用场景 : 主要作用可以实现继承
返回值 : 就是函数的返回值,因为它就是调用函数
语法
fun.call(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
function Son(name, age) {
Father.call(this, name, age); // 调用函数 , 改变 this指向 ,传递参数
}
var ldh = new Son("刘德华", 22);
console.log(ldh); // Son {name: '刘德华', age: 22}
2. apply()方法
作用 : 第一个可以调用函数 第二个可以改变函数内的 this 指向
但是他的参数必须是数组(伪数组)
应用场景 : 借助于数学内置对象求数组最大值
返回值 : 就是函数的返回值,因为它就是调用函数
区别
call 第二个参数传递的是单个参数
applay 第二个参数传递的是数组
语法
fun.apply(thisArg, [argsArray]);
- thisArg:在 fun 函数运行时指定的 this 值
- argsArray:传递的值,必须包含在数组里面
var arrMax1 = Math.max.apply(Math, [111, 23, 45, 78, 234, 34]);
console.log(arrMax1); // 234 , 把伪数组转成单个参数
var arr = [175, 23, 67, 89, 24, 65];
var arrMax = Math.max.apply(Math, arr);
console.log(arrMax); // 175
3. bind()方法
作用 : 改变原来函数内部的this 指向 , 不会调用原来的函数
应用场景 : 不调用函数,但是还想改变 this 指向 , 改变定时器内部的 this 指向.
返回值 : 返回的是原函数改变 this 之后产生的新函数
特点
- bind 不会调用原函数
- 返回的是改变 this 之后产生的新函数
语法
fun.bind(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
var o = {
name: "andy",
};
function fn(a, b) {
console.log(this);
console.log(a + b);
}
var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数(返回值)
fn.bind(o, 1, 2)(); // 直接调用
f(); //调用新函数 this指向的是对象o 参数使用逗号隔开
应用场景
定时器复杂数据 , 定时器执行完后按钮 4.找不到
1.循环绑定单个按钮
var btn = document.querySelector("button");
btn.onclick = function () {
this.disabled = true;
setInterval(
function () {
this.disabled = false;
}.bind(this),
2000
);
};
2.循环绑定多个按钮
var btns = document.querySelectorAll("button");
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
this.disabled = true;
setInterval(
function () {
this.disabled = false;
}.bind(this),
2000
);
};
}
3.箭头函数
4. call、apply、bind 三者的异同
-
共同点 : 都可以改变 this 指向
-
不同点:
- call 和 apply 会调用函数, 并且改变函数内部 this 指向.
- call 和 apply 传递的参数不一样,call 传递参数使用逗号隔开,apply 使用数组传递
- bind 不会调用函数, 可以改变函数内部 this 指向.
-
应用场景
- call 经常做继承.
- apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变 this 指向. 比如改变定时器内部的 this 指向.
严格模式
1. 什么是严格模式
ES5 的严格模式 . IE10 以上可以用
1.消除了 Javascript 语法的一些不合理
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.为未来新版本的 Javascript 做好铺垫 , 如保留字 class,enum,export, extends, import, super 不能做变量名
2. 开启严格模式
情况一 :为脚本开启严格模式
(function (){
//在当前的这个自调用函数中有开启严格模式,当前函数之外还是普通模式
"use strict";
var num = 10;
function fn() {}
})();
//或者
<script>
"use strict"; //当前script标签开启了严格模式
</script>
<script>
//当前script标签未开启严格模式
</script>
情况二: 为函数开启严格模式
function fn() {
"use strict";
return "123";
}
//当前fn函数开启了严格模式
3. 严格模式中的变化
变量
- 变量名必须先声明在使用
- 不能随意删除已声明好的变量 delete num
this 指向
- 全局作用中函数中的 this 是 undefined
- 构造函数不加 new 调用 , this 指向的是 undefined 如果给它赋值会报错
函数
- 参数名不能重复
- 函数声明只能在代码块内 , 不能声明在非函数代码块内( if for )
'use strict'
num = 10
console.log(num)//严格模式后使用未声明的变量
--------------------------------------------------------------------------------
var num2 = 1;
delete num2;//严格模式不允许删除变量
--------------------------------------------------------------------------------
function fn() {
console.log(this); // 严格模式下全局作用域中函数中的 this 是 undefined
}
fn();
---------------------------------------------------------------------------------
function Star() {
this.sex = '男';
}
// Star();严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错.
var ldh = new Star();
console.log(ldh.sex);
----------------------------------------------------------------------------------
setTimeout(function() {
console.log(this); //严格模式下,定时器 this 还是指向 window
}, 2000);
---------------------------------------------------------------------------------
// 6. 严格模式下函数里面的参数不允许有重名
function fn(a, a) {
console.log(a + a);
};
fn(1, 2);
----------------------------------------------------------------------------------
if (true) {
function fun() { // 可以在非函数的代码块中,声明函数(严格模式下不可以)
console.log('fun')
}
}
fun()
高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
![image-20220213170239764](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220213170239764.png)
此时 fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
闭包⭐️⭐️⭐️
变量的作用域复习
变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
1. 什么是闭包
**闭包(closure)**指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
作用:延伸变量的作用范围。
闭包 : 表现形式
- 函数嵌套函数
- 内部函数访问外部函数的变量
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 闭包: 我们fun 这个函数作用域 访问了另外一个函数 fn 里面的局部变量 num
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
fn();
2. 闭包的作用
作用:延伸变量的作用范围。
function fn() {
var num = 10;
// function fun() {
// console.log(num);
// }
// return fun;
// 高阶函数 返回值是函数
return function () {
console.log(num);
};
}
var f = fn();
f();
4. 闭包的案例
【1】利用闭包的方式得到当前 li 的索引号 ( 面试)⭐️⭐️
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function (i) {
// 外层函数
lis[i].onclick = function () {
console.log(i); // 内层函数
};
})(i);
}
【2】闭包应用-3 秒钟之后,打印当前 li 元素的内容
for (var i = 0; i < lis.length; i++) {
(function (i) {
// 创建了四个立即执行函数 , 三秒时间一到 同时执行四个函数里的li
setTimeout(function () {
console.log(lis[i].innerHTML);
}, 3000);
})(i);
}
【3】闭包应用-计算打车价格
/*需求分析
打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车
价格
如果有拥堵情况,总价格多收取10块钱拥堵费*/
var car = (function() {
var start = 13; // 起步价 局部变量
var total = 0; // 总价 局部变量
return {
// 正常的总价
price: function(n) {
if (n <= 3) {
total = start;
} else {
total = start + (n ‐ 3) * 5
}
return total;
},
// 拥堵之后的费用
yd: function(flag) {
return flag ? total + 10 : total;
}
}
})();
console.log(car.price(5)); // 23
console.log(car.yd(true)); // 33
console.log(car.price(1)); // 13
console.log(car.yd(false)); // 13
5. 代码思考题(闭包)
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
console.log(object.getNameFunc()())
-----------------------------------------------------------------------------------
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
var that = this;
return function() {
return that.name;
};
}
};
console.log(object.getNameFunc()())
递归⭐️⭐️⭐️
1. 什么是递归
**递归:**如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数
**注意:**递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”(开辟 内存空间太多)错误(stack overflow),所以必须要加退出条件 return。
![image-20220213172945298](https://longfei-01.oss-cn-qingdao.aliyuncs.com/ImageWebApls/img/image-20220213172945298.png)
2. 递归案例(面试)
【1】利用递归求 1~n 的阶乘⭐️
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n) {
if (n == 1) {
//结束条件
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3));
【2】利用递归求斐波那契数列⭐️
// 利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));
【3】利用递归遍历数据
var data = [
{
id: 1,
gname: "家电",
goods: [
{
id: 11,
gname: "冰箱",
goods: [
{
id: 111,
gname: "海尔",
},
{
id: 112,
gname: "美的",
},
],
},
{
id: 12,
gname: "洗衣机",
},
],
},
{
id: 2,
gname: "服饰",
},
];
// 我们想要做输入id号,就可以返回的数据对象
// 1. 利用 forEach 去遍历里面的每一个对象
function getID(json, id) {
var o = {};
json.forEach(function (item) {
// console.log(item); // 2个数组元素
if (item.id == id) {
// console.log(item);
o = item;
return o;
// 2. 我们想要得里层的数据 11 12 可以利用递归函数
// 里面应该有goods这个数组并且数组的长度不为 0
} else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id);
}
});
return o;
}
深拷贝-浅拷贝⭐️⭐️⭐️
- 浅拷贝:只拷贝一层,更深层次复杂类型只拷贝引用(地址)
- 造成的问题:因为拷贝的是对象,所以任意一个对象改变了值,另一个对象就会跟着变。
- 深拷贝:拷贝多层,每一层都会拷贝
- 每一层拷贝的都是值,不是地址,所以互不影响。
- Object.assign(target, …sources) es6 新增方法可以浅拷贝
1. 浅拷贝
<script>
// 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
// for (var k in obj) {
// // k 是属性名 obj[k] 属性值
// o[k] = obj[k];
// }
// console.log(o);
// o.msg.age = 20;
// console.log(obj);
console.log('--------------');
Object.assign(o, obj);
console.log(o);
o.msg.age = 20;
console.log(obj);
</script>
2. 递归深拷贝的前夜
<script>
// 对象的拷贝操作
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18,
son: ['s1', 's2', 's3']
},
color: ['pink', 'red']
};
var o = {};
// 对obj对象做一个遍历
for (var k in obj) {
// 判断,如果属性值为对象,则创建一个新对象,如果未数组,则创建新的数组
var item = obj[k]; // 属性值
if (item instanceof Array) {
o[k] = []; // o对象的属性值要保存一个新数组
// 对数组(属性值)做遍历 ['pink', 'red']
for (var kk in item) {
o[k][kk] = item[kk];
}
} else if (item instanceof Object) {
o[k] = {}; // o对象的属性值要保存一个新对象
// 对对象(属性值)做遍历
for (var kkk in item) {
o[k][kkk] = item[kkk];
}
} else {
o[k] = item;
}
}
console.log(o);
o.msg.age = 30;
console.log(obj);
</script>
3. 递归实现深拷贝
<script>
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['pink', 'red']
};
var o = {};
// 封装函数
function deepCopy(newobj, oldobj) {
for (var k in oldobj) {
// 判断我们的属性值属于那种数据类型
// 1. 获取属性值 oldobj[k]
var item = oldobj[k];
// 2. 判断这个值是否是数组
if (item instanceof Array) {
newobj[k] = [];
deepCopy(newobj[k], item)
} else if (item instanceof Object) {
// 3. 判断这个值是否是对象
newobj[k] = {};
deepCopy(newobj[k], item)
} else {
// 4. 属于简单数据类型
newobj[k] = item;
}
}
}
deepCopy(o, obj);
console.log(o);
var arr = [];
console.log(arr instanceof Object);
o.msg.age = 20;
console.log(obj);
</script>
案例
1、闭包 - 点击 li 打印出索引号(面试)
2、闭包 - 3 秒之后打印 li 内容
3、递归求阶乘(面试)
4、递归求斐波那契数列(面试)
5、递归遍历数据
6、实现浅拷贝&深拷贝(面试)
JS 高级
作用域
目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问
局部作用域
局部作用域分为函数作用域和块作用域。
函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
function getSum() {
// 函数内部是函数作用域属于局部变量
const num = 10;
}
console.log(num); // 此处报错函数外部不能使用局部作用域变量
总结:
-
函数内部声明的变量,在函数外部无法被访问
-
函数的参数也是函数内部的局部变量
-
不同函数内部声明的变量无法互相访问
-
函数执行完毕后,函数内部的变量实际被清空了
-
块作用域:
在 JavaScript 中使用 { } 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
// var i = 1; var 没块级作用域
for (let i = 1; i <= 3; i++) {
// 块作用域
console.log(i);
}
console.log(i); // 报错 超出了 i 的作用域
总结:
- let 声明的变量会产生块作用域,var 不会产生块作用域
- const 声明的常量也会产生块作用域
- 不同代码块之间的变量无法互相访问
- 推荐使用 let 或 const
全局作用域
<script>
标签 和 .js 文件
的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问
//全局作用域下声明了num变量
const num = 10;
function fn() {
//函数内部可以使用全局作用域的变量
console.log(num);
}
//此处全局作用域
注意:
- 为 window 对象动态添加的属性默认也是全局的,不推荐!
- 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
作用域链
作用域链本质上是底层的变量查找机制。
-
在函数被执行时,会优先查找当前函数作用域中查找变量
-
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
子作用域能够访问父作用域,父级作用域无法访问子级作用域
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
<script>
// 全局作用域
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();
</script>
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
JS 垃圾回收机制
学习目的: 为了闭包做铺垫
什么是垃圾回收机制
JS 中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
不再用到的内存,没有及时释放,就叫做内存泄漏(内存浪费)
内存的生明周期
-
内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
-
内存使用:即读写内存,也就是使用变量、函数等
-
内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
说明:
- 全局变量一般不会回收(关闭页面回收);
- 一般情况下局部变量的值, 不用了, 会被自动回收掉
//为变量分配内存
const i = 11;
const str = "pink老师";
//为对象分配内存
const person = {
age: 18,
uname: "pink老师",
};
//为函数分配内存
function sum(a, b) {
return a + b;
}
垃圾回收的算法说明
所谓垃圾回收, 核心思想就是如何判断内存是否已经不再会被使用了, 如果是, 就视为垃圾, 释放掉
- 引用计数
IE 采用的引用计数算法, 定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。
算法:
- 跟踪记录每个值被引用的次数。
- 如果这个值的被引用了一次,那么就记录次数 1
- 多次引用会累加。
- 如果减少一个引用就减 1。
- 如果引用次数是 0 ,则释放内存。
但它却存在一个致命的问题:嵌套引用。
如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露
因为他们的引用次数永远不会是 0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露
- 标记清除法
现代的浏览器已经不再使用引用计数算法了。
核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在 JS 中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进 行回收。
跟部访问不到 , 自动清除
闭包
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解:闭包 = 内层函数 + 外层函数的变量 (内层函数访问外层函数的变量)
闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
闭包的基本格式:
- 常见的闭包的形式
// 常见的闭包的写法1 外部可以访问使用 函数内部的变量
function outer() {
let a = 100;
function fn() {
console.log(a);
}
return fn;
}
// outer() === fn === function fn() {}
// const fun = function fn() { }
const fun = outer();
fun(); // 调用函数 100
// 常见的写法2
function outer() {
let a = 100;
return function () {
console.log(a);
};
}
const fun = outer();
fun(); // 调用函数 100
// 常见常见的写法3
function outer() {
let a = 100;
return function () {
return a;
};
}
// console.log(outer())
const fun1 = outer();
// 调用函数 100
console.log(fun1());
闭包的应用
闭包应用:实现数据的私有( 不能被随意修改 )
比如,我们要做个统计函数调用次数,函数调用一次,就++
闭包可能引起的问题(内存泄漏)
i 使用完应该被回收但没有被回收,所以存在 内存泄漏(占用内存)
// 闭包形式 统计函数调用的次数
function count() {
let i = 0; // i 不会被回收,一直有在用
function fn() {
i++;
console.log(`函数被调用了${i}次`);
}
return fn; // fn 一直在使用
}
const fun = count(); // 全局的不会销毁 一直找fn , fn里面一直调用i
// i 应该被回收但没有被回收,所以存在 内存泄漏
变量提升
- var 声明的变量提升当前作用域当的最前面
- 只提升变量声明 , 不提升变量赋值
函数
知道函数参数默认值、动态参数、剩余参数的使用细节,提升函数应用的灵活度,知道箭头函数的语法及与普通函数的差异。
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
-
会把所有函数声明提升到当前作用域的最前面
-
只提升函数声明,不提升函数调用
fn();
function fn() {
console.log("函数提升");
}
// 函数提升后
function fn() {
console.log("函数提升");
}
fn();
- 函数表达式(匿名函数) 必须先声明和赋值, 后调用 否则 报错
fun();
var fun = function () {
console.log("函数表达式");
};
// 函数表达式提升后
var fun;
fun = function () {
console.log("函数表达式");
};
函数参数
- 动态参数
arguments (动态参数)是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
没有数组的方法 , 箭头函数中不可以使用
function getSum() {
// arguments 动态参数 只存在于 函数里面
// 是伪数组 里面存储的是传递过来的实参
// console.log(arguments) [2,3,4]
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
console.log(sum); //9
}
getSum(2, 3, 4);
getSum(1, 2, 3, 4);
总结:
- arguments 是一个伪数组,只存在于函数中
- arguments 的作用是动态获取函数的实参
- 可以通过 for 循环依次得到传递过来的实参
- 剩余参数
- … 是语法符号,放置于最末函数形参之前,用于获取多余的实参
- 借助 … 获取的剩余实参,是个**真数组 **
- 有数组的方法 , 箭头函数中可以使用 , 只存在函数中
function getSum(...arr) {
console.log(arr); // 使用的时候不需要写 ...
}
getSum(2, 3); // [3]
getSum(1, 2, 3, 4); // [2,3,4]
function getSum(a, ...arr) {
console.log(arr); // 使用的时候不需要写 ...
}
getSum(2, 3); // [3]
getSum(1, 2, 3, 4); // [2,3,4]
展开运算符
不存在函数中 , 数组,对象都可以使用
- (. . .) 展开运算符可以展开数组
- 不会修改原数组
const arr = [1, 2, 3];
// 展开运算符可以展开数组
console.log(arr); // 1 2 3 字符串
典型运用场景: 求数组最大值(最小值)、合并数组等
// 求最大值
const arr1 = [1, 2, 3];
// 展开运算符 可以展开数组
// console.log(...arr)
// console.log(Math.max(1, 2, 3))
// ...arr1 === 1,2,3
// 1 求数组最大值
console.log(Math.max(...arr1)); // 3
console.log(Math.min(...arr1)); // 1
// 2. 合并数组
const arr2 = [3, 4, 5];
const arr = [...arr1, ...arr2];
console.log(arr); // [1,2,3,4,5]
- 剩余参数:函数参数使用,得到真数组
- 展开运算符:数组中使用,数组展开
箭头函数⭐️
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this
,箭头函数的语法比函数表达式更简洁
使用场景:箭头函数更适用于那些本来需要匿名函数(函数表达式)
的地方
基本语法
// 匿名函数(函数表达式)
const fn = function () {
console.log(123);
};
// 1. 箭头函数 基本语法
const fn = () => {
console.log(123);
};
fn();
- 箭头函数属于表达式函数,因此
不存在函数提升
- 箭头函数只有一个参数时可以
省略圆括号
() - 箭头函数函数体只有一行代码时可以
省略花括号
{},和省略 return
- 箭头函数可以
直接返回一个对象
,加括号
// 2. 只有一个形参的时候,可以省略小括号
const fn = x => {
console.log(x)
}
fn(1)
// // 3. 只有一行代码的时候,我们可以省略大括号
const fn = x => console.log(x)
fn(1)
// 4. 只有一行代码的时候,可以省略return
const fn = x => x + x
console.log(fn(1))
// 5. 箭头函数可以直接返回一个对象,加括号
const fn = (uname) => ({ uname: uname })
console.log(fn('刘德华')) // {uname:'刘德华'}
箭头函数参数
- 普通函数有arguments 动态参数
- 箭头函数没有 arguments 动态参数,但是有 剩余参数 …args
// 1. 利用箭头函数来求和
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
}
const result = getSum(2, 3, 4)
console.log(result) // 9
箭头函数this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
// 1.以前this的指向: 谁调用的这个函数,this 就指向谁
console.log(this); // 指向 window
// 普通函数
function fn() {
console.log(this); // 指向 window
}
window.fn();
// 事件回调中 this 指向 DOM 对象 btn
btn.addEventListener("click", function () {
console.log(this); // 当前 this 指向 btn
});
// 对象方法里面的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 window.obj
},
};
obj.sayHi();
const obj = {
uname: "pink老师",
sayHi: function () {
console.log(this); // 指向 obj
let i = 10;
const count = () => {
console.log(this); // 指向 obj 与 sayHi中的 this一致
};
count();
},
};
obj.sayHi();
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数
使用箭头函数时,this 为全局的 window
,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数
// 事件回调中 this 指向 DOM 对象 btn
btn.addEventListener("click", function () {
console.log(this); // 当前 this 指向 btn
});
// 箭头函数中 this 指向 window
btn.addEventListener("click", () => {
console.log(this); // 当前 this 指向它的上一级 window
});
解构赋值⭐️
数组解构
可以快速把数组元素
批量赋值给一个变量
典型应用交互2个变量
const arr = [100, 60, 80];
// 数组解构 赋值
const [max, min, avg] = arr;
console.log(max, min, avg); // 100 60 80
// 基本语法:典型应用交互2个变量
let a = 1;
let b = 2;
[b, a] = [a, b];
console.log(a, b); // 2,1
js 前面
必须加分号
情况的两种情况
-
立即执行函数
-
数组解构
数组解构
// 1. 立即执行函数要加
(function () {})();
(function () {})();
// 2. 使用数组的时候
const arr = [1, 2, 3];
[1, 2, 3].map(function (item) {
console.log(item);
});
// 不加分号会把上下连起来解析
let a = 1;
let b = 2
;[b, a] = [a, b];
console.log(a, b);
- demo
const pc = ['海尔', '联想', '小米', '方正']
const [hr, lx, mi, fz] = ['海尔', '联想', '小米', '方正']
console.log(hr)
console.log(lx)
console.log(mi)
console.log(fz)
// 请将最大值和最小值函数返回值解构 max 和min 两个变量
function getValue() {
return [100, 60]
}
const [max, min] = getValue()
console.log(max)
- 细节
- 变量多, 单元值少 ,多余的变量将被赋值为
undefined
- 变量少, 单元值多 正常解析
- 变量少, 单元值多, 用
剩余参数
来接收 置于最末位 - 单元值为
undefined
时传入初始参数 - 用逗号隔开 , 按需导入赋值
支持多维数组解构
// 1. 变量多, 单元值少 , 多余的变量将被赋值为 undefined
const [a, b, c, d] = [1, 2, 3]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
console.log(d) // undefined
// 2. 变量少, 单元值多
const [a, b] = [1, 2, 3]
console.log(a) // 1
console.log(b) // 2
// 3. 剩余参数 变量少, 单元值多用剩余参数来接收 置于最末位
const [a, b, ...c] = [1, 2, 3, 4]
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3, 4] 真数组
// 4. 防止 undefined 传递 默认值
const [a = 0, b = 0] = [1, 2]
const [a = 0, b = 0] = []
console.log(a) // 1 0
console.log(b) // 2 0
// 5. 按需导入赋值
const [a, b, , d] = [1, 2, 3, 4]
console.log(a) // 1
console.log(b) // 2
console.log(d) // 4
const arr = [1, 2, [3, 4]]
console.log(arr[0]) // 1
console.log(arr[1]) // 2
console.log(arr[2]) // [3,4]
console.log(arr[2][0]) // 3
// 6.多维数组解构
const arr = [1, 2, [3, 4]]
const [a, b, c] = [1, 2, [3, 4]]
console.log(a) // 1
console.log(b) // 2
console.log(c) // [3,4]
const [a, b, [c, d]] = [1, 2, [3, 4]]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
console.log(d) // 4
对象解构
可以快速把对象的
属性和方法
批量赋值给一个变量
// 对象解构
const obj = {
uname: 'pink老师',
age: 18
}
const { uname, age } = {age: 18, uname: 'pink老师' }
// 等价于 const uname = obj.uname
// 要求属性名和变量名必须一直才可以
// 1. 对象解构的变量名 可以重新改名 旧变量名: 新变量名
const { uname: username, age } = { uname: "pink老师", age: 18 };
注意
- 对象属性名必须和变量名
相同
(对象是无序的 , 名字一样顺序可以不一样) - 解构的变量名不要和外面的变量名
冲突
否则报错 - 对象中找不到与变量名
相同
的属性时变量值为undefined
- 重新修改对象解构的变量名
旧变量名: 新变量名
// 1. 对象解构的变量名 可以重新改名 旧变量名: 新变量名
const { uname: username, age } = { uname: "pink老师", age: 18 };
解构数组对象
const obj = [
{
uname: "佩奇",
age: 6,
},
];
const [{ uname, age }] = obj;
console.log(uname, age); //佩奇 6
// 修改对象解构的变量名
const [{ uname: username, age }] = obj;
console.log(username, age); //佩奇 6
- demo
// 1.对象解构
const pig = { name: "佩奇", age: 6 };
const { name, age } = pig; // 佩奇 6
console.log(name, age);
// 2.对象解构修改变量名
const { name: uname, age } = pig;
console.log(uname, age); // 佩奇 6
// 3.数组对象解构
const goods = [
{
goodsName: "小米",
price: 1999,
},
];
const [{ goodsName, price }] = goods;
console.log(goodsName, price); // 小米 1999
支持多级对象解构
const pig = {
name: "佩奇",
family: {
mother: "猪妈妈",
father: "猪爸爸",
sister: "乔治",
},
age: 6,
};
// 多级对象解构
const {name,family:{mother,father,sister}}=pig
console.log(name); // 佩奇
console.log(mother); // 猪妈妈
console.log(father); // 猪爸爸
console.log(sister); // 乔治
const person = [
{
name: "佩奇",
family: {
mother: "猪妈妈",
father: "猪爸爸",
sister: "乔治",
},
age: 6,
},
];
// 多级数组对象解构
const [{name,family:{mother, father, sister}}]=person
console.log(name); // 佩奇
console.log(mother); // 猪妈妈
console.log(father); // 猪爸爸
console.log(sister); // 乔治
ajax
从后台请求过来的数据格式是JSON 对象
传值的时候需要data
就顺便解构data
- demo
// 1. 这是后台传递过来的数据
const msg = {
code: 200,
msg: "获取新闻列表成功",
data: [
{
id: 1,
title: "5G商用自己,三大运用商收入下降",
count: 58,
},
{
id: 2,
title: "国际媒体头条速览",
count: 56,
},
{
id: 3,
title: "乌克兰和俄罗斯持续冲突",
count: 1669,
},
],
};
// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
const { data } = msg;
console.log(data); // [{…}, {…}, {…}]
// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// msg 虽然很多属性,但是我们利用解构只要 data值
function render(obj) {
const { data } = obj;
console.log(data); // [{…}, {…}, {…}]
}
render(msg);
// 传值的时候需要data就顺便解构data
function render({ data }) {
console.log(data); // [{…}, {…}, {…}]
}
render(msg);
// 需求3,为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
// {data:res} 解构出来data,并且重名为res(这对应服务器返回来的数据)
function render({ data: res }) {
console.log(res); // [{…}, {…}, {…}]
}
render(msg);
axios({
url: "http://www.liulongbin.top:3009/api/news",
method: "get",
}).then(({ data: { code, msg, data } }) => {
// 对data服务器响应回来的数据进行深层解构
console.log(code);
console.log(msg);
console.log(data);
});
// 为了避免变量名冲突,解构时需要改名
const { house: { name, price }, car: { name: carName, price: carPrice } } = obj
console.log(name, price, carName, carPrice)
forEach()方法
forEach 就是遍历 加强版的for循环 , 只遍历不返回 .适合于
遍历数组对象
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ["red", "green", "pink"];
arr.forEach(function (item, index) {
console.log(item, index);
});
- 渲染商品案例
forEach() 遍历数组生成 div
// 1. 声明一个字符串变量
let str = ''
// 2. 遍历数据
goodsList.forEach(item => {
// console.log(item) // 可以得到每一个数组元素 对象 {id: '4001172'}
// const {id} = item 对象解构
// 对象解构
const { name, price, picture } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
// 3.生成的 字符串 添加给 list
document.querySelector('.list').innerHTML = str
filter()方法
筛选
符合条件的元素 , 返回筛选之后元素的新数组
, 不会影响原数组
只能写 > <
号 , 比较运算符
const arr = [10, 20, 30];
const newArr = arr.filter(function (item, index) {
// console.log(item)
// console.log(index)
return item >= 20
})
// 返回的符合条件的新数组
// 箭头函数
const newArr = arr.filter((item) => item >= 20);
console.log(newArr);
- 商品价格筛选
forEach() 遍历数组生成 div
filter() 过滤生成复合条件的新数组 , 返回到渲染函数的值
数组解构
// 1. 渲染函数 封装
function render(arr) {
// 声明空字符串
let str = ''
// 遍历数组
arr.forEach(item => {
// 解构
const { name, picture, price } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
// 追加给list
document.querySelector('.list').innerHTML = str
}
render(goodsList) // 页面一打开就需要渲染
// 2. 过滤筛选
document.querySelector('.filter').addEventListener('click', e => {
console.log(e.target);
// e.target.dataset.index e.target.tagName
const { tagName, dataset } = e.target
// 判断
if (tagName === 'A') {
// console.log(11)
// arr 返回的新数组
let arr = goodsList // 给一个默认值
if (dataset.index === '1') {
arr = goodsList.filter(item => item.price > 0 && item.price <= 100)
} else if (dataset.index === '2') {
arr = goodsList.filter(item => item.price >= 100 && item.price <= 300)
} else if (dataset.index === '3') {
arr = goodsList.filter(item => item.price >= 300)
}
// 渲染函数
render(arr)
}
})
深入对象
了解面向对象的基础概念,能够利用构造函数创建对象。
创建对象三种方式
- 利用对象字面量创建对象
const o = {
name: "阿飞",
};
console.log(o); // {name: '阿飞'}
- 利用 new Object 创建对象
const obj = new Object()
obj.uname = 'pink老师'
console.log(obj) // {uname: 'pink老师'}
const obj = new Object({ uname: "pink" });
console.log(obj); // {uname: 'pink老师'}
- 利用构造函数创建对象
// 创建一个猪 构造函数
function Pig(uname, age) {
this.uname = uname;
this.age = age;
}
// console.log(new Pig('佩奇', 6))
// console.log(new Pig('乔治', 3))
const p = new Pig("佩奇", 6);
const q = new Pig("乔治", 9);
console.log(p); // Pig {uname: '佩奇', age: 6}
console.log(q); // Pig {uname: '乔治', age: 9}
构造函数
构造函数是一个函数
, 是专门用于创建对象
的函数,如果一个函数使用 new
关键字调用,那么这个函数就是构造函数。可以通过构造函数
来快速创建多个类似的对象
注意
- 它们的命名以
大写
字母开头。 - 它们只能由
"new"
操作符来执行。
// 创建一个猪 构造函数
function Pig(uname, age) {
this.uname = uname;
this.age = age;
}
// console.log(new Pig('佩奇', 6))
// console.log(new Pig('乔治', 3))
const p = new Pig("佩奇", 6);
const q = new Pig("乔治", 9);
console.log(p); // Pig {uname: '佩奇', age: 6}
console.log(q); // Pig {uname: '乔治', age: 9}
说明
- 使用
new
关键字调用函数的行为被称为实例化
- 构造函数内部
无需写return
,返回值即为新创建的对象
- 构造函数内部的 return
返回的值无效
,所以不要写return - new Object() new Date() 也是实例化构造函数
- demo
function Goods(name, price, count) {
this.name = name;
this.price = price;
this.count = count;
}
const xm = new Goods("小米", 1999, 20);
const hw = new Goods("华为", 3999, 10);
const vv = new Goods("音乐", 2999, 30);
console.log(xm); // Goods {name: '小米', price: 1999, count: 20}
console.log(hw); // Goods {name: '华为', price: 3999, count: 10}
console.log(vv); // Goods {name: '音乐', price: 2999, count: 30}
实例化执行过程(new)
- 创建一个新空的对象
- 构造函数this指向新空对象
- 执行构造函数代码 , 修改this 添加新的属性
- 返回新的对象
实例成员
通过
构造函数创建
的对象称为实例对象
,实例对象中的属性和方法称为实例成员
。
-
实例对象的属性和方法即为实例成员
-
为构造函数传入参数,动态创建结构相同但值不同的对象
-
构造函数创建的实例对象彼此独立互不影响。
静态成员
构造函数
的属性和方法被称为静态成员
- 构造函数的属性和方法被称为静态成员
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的 this 指向构造函数本身(指向Person)
内置构造函数
•Object
• Array
• String
• Number
在 JavaScript 中最主要的数据类型有 6 种:
- 基本数据类型:
字符串、数值、布尔、undefined、null
- 引用类型:
对象
Object (数组 , 函数都属于对象)
- 其实字符串、数值、布尔、等基本类型也都有专门的构造函数,这些我们称为包装类型。
- JS中几乎所有的数据都可以基于构成函数创建。
// const str = 'pink' 简单数组类型有属性
console.log(str.length);
const num = 12; // 简单数组类型有方法
console.log(num.toFixed(2));
// const str = 'pink'
//! js 底层完成, 把简单数据类型包装为了引用数据类型
// const str = new String('pink')
1. 引用类型
Object
Object 是内置的构造函数,用于创建普通对象。
推荐使用字面量方式声明对象,而不是 Object 构造函数
const obj = new Object({ uname: "pink" });
三个常用静态方法(静态方法就是只有构造函数Object可以调用的)
Object.keys
作用:Object.keys静态方法获取对象中所有属性(键)
注意: 返回的是一个数组
const o = { uname: 'pink', age: 18 }
// 1.获得所有的属性名
console.log(Object.keys(o)) //返回数组['uname', 'age']
Object.values
Object.values 静态方法获取对象中所有属性值
注意: 返回的是一个数组
// 2. 获得所有的属性值
console.log(Object.values(o)) // ['pink', 18]
Object. assign
作用:Object. assign 静态方法常用于对象拷贝
使用:经常使用的场景给对象添加属性
const o = { uname: "pink", age: 18 };
const obj = {};
// 拷贝对象把 o 烤给 obj
Object.assign(obj, o);
console.log(obj); // { uname: "pink", age: 18 }
const o = { uname: "pink", age: 18 };
// 给 o 新增属性
Object.assign(o, { gender: "女" });
console.log(o); // {uname: 'pink', age: 18, gender: '女'}
Array
Array 是内置的构造函数,用于创建数组
const arr = new Array(3, 5);
console.log(arr);
- reduce 返回函数累计处理的结果,经常用于
求和
等 - arr.reduce(function(){},起始值) arr.reduce(function(累计值,单前元素[,元素索引][,源数组]){},起始值)
起始值可以省略
, 如果写就作为第一次累计的起始值
累计值参数:
-
如果有起始值,则以
起始值为准
开始累计, 累计值 = 起始值 -
如果没有起始值, 则累计值以
数组的第一个数组元素
作为起始值开始累计 -
后面每次遍历就会用后面的数组元素 累计到
累计值
里面 (类似求和里面的 sum ) -
带起始值的比不带起始值的少循环一次
const arr = [1, 2, 3];
// arr.reduce(function(累计值, 当前元素){}, 起始值)
arr.reduce(function (prev, item) {
console.log(prev, item);
// 0 1
// 1 2
// 3 3
// 带起始值循环3次
return prev + item;
}, 0);
arr.reduce(function (prev, item) {
console.log(prev, item);
// 1 2
// 3 3
// 不带起始值循环两次
return prev + item;
});
const arr = [1, 2, 3]
const re = arr.reduce((prev, item) => prev + item)
console.log(re)
方法 | 作用 | 说明 |
---|---|---|
forEach | 遍历数组 | 不返回 ,用于不改变值,经常用于查找打印输出值 |
filter | 过滤数组 | 筛选数组元素,并生成新数组 |
map | 迭代数组 | 返回新数组,新数组里面的元素是处理之后的值,经常用于处理数据 |
reduce | 累计器 | 返回函数累计处理的结果,经常用于求和等 |
- demo
const arr = [{
name: '张三',
salary: 10000
}, {
name: '李四',
salary: 10000
}, {
name: '王五',
salary: 20000
},
]
// 涨薪的钱数 10000 * 0.3
// const money = arr.reduce(function (prev, item) {
// return prev + item.salary * 0.3
// 0 10000
// 3000 10000
// 6000 20000
// }, 0)
const re = arr.reduce((prev, item) => prev + item.salary * 0.3, 0);
console.log(re)
find
数组中查找复合条件的对象(第一个元素的值)
, 并返回这个对象
every
每一个是否都符合
条件,如果都符合返回 true
,否则返回false
const arr = [
{
name: "小米",
price: 1999,
},
{
name: "华为",
price: 3999,
},
{
name: "锤子",
price: 4999,
},
];
// find 找华为 这个对象,并且返回这个对象
const hua = arr.find((item) => item.name === "华为");
console.log(hua); // {name: '华为', price: 3999}
// every 每一个是否都符合条件,如果都符合返回 true ,否则返回false
const re1 = arr.some((item) => item.price > 3000);
console.log(re1);
数组常见方法
- 其他方法
- demo
const spec = { size: '40cm*40cm', color: '黑色' }
//1. 所有的属性值回去过来 数组
// console.log(Object.values(spec))
// 2. 转换为字符串 数组join('/') 把数组根据分隔符转换为字符串
// console.log(Object.values(spec).join('/'))
document.querySelector('div').innerHTML = Object.values(spec).join('/')
//第一种方法更简单,万一对象里面有多个属性,第二种方法就不方便了
document.querySelector("div").innerHTML = spec.size + "/" + spec.color; // (属性多了不方便,拼接次数多)
数组常见方法
- 伪数组转换为真
数组
静态方法 Array.from()
// Array.from(lis) 把伪数组转换为真数组
const lis = document.querySelectorAll("ul li");
// console.log(lis)
// lis.pop() 报错
const liss = Array.from(lis);
liss.pop();
console.log(liss); //
2. 包装类型
String
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法
之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”
来的,被称为包装类型。
- demo
//1. split 把字符串 转换为 数组 和 join() 相反
const str = 'pink,red'
const arr = str.split(',')
console.log(arr)
const str1 = '2022-4-8'
const arr1 = str1.split('-')
console.log(arr1)
// 2. 字符串的截取 substring(开始的索引号[, 结束的索引号])
// 2.1 如果省略 结束的索引号,默认取到最后
// 2.2 结束的索引号不包含想要截取的部分
const str = '今天又要做核酸了'
console.log(str.substring(5, 7))
// 3. startsWith 判断是不是以某个字符开头
const str = 'pink老师上课中'
console.log(str.startsWith('pink'))
// 4. includes 判断某个字符是不是包含在一个字符串里面
const str = '我是pink老师'
console.log(str.includes('pink')) // true
- demo
思路:
①:把字符串拆分为数组,这样两个赠品就拆分开了 用那个方法? split(‘,’)
②:利用map
遍历数组,同时把数组元素生成到span里面,并且返回
③:因为返回的是数组,所以需要 转换为字符串, 用那个方法? join(‘’)
④:p的innerHTML 存放刚才的返回值
const gift = '50g的茶叶,清洗球'
// 1. 把字符串拆分为数组
// console.log(gift.split(',')) [,]
// 2. 根据数组元素的个数,生成 对应 span标签
// const str = gift.split(',').map(function (item) {
// return `<span>【赠品】 ${item}</span> <br>`
// }).join('')
// // console.log(str)
// document.querySelector('div').innerHTML = str
document.querySelector('div').innerHTML = gift.split(',').map(item => `<span>【赠品】 ${item}</span> <br>`).join('')
Number
toFixed() 设置
保留小数
位的长度 (商品价格
)
// toFixed 方法可以让数字指定保留的小数位数
const num = 10.923
// console.log(num.toFixed())
console.log(num.toFixed(1)) // 10.9
const num1 = 10
console.log(num1.toFixed(2)) // 10.00
综合案例
const goodsList = [
{
id: "4001649",
name: "大师监制龙泉青瓷茶叶罐",
price: 139,
picture: "https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png",
count: 1,
spec: { size: "小号", color: "紫色" },
gift: "50g茶叶,清洗球,宝马, 奔驰",
},
];
// 1. 根据数据渲染页面
document.querySelector('.list').innerHTML = goodsList.map(item => {
// console.log(item) // 每一条对象
// 对象解构 item.price item.count
const { picture, name, count, price, spec, gift } = item
// 规格文字模块处理
const text = Object.values(spec).join('/')
// 计算小计模块 单价 * 数量 保留两位小数
// 注意精度问题,因为保留两位小数,所以乘以 100 最后除以100
const subTotal = ((price * 100 * count) / 100).toFixed(2)
// 处理赠品模块 '50g茶叶,清洗球'
const str = gift ? gift.split(',').map(item => `<span class="tag">【赠品】${item}</span> `).join('') : ''
return `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name} ${str} </p>
<p class="spec">${text} </p>
<p class="price">${price.toFixed(2)}</p>
<p class="count">x${count}</p>
<p class="sub-total">${subTotal}</p>
</div>
`
}).join('')
// 3. 合计模块
const total = goodsList.reduce((prev, item) => prev + (item.price * 100 * item.count) / 100, 0)
// console.log(total)
document.querySelector('.amount').innerHTML = total.toFixed(2)
面向对象
编程思想
面向过程
就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
面向对象
是把事务分解成为一个个对象,然后由对象之间分工与合作。
面向对象编程具有灵活
、代码可复用
、容易维护和开发的优点
,更适合多人合作的大型软件项目。
面向对象的特性
:
- 封装性
- 继承性
- 多态性
面向过程和面向对象的对比
构造函数
- Js 实现
面向对象
需要借助于谁来实现?
构造函数
- 构造函数存在什么问题?
浪费内存
(单独开辟了内存)
原型
每一个构造函数都有一个 prototype
属性,指向另一个对象,所以我们也称为原型对象
可以解决构造函数浪费内存问题
- 公共的
属性
写到构造函数里面
- 公共的
方法
写到原型对象身上
节约了内存
// 构造函数 公共的属性和方法 封装到 Star 构造函数里面了
// 1.公共的属性写到 构造函数里面
function Star(uname, age) {
this.uname = uname
this.age = age
// this.sing = function () {
// console.log('唱歌')
// }
}
// 2. 公共的方法写到原型对象身上 节约了内存
Star.prototype.sing = function () {
console.log('唱歌')
}
const ldh = new Star('刘德华', 55)
const zxy = new Star('张学友', 58)
ldh.sing() //调用
zxy.sing() //调用
console.log(ldh.sing === zxy.sing) // true
// console.dir(Star.prototype)
-
原型是什么 ?
一个对象,我们也称为prototype 为原型对象
-
原型的作用是什么 ?
共享方法
可以把那些不变的方法,直接定义在 prototype 对象上
-
构造函数和原型里面的this指向谁?
实例化的对象
let that
function Star(uname) {
// that = this
// console.log(this)
this.uname = uname
}
// 原型对象里面的函数this指向的还是 实例对象 ldh
Star.prototype.sing = function () {
that = this
console.log('唱歌')
}
// 实例对象 ldh
// 构造函数里面的 this 就是 实例对象 ldh
const ldh = new Star('刘德华')
ldh.sing()
console.log(that === ldh) //true
- demo 数组扩展方法
// 自己定义 数组扩展方法 求和 和 最大值
// 1. 我们定义的这个方法,任何一个数组实例对象都可以使用
// 2. 自定义的方法写到 数组.prototype 身上
// 1. 最大值
const arr = [1, 2, 3]
Array.prototype.max = function () {
console.log(this); // 实例对象 [1,2,3]
// 展开运算符
return Math.max(...this)
// 原型函数里面的this 指向谁? 实例对象 arr
}
// 2. 最小值
Array.prototype.min = function () {
// 展开运算符
return Math.min(...this)
// 原型函数里面的this 指向谁? 实例对象 arr
}
console.log(arr.max())
console.log([2, 5, 9].max())
console.log(arr.min())
// const arr = new Array(1, 2)
// console.log(arr)
// 3. 求和 方法
Array.prototype.sum = function () {
return this.reduce((prev, item) => prev + item, 0)
}
console.log([1, 2, 3].sum())
console.log([11, 21, 31].sum())
constructor 属性
每个原型对象
里面都有个constructor
属性(constructor 构造函数)
作用
: 该属性指向
该原型对象的构造函数
, 简单理解,就是指向我的爸爸,我是有爸爸的孩子
const ldh = new Star()xx
console.log(Star.prototype.constructor === Star) // true
修改后的原型对象中,添加一个 constructor 指向原来的构造函数
function Star() {
}
// console.log(Star.prototype)
Star.prototype = {
// 从新指回创造这个原型对象的 构造函数 ƒ Star() {}
constructor: Star,
sing: function () {
console.log('唱歌')
},
dance: function () {
console.log('跳舞')
},
}
console.log(Star.prototype)
对象原型
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
注意:
proto 是JS非标准属性
[[prototype]]和__proto__意义相同
用来表明当前实例对象指向哪个原型对象prototype
__proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
原型继承
如果我们给男人添加了一个吸烟的方法,发现女人自动也添加这个方法
构造函数 new 出来的对象 结构一样,但是对象不一样
思路
真正做这个案例,我们的思路应该是先考虑大的,后考虑小的
- 人类共有的属性和方法有那些,然后做个构造函数,进行封装,一般公共属性写到构造函数内部,公共方法,挂载到构造函数原型身上。
- 男人继承人类的属性和方法,之后创建自己独有的属性和方法
- 女人同理
// 父构造函数(父类) 子构造函数(子类)
// 子类的原型 = new 父类
Woman.prototype = new Person() // {eyes: 2, head: 1}
原型链
- 只要是对象就有 proto 指向原型对象
- 只要是原型对象里面就有 constructor 指向创造该原型对象 的构造函数
原型链是一个查找规则 , 提供了查找属性和方法的一条路 , 像一条链子连起来的所以叫原型链 ; 怎么查找的呢 , 先从自身的原型对象查找 , 如果没有再从上一层查找 , 如果上一层没有再往上层查找 , 找到最大的那个 , 就是因为有这条线路__proto__的存在才可以使用这些方法
原型链-查找规则
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
function Person() {
}
const ldh = new Person()
// console.log(ldh.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(ldh instanceof Person) // true
console.log(ldh instanceof Object) // true
console.log(ldh instanceof Array) // false
console.log([1, 2, 3] instanceof Array) // true
console.log(Array instanceof Object) // true
- demo 信息提示封装
// 1. 模态框的构造函数
function Modal(title = '', message = '') {
// 公共的属性部分
this.title = title
this.message = message
// 因为盒子是公共的
// 1. 创建 一定不要忘了加 this
this.modalBox = document.createElement('div')
// 2. 添加类名
this.modalBox.className = 'modal'
// 3. 填充内容 更换数据
this.modalBox.innerHTML = `
<div class="header">${this.title} <i>x</i></div>
<div class="body">${this.message}</div>
`
// console.log(this.modalBox)
}
// 2. 打开方法 挂载 到 模态框的构造函数原型身上
Modal.prototype.open = function () {
if (!document.querySelector('.modal')) {
// 把刚才创建的盒子 modalBox 渲染到 页面中 父元素.appendChild(子元素)
document.body.appendChild(this.modalBox)
// 获取 x 调用关闭方法
this.modalBox.querySelector('i').addEventListener('click', () => {
// 箭头函数没有this 上一级作用域的this
// 这个this 指向 m
this.close()
})
}
}
// 3. 关闭方法 挂载 到 模态框的构造函数原型身上
Modal.prototype.close = function () {
document.body.removeChild(this.modalBox)
}
// 4. 按钮点击
document.querySelector('#delete').addEventListener('click', () => {
const m = new Modal('温馨提示', '您没有权限删除')
// 调用 打开方法
m.open()
})
// 5. 按钮点击
document.querySelector('#login').addEventListener('click', () => {
const m = new Modal('友情提示', '您还么有注册账号')
// 调用 打开方法
m.open()
})
深浅拷贝
开发中我们经常需要复制一个对象。如果直接用赋值会有下面问题:(数据类型 堆栈)
浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址(只拷贝外部浅浅的一层)
常见方法:
- 拷贝对象:Object.assgin() / 展开运算符 {…obj} 拷贝对象
- 拷贝数组:Array.prototype.concat() 或者 […arr]
但是会有问题
如果是简单数据类型拷贝值
,引用数据类型拷贝的是地址
(简单理解: 如果是单层对象,没问题,如果有多层就有问题)
深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法(三种实现方法):
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringify()实现
- 通过递归实现深拷贝
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
简单理解:函数内部自己调用自己, 这个函数就是递归函数
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
let i = 1
function fn() {
console.log(`这是第${i}次`)
if (i >= 6) {
return
}
i++
fn()
}
fn()
利用递归函数实现 setTimeout 模拟 setInterval效果
function getTime() {
document.querySelector('div').innerHTML = new Date().toLocaleString()
setTimeout(getTime, 1000)
}
getTime()
- 深拷贝 , 拷贝出来的新对象不会影响原对象 , 要想实现深拷贝要用到函数递归
- 当我们在普通拷贝时直接赋值就可以 , 但是如果遇到数组再次调用递归函数就可以了
- 如果遇到是对象形式 , 再次利用递归把对象解决
- 处理数组的问题一定
先写数组在写对象
不能颠倒
- js库lodash里面cloneDeep内部实现了深拷贝
<script>
const obj = {
uname: "pink",
age: 18,
hobby: ["乒乓球", "足球"],
family: {
baby: "小pink",
},
};
// 语法:_.cLoneDeep(要被克隆的对象)
const o = _.cloneDeep(obj);
o.family.baby='老pink'
console.log(o);
console.log(obj);
- 通过JSON.stringify()实现
const obj = {
uname: "pink",
age: 18,
hobby: ["乒乓球", "足球"],
family: {
baby: "小pink",
},
};
// 把对象转换为 JSON 字符串
// console.log(JSON.stringify(obj))
const o = JSON.parse(JSON.stringify(obj));
o.family.baby='老pink'
console.log(o);
console.log(obj);
异常处理
throw 抛异常
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
function fn(x, y) {
if (!x || !y) {
// throw '没有参数传递进来'
throw new Error('没有参数传递过来')
}
return x + y
}
console.log(fn())
总结
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
try /catch 捕获异常
我们可以通过try / catch 捕获错误信息(浏览器提供的错误信息)
function fn() {
try {
// 可能预估有问题的代码写到这里
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (err) {
console.log(err.message)
// 中断
// return
throw new Error('你是不是把选择器写错了')
} finally {
alert('执行')
}
console.log(111)
}
fn()
debugger
代码调试
处理this
this指向
// 普通函数: 谁调用我,this就指向谁
console.log(this) // window
function fn() {
console.log(this) // window
}
window.fn()
window.setTimeout(function () {
console.log(this) // window
}, 1000)
document.querySelector('button').addEventListener('click', function () {
console.log(this) // 指向 button
})
const obj = {
sayHi: function () {
console.log(this) // 指向 obj
}
}
obj.sayHi()
- 普通函数谁调用 this 的值指向谁 ,没有调用者指向 Window
- 普通函数严格模式下指向 undefined
- 箭头函数没有this 指向
- 函数内不存在this,沿用上一级的,过程:向外层作用域中,一层一层查找this,直到有this的定义
- 不适用 : 构造函数,原型函数,dom事件函数
注意情况1:在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
注意情况2:同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数
改变this
- call()
- apply()
- bind()
call()
作用:
-
调用函数
-
改变函数里面的 this 指向(传递的参数
普通参数
) -
返回值就是函数的返回值
参数 : call 里面第一个参数是 指定this, 其余是实参,传递的参数
整体做个了解,后期用的很少
const obj = {
uname: 'pink'
}
function fn(x, y) {
console.log(this) // window
console.log(x + y)
}
// 1. 调用函数
// 2. 改变 this 指向
fn.call(obj, 1, 2)
apply()
-
调用函数
-
改变函数里面的 this 指向(传递的参数
数组参数
) -
返回值就是函数的返回值
参数 : call 里面第一个参数是 指定this, 第二个是数组参数
本身就是在调用函数,所以返回值就是函数的返回值
因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
const obj = {
age: 18
}
function fn(x, y) {
console.log(this) // {age: 18}
console.log(x + y)
}
// 1. 调用函数
// 2. 改变this指向
// fn.apply(this指向谁, 数组参数)
fn.apply(obj, [1, 2])
// 3. 返回值 本身就是在调用函数,所以返回值就是函数的返回值
使用场景 : 求最大值
// 使用场景: 求数组最大值
// const max = Math.max(1, 2, 3)
// console.log(max)
const arr = [100, 44, 77]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(null, arr)
console.log(max, min)
// 使用场景: 求数组最大值
console.log(Math.max(...arr))
call和apply的区别是?
- 都是调用函数,都能改变this指向
- 参数不一样,apply传递的必须是数组
bind()
-
bind 不会调用函数
-
能改变this指向
-
返回值是个函数,但是这个函数里面的 this是更改过的obj
const obj = {
age: 18
}
function fn() {
console.log(this)
}
// 1. bind 不会调用函数
// 2. 能改变this指向
// 3. 返回值是个函数, 但是这个函数里面的this是更改过的obj
const fun = fn.bind(obj)
// console.log(fun)
fun()
因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向.
// 需求,有一个按钮,点击里面就禁用,2秒钟之后开启
document.querySelector('button').addEventListener('click', function () {
// 禁用按钮
this.disabled = true
window.setTimeout(function () {
// 在这个普通函数里面,我们要this由原来的window 改为 btn
this.disabled = false
}.bind(this), 2000) // 这里的this 和 btn 一样
// }.bind(btn), 2000) // 这里的this 和 btn 一样
})
call apply bind 总结
相同点:
- 都可以改变函数内部的this指向.
区别点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply 传递的参数不一样, call 传递普通参数.形式 apply 必须数组形式
- bind 不会调用函数, 可以改变函数内部this指向.
主要应用场景:
- call 调用函数并且可以传递参数
- apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
节流
节流使用比防抖多
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
开发使用场景 – 小米轮播图点击效果 、 鼠标移动、页面尺寸缩放resize、滚动条滚动 就可以加节流
- 利用节流来处理-鼠标滑过盒子显示文字
核心思路:
利用时间相减:移动后的时间 - 刚开始移动的时间 >= 500ms 我才去执行 mouseMove函数
const box = document.querySelector('.box')
let i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
// 如果里面存在大量操作 dom 的情况,可能会卡顿
}
// console.log(mouseMove)
// 节流函数 throttle
function throttle(fn, t) {
// 起始时间
let startTime = 0
return function () {
// 得到当前的时间
let now = Date.now()
// 判断如果大于等于 500 采取调用函数
if (now - startTime >= t) {
// 调用函数
fn()
// 起始的时间 = 现在的时间 写在调用函数的下面
startTime = now
}
}
}
box.addEventListener('mousemove', throttle(mouseMove, 500))
节流函数怎么写 : 两个时间相减 现在的时间 - 起始的时间 >=多少就去调用这个函数 , 否则不调用
防抖
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
const box = document.querySelector('.box')
let i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
// 如果里面存在大量操作 dom 的情况,可能会卡顿
}
// 防抖函数
function debounce(fn, t) {
let timeId
return function () {
// 如果有定时器就清除
if (timeId) clearTimeout(timeId)
// 开启定时器 200
timeId = setTimeout(function () {
fn()
}, t)
}
}
// box.addEventListener('mousemove', mouseMove)
box.addEventListener('mousemove', debounce(mouseMove, 200))
节流
采取的是两个时间相减 , 如果>=了默认的时间之后再去调用函数防抖
采用定时器的方式 , 在触发了事件后不断清除定时器 , 当事件不在执行了多少毫秒后开启定时器执行里面的函数
节流综合案例
两个事件:
①:ontimeupdate 事件在视频/音频(audio/video)当前的播放位置发送改变时触发
②:onloadeddata 事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频(audio/video)的
下一帧时触发
谁需要节流?
ontimeupdate , 触发频次太高了,我们可以设定 1秒钟触发一次
// 1. 获取元素 要对视频进行操作
const video = document.querySelector('video')
video.ontimeupdate = _.throttle(() => {
// console.log(video.currentTime) 获得当前的视频时间
// 把当前的时间存储到本地存储
localStorage.setItem('currentTime', video.currentTime)
}, 1000)
// 打开页面触发事件,就从本地存储里面取出记录的时间, 赋值给 video.currentTime
video.onloadeddata = () => {
// console.log(111)
video.currentTime = localStorage.getItem('currentTime') || 0
}