# JS-高级程序设计(红宝书)
# JavaScript 高级
# 1.1 两大编程思想
- 面向过程
- 面向对象
# 1.2 面向过程编程 POP(Process-oriented programming)
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
举个栗子:将大象装进冰箱,面向过程做法。
面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
# 1.3 面向对象编程 OOP (Object Oriented Programming)
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作
举个例子:将大象装进冰箱,面向对象做法。
先找出对象,并写出这些对象的功能:
大象对象
进去
冰箱对象
打开
关闭
使用大象和冰箱的功能
面向对象是以对象功能来划分问题,而不是步骤。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
- 封装性
- 继承性
- 多态性
# 1.4 面向过程和向对象的对比
# 面向过程
- 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。
- 缺点:没有面向对象易维护、易复用、易扩展
# 面向对象
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
- 缺点:性能比面向过程低
例子:用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。
# ES6 中的类和对象
# 面向对象
面向对象更贴近我们的实际生活, 可以使用面向对象描述现实世界事物. 但是事物分为具体的事物和抽象的事物
手机 抽象的(泛指的)
具体的(特指的)
面向对象的思维特点:
1.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
2.对类进行实例化, 获取类的对象
# 2.1 对象
现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的实物。
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的:
属性:事物的特征,在对象中用属性来表示(常用名词)
方法:事物的行为,在对象中用方法来表示(常用动词)
# 2.2 类 class
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它泛指某一大类(class)
对象特指某一个,通过类实例化一个具体的对象
类抽象了对象的公共部分,它泛指某一大类(class)
对象特指某一个,通过类实例化一个具体的对象
面向对象的思维特点:
1.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
2.对类进行实例化, 获取类的对象
# 2.3 创建类
语法:
class name {
// class body**
}
创建实例:
var xx = new name();
注意: 类必须使用 new 实例化对象
# 2.4 类 constructor 构造函数
constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义, 类内部会自动给我们创建一个 constructor()
语法:
class Person {
constructor(name,age) { // constructor 构造方法或者构造函数**
this.name = name;
this.age = age;
}
}
创建实例:
var ldh = new Person('刘德华', 18);
console.log(ldh.name)
注意点:
(1) 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
(2) 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
(3) constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
(4) 生成实例 new 不能省略
(5) 最后注意语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
# 2.5 类添加方法
语法:
class Person {
constructor(name,age) { // constructor 构造器或者构造函数**
this.name = name;
this.age = age;
}
say() {
console.log(this.name + '你好');
}
}
创建实例:
var ldh = new Person('刘德华', 18);
ldh.say()
注意: 方法之间不能加逗号分隔,同时方法不需要添加 function 关键字。
# 类的继承
# 3.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();
# 案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script>
// *super 关键字调用父类普通函数*
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
say() {
// *console.log('我是儿子');*
console.log(super.say() + '的儿子');
// *super.say() 就是调用父类中的普通函数 say()***
}
}
var son = new *Son*();
son.say();
// 继承中的属性或者方法查找原则: 就近原则
// 1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
// 2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
</script>
</body>
</html>
# 3.2 super 关键字
super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数
语法:
class Person { // 父类
constructor(surname){
this.surname = surname;
}
}
class Student extends Person { // 子类继承父类
constructor(surname,firstname){
super(surname); // 调用父类的constructor(surname)
this.firstname = firstname; // 定义子类独有的属性
}
}
注意: 子类在构造函数中使用 super, 必须放到 this 前面 (必须先调用父类的构造方法,在使用子类构造方法)
# ES6 注意点
1.在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象.
2.类里面的共有属性和方法一定要加 this 使用.
3.类里面的this 指向问题.
4.constructor 里面的 this 指向实例对象, 方法里面的 this 指向这个方法的调用者
# 案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<button>点击</button>
</head>
<body>
<script>
var that;
class Father {
constructor(x, y) {
// constructor 里面的this 指向的是 创建的实例对象
that = this
this.x = x
this.y = y
this.btn = document.querySelector('button')
this.btn.onclick = this.add
}
add() {
// *这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数*
console.log((that.x + that.y));
}
dance() {
// 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
_that = this;
console.log(this);
}
}
class Son extends Father {
constructor(x, y) {
super(x, y)
this.x = x
this.y = y
}
jian() {
console.log((this.y - this.x));
}
}
const son = new *Son*(3, 4)
// *son.add()*
// *console.log("-------------");*
// *son.jian()*
</script>
</body>
</html>
# Tab 栏案例
var that;
class Tab {
constructor(id) {
// *获取元素***
that = this;
this.main = document.querySelector(id);
this.add = this.main.querySelector('.tabadd');
// *li的父元素***
this.ul = this.main.querySelector('.fisrstnav ul:first-child');
// *section 父元素***
this.fsection = this.main.querySelector('.tabscon');
this.init();
}
init() {
this.updateNode();
// *init 初始化操作让相关的元素绑定事件***
this.add.onclick = this.addTab;
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].index = i;
this.lis[i].onclick = this.toggleTab;
this.remove[i].onclick = this.removeTab;
this.spans[i].ondblclick = this.editTab;
this.sections[i].ondblclick = this.editTab;
}
}
// *因为我们动态添加元素 需要从新获取对应的元素***
updateNode() {
this.lis = this.main.querySelectorAll('li');
this.sections = this.main.querySelectorAll('section');
this.remove = this.main.querySelectorAll('.icon-guanbi');
this.spans = this.main.querySelectorAll('.fisrstnav li span:first-child');
}
// *1. 切换功能***
toggleTab() {
// *console.log(this.index);***
that.clearClass();
this.className = 'liactive';
that.sections[this.index].className = 'conactive';
}
// *清除所有li 和section 的类***
clearClass() {
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].className = '';
this.sections[i].className = '';
}
}
// *2. 添加功能***
addTab() {
that.clearClass();
// *(1) 创建li元素和section元素***
var random = Math.random();
var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"></span></li>';
var section = '<section class="conactive">测试 ' + random + '</section>';
**// *(2) 把这两个元素追加到对应的父元素里面***
that.ul.insertAdjacentHTML('beforeend', li);
that.fsection.insertAdjacentHTML('beforeend', section);
that.init();
}
// *3. 删除功能***
removeTab(e) {
e.stopPropagation(); // *阻止冒泡 防止触发li 的切换点击事件***
var index = this.parentNode.index;
console.log(index);
// *根据索引号删除对应的li 和section remove()方法可以直接删除指定的元素***
that.lis[index].remove();
that.sections[index].remove();
that.init();
// *当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变***
if (document.querySelector('.liactive')) return;
// *当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态***
index--;
// *手动调用我们的点击事件 不需要鼠标触发***
that.lis[index] && that.lis[index].click();
}
// *4. 修改功能***
editTab() {
var str = this.innerHTML;
// *双击禁止选定文字***
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
// *alert(11);***
this.innerHTML = '<input type="text" />';
var input = this.children[0];
input.value = str;
input.select(); // *文本框里面的文字处于选定状态***
// *当我们离开文本框就把文本框里面的值给span***
input.onblur = function() {
this.parentNode.innerHTML = this.value;
};
// *按下回车也可以把文本框里面的值给span**
input.onkeyup = function(e) {
if (e.keyCode === 13) {
// *手动调用表单失去焦点事件 不需要鼠标离开操作***
this.blur();
}
}
}
}
new Tab('#tab');
# 构造函数和原型
# 1.1 概述
在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6 之前, JS 中并没用引入类的概念。
ES6, 全称 ECMAScript 6.0 ,2015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
在 ES6 之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。
# 创建对象可以通过以下三种方式:
对象字面量
new Object()
自定义构造函数
# 1.2 构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
在 JS 中,使用构造函数时要注意以下两点:
1.构造函数用于创建某一类对象,其首字母要大写
2.构造函数要和 new 一起使用才有意义
# new 在执行时会做四件事情:
① 在内存中创建一个新的空对象。
② 让 this 指向这个新的对象。
③ 执行构造函数里面的代码,给这个新对象添加属性和方法。
④ 返回这个新对象(所以构造函数里面不需要 return )。
JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
实例成员:在构造函数内部创建的对象成员称为实例成员,通过 this 添加,只能由实例化的对象来访问
# 1.3 构造函数的问题
构造函数方法很好用,但是存在浪费内存的问题。
# 1.4 构造函数原型 prototype
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
问答?
- 原型是什么 ?
一个对象,我们也称为 prototype 为原型对象。
- 原型的作用是什么 ?
共享方法。
一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放到原型对象身上。
# 1.5 对象原型 _ proto _
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
proto 对象原型和原型对象 prototype 是等价的
proto对象原型的意义就在于为对象的查找机制提供一个方向**,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype**
<!DOCTYPE html>
<html *lang*="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
//*构造函数*
function Star(uname, age) {
this.uname = uname
this.age = age
// *this.sing=function(){*
// *console.log("我爱唱歌");
// *}*
}
Star.prototype.sing = function () {
console.log('我爱唱歌')
}
const ldh = new Star('刘德华', 18)
const zxy = new Star('张学友', 18)
console.log(ldh.sing === zxy.sing)
console.log(ldh.__proto__ === Star.prototype)
// *方法的查找规则:首先看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing*
//*如果没有sing这个方法,因为__proto__的存在,就去构造函数原型对象prototype身上去查找*
//*sing方法*
</script>
</body>
</html>
# 1.6 constructor 构造函数
对象原型( proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
注意:
很多情况下,我们需要手动的利用 constructor 这个属性指回原来的构造函数
Star.prototype={
constructor:Star,
sing(){
console.log("我爱唱歌");
},
dance(){
console.log("我爱跳舞");
}
}
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动利用 constructor 指回原来的构造函数
# 1.7 构造函数、实例、原型对象三者之间的关系
# 1.8 原型链
重点:
- 只要是对象就有 _ _ proto _ _ 原型,指向原型对象
- 我们 Star 原型对象里面的 _ _ proto _ _ 原型指向的是 Object.prototype
- 我们 Object.prototype 原型对象里面的_ _ proto _ _ 原型 指向为 null
- 找对象身上的属性或方法,会先在对象实例上找,再去构造函数上找,依次找上去,如果最后找不到的话,返回 undefined
# 1.9 JavaScript 的成员查找机制(规则)
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。 如果找到了,则按照就近原则
② 如果没有就查找它的原型(也就是 proto指向的 prototype 原型对象)。
③ 如果还没有就查找原型对象的原型(Object 的原型对象)。
④ 依此类推一直找到 Object 为止(null)。
⑤proto对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
# 1.10 原型对象this 指向
构造函数中的 this 指向我们实例对象.
原型对象里面放的是方法, 这个方法里面的 this 指向的是 这个方法的调用者, 也就是这个实例对象.
# 1.11 扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
# 2. 继承
ES6 之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
# 2.1 call()
调用这个函数, 并且修改函数运行时的 this 指向
语法:fun.call(thisArg, arg1, arg2, ...) thisArg :当前调用函数 this 的指向对象 arg1,arg2:传递的其他参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
function fn(x, y) {
console.log('我是一个前端学习人')
console.log(this)
console.log(x + y)
}
//1.调用 函数
fn.call()
// 2. 改变this指向,call函数的第一个参数为修改的指向的对象的对象名*
var o = {
name: 'andy'
}
fn.call(o, 1, 2)
</script>
</body>
</html>
# 2.2 借用构造函数继承父类型属性
核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。
// 父类
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 子类
function Student(name, age, sex, score) {
Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数**
this.score = score;
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);
# 2.3 借用原型对象继承父类型方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:
① 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
② 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
③将子类的 constructor 从新指向子类的构造函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
function Father(uname, age) {
// this 指向 Father构造函数的实例对象
this.uname = uname
this.age = age
}
Father.prototype.money = function () {
console.log("赚了100万");
}
function Son(uname, age, score) {
// this 指向 Son构造函数的实例对象
Father.call(this, uname, age)
this.score = score
}
var son = new Son("刘德华", 18, 100)
Son.prototype.exam= function () {
console.log("孩子要考试");
}
// console.log(son);
// console.log(son.__proto__.constructor);
//这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起改变
// Son.prototype = Father.prototype
// *如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数*
Son.prototype = new Father()
Son.prototype*.*constructor = Son
console.log(son);
console.log(son.__proto__.constructor);
console.log(Father.prototype);
</script>
</body>
</html>
# 3. 类的本质与 function 一致
class 本质还是 function.
类的所有方法都定义在类的 prototype 属性上
类创建的实例,里面也有proto 指向类的 prototype 原型对象
4.所以 ES6 的类它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
5.所以 ES6 的类其实就是语法糖.
语法糖:语法糖就是一种便捷写法. 简单理解, 有两种方法可以实现同样的功能, 但是一种写法更加清晰、方便,那么这个方法就是语法糖
# ES5 中的新增方法
ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:
数组方法
字符串方法
对象方法
# 3.2 数组方法
迭代(遍历)方法:forEach()、map()、filter()、some()、every();
# forEach 方法与 map 方法相似
array.forEach(function(currentValue, index, arr){})
lcurrentValue:数组当前项的值
lindex:数组当前项的索引(可选)
larr:数组对象本身(可选)
filter()
array.filter(function(currentValue, index, arr))
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
注意它直接返回一个新数组
currentValue: 数组当前项的值
index:数组当前项的索引(可选)
arr:数组对象本身(可选)
some()与every()相似
some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.**
如果找到第一个满足条件的元素,则终止循环. 不在继续查找.**
currentValue: 数组当前项的值
index:数组当前项的索引(可选)
arr:数组对象本身(可选)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var arr = [11, 5, 3, 2, 44, 24, 66]
// 1.forEach 方法
arr.forEach(function (item, index, arr) {
console.log('第' + index + '个元素的值为:' + item);
})
console.log("---------------------------------");
// 2.filter 方法
const newArr = arr.filter(function (item, index, arr) {
return item % 2 === 0
})
console.log(newArr);
console.log("---------------------------------");
//*3. some 方法*
var flag = arr.some(function (item, index, arr) {
return item === 0
})
console.log(flag);
// *1. filter 是查找满足条件的元素,返回的是一个数组,而且是把所有满足条件的元素返回回来***
// *2.some 也是查找满足条件的元素是否存在, 返回的是一个布尔值,如果查找到第一个满足条件的元素就终止循环
</script>
</body>
</html>
# 案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
table {
width: 400px;
border: 1px solid #000;
border-collapse: collapse;
margin: 0 auto;
}
td,
th {
border: 1px solid #000;
text-align: center;
}
input {
width: 50px;
}
.search {
width: 600px;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="search">
按照价格查询: <input type="text" class="start" /> - <input type="text" class="end" />
<button class="search-price">搜索</button> 按照商品名称查询: <input type="text" class="product" />
<button class="search-pro">查询</button>
</div>
<table>
<thead>
<tr>
<th>id</th>
<th>产品名称</th>
<th>价格</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script>
// 利用新增数组方法操作数据
var data = [{
id: 1,
pname: '小米',
price: 3999
}, {
id: 2,
pname: 'oppo',
price: 999
}, {
id: 3,
pname: '荣耀',
price: 1299
}, {
id: 4,
pname: '华为',
price: 1999
},];
// 获取相应的元素
var tbody = document.querySelector('tbody')
var start = document.querySelector('.start')
var end = document.querySelector('.end')
var search_price = document.querySelector('.search-price')
var product = document.querySelector('.product')
var search_pro = document.querySelector('.search-pro')
setData(data)
function setData(data) {
// 先清空原来tbody 里面的数据
tbody.innerHTML = ''
data.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)
})
}
// 3. 根据价格查询商品
// 当我们点击了按钮,就可以根据我们的商品价格去筛选数组里面的对象
search_price.addEventListener('click', function () {
var newData = data.filter(function (value, index, arr) {
if (start.value !== '' && end.value !== '') {
return value.price >= start.value && value.price <= end.value
} else {
return arr
}
})
setData(newData)
start.value = ''
end.value = ''
})
// 4. 根据商品名称查找商品
// 如果查询数组中唯一的元素, 用some方法更合适,因为它找到这个元素,就不在进行循环,效率更高]
search_pro.addEventListener('click', function () {
var arr = []
data.some(function (value, index, array) {
if (value.pname === product.value) {
console.log(value);
arr.push(value)
return true
}
if (product.value === '') {
arr = array
}
})
setData(arr)
product.value = ''
})
</script>
</body>
</html>
# some 和 forEach 的区别
在 forEach 和 filter 方法里面 return 不会终止迭代
在 some 里面遇到 return true 就是终止遍历,迭代效率更高
# 3.3 字符串方法
trim() 方法会从一个字符串的两端删除空白字符。
str.trim()
trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
# 3.4 对象方法
# 1. Object.keys() 用于获取对象自身所有的属性
Object.keys(obj)
效果类似 for…in
返回一个由属性名组成的数组
# 2.Object.defineProperty() 定义对象中新属性或修改原有的属性。
Object.defineProperty(obj, prop, descriptor)
obj:必需。目标对象
prop:必需。需定义或修改的属性的名字(字符串)
descriptor:必需。目标属性所拥有的特性
Object.defineProperty() 第三个参数 descriptor 说明: 以对象形式 { } 书写
value: 设置属性的值 默认为 undefined
writable: 值是否可以重写。true | false 默认为 false
enumerable: 目标属性是否可以被枚举(是否可以遍历出来)。true | false 默认为 false
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为 false
# 函数进阶
# 1. 函数的定义和调用
# 1.1 函数的定义方式
函数声明方式 function 关键字 (命名函数)
函数表达式 (匿名函数) var fun =function(){}
new Function()
var fn = new Function('参数 1','参数 2'..., '函数体')
Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)
函数也属于对象
# 1.2 函数的调用方式
普通函数 fn() /fn.call() this 指向的是 window
对象的方法 obj.fn() this 指向的是所属对象
构造函数 new Fn() this 指向的是实例对象
绑定事件函数 btn.onclick=function(){} 点击了按钮就可以调用 this 指向绑定的事件对象
定时器函数 setInterval(function(){},1000) 这个函数是定时器自动一秒钟调用一次 this 指向 window
立即执行函数 (function(){})() 立即执行函数是自动调用的 this 指向 window
# 2. this
# 2.1 函数内 this 的指向
这些 this 的指向,是当我们调用函数的时候确定的。 调用方式的不同决定了 this 的指向不同
一般指向我们的调用者.
# 2.1 改变函数内部 this 指向
JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有 bind()、call()、apply() 三种方法。
# 1. call 方法
call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.call(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
- 返回值就是函数的返回值,因为它就是调用函数
- 因此当我们想改变 this 指向,同时想调用这个函数的时候,可以使用 call,比如继承
# 2. apply 方法
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray])
- thisArg:在 fun 函数运行时指定的 this 值
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
# 3. bind 方法
bind() 方法不会调用函数。但是能改变函数内部 this 指向
fun.bind(thisArg, arg1, arg2, ...)
- thisArg:在 fun 函数运行时指定的 this 值
- arg1,arg2:传递的其他参数
- 返回的是原函数改变 this 之后产生的新函数
- 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button>点击</button>
<script>
var o = {
name: 'andy'
}
function fn(arr) {
console.log("this指向的是" + this);
console.log(this);
// console.log("参数值的和为"+(x));
console.log(arr);
}
// 1 call 方法 ,既能调用函数,又能改变this指向,参数以逗号的形式隔开传递
fn.call(o,1,2)
//2. apply 方法,既能调用函数,又能改变this指向,参数以数组形式进行传递
fn.apply(o,['pink','ldh'])
var max= Math.max.apply(Math,[11,2,3,4,99,55])
var min= Math.min.apply(Math,[11,2,3,4,99,55])
console.log(max);
console.log(min);
//3.bind 方法,不能调用函数,只能改变this指向,返回的是改变this指向后的原函数的拷贝
// 适合于 有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
var f = fn.bind(o)
f()
fn()
// 场景: 有一个按钮,我们点击之后,就禁用这个按钮,3秒钟之后开启这个按钮
var btn=document.querySelector('button')
btn.onclick=function(){
this.disabled=true //*这个this指向的是btn这个按钮*
setTimeout(function () {
this.disabled=false //*定时器函数里面的this指向的是window*
}.bind(this),3000) //*这个this 指向的是btn这个对象*
}
var btns =document.querySelectorAll('button')
for(var i=0;i<btns.length;i++){
btns[i].onclick=function(){
this.disabled=true
setTimeout(function(){
this.disabled=false
}.bind(this),2000)
}
}
</script>
</body>
</html>
# 2.2 call apply bind
相同点: 都可以改变函数内部的 this 指向.
区别点: 1.call 和 apply 会调用函数,并且改变函数内部 this 指向. 2.call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg] 3.bind 不会调用函数, 可以改变函数内部 this 指向. 主要应用场景: 1.call 经常做继承. 2.apply 经常跟数组有关系.比如借助于数学对象实现数组最大值最小值 3.bind 不调用函数,但是还想改变 this 指向. 比如改变定时器内部的 this 指向.
# 3. 严格模式
# 3.1 什么是严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。变量必须声明才能使用
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名
# 3.2 开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
# 1. 为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或‘use strict’;)。
<script>
"use strict";
console.log("这是严格模式。");
</script>
因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。 有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
<script>
(function (){
"use strict";
var num = 10;
function fn() {}
})();
# 2. 为函数开启严格模式
要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。
function fn(){
"use strict";
return "这是严格模式。";
}
将 "use strict" 放在函数体的第一行,则整个函数以 "严格模式" 运行。
# 3.4 严格模式中的变化
严格模式对 Javascript 的语法和行为,都做了一些改变。
# 1. 变量规定
① 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var 命令声明,然后再使用。
②**严禁删除已经声明变量。**例如,delete x; 语法是错误的。
# 2. 严格模式下 this 指向问题
① 以前在全局作用域函数中的 this 指向 window 对象。
②严格模式下全局作用域中函数中的 this 是 undefined。
③ 以前构造函数时不加 new 也可以 调用,当普通函数,this 指向全局对象
④ 严格模式下,如果 构造函数不加 new 调用, this 指向的是 undefined 如果给他赋值则 会报错
⑤new 实例化的构造函数指向创建的对象实例。
⑥定时器 this 还是指向 window 。
事件、对象还是指向调用者。
# 3. 函数变化
① 函数不能有重名的参数。
② 函数必须声明在顶层.新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块(例如 if,for..)内声明函数。
# 4. 高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
情况(1):接收函数作为参数
<script>
function fn(callback){
callback&&callback();
}
fn(function(){alert('hi')})
</script>
情况(2):将函数作为返回值输出。
<script>
function fn(){
return function() {}
}
fn();
此时fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。 最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./jquery.js"></script>
</head>
<style>
div {
width: 5.3333rem;
height: 5.3333rem;
position: absolute;
background-color: pink;
}
</style>
<body>
<div></div>
<script>
function fn(a, b, callback) {
console.log(a + b)
callback && callback()
}
fn(1, 2, function () {
console.log('我是最后执行的')
})
$('div').animate(
{
left: 1000
},
function () {
$('div').css('backgroundColor', 'purple')
}
)
</script>
</body>
</html>
# 5. 闭包
# 5.1 变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量。
函数内部可以使用全局变量。
函数外部不可以使用局部变量。
当函数执行完毕,本作用域内的局部变量会销毁。
# 5.2 什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。 ----- JavaScript 高级程序设计
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
<script>
function fn1(){ // fn1 就是闭包函数
var num = 10;
function fn2(){
console.log(num); // 10
}
fn2()
}
fn1();
</script>
# 5.3 在 chrome 中调试闭包
打开浏览器,按 F12 键启动 chrome 调试工具。
设置断点。
找到 Scope 选项(Scope 作用域的意思)。
当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
# 5.3 闭包的作用
<script>
function fn() {
var num = 10;
return function fn{
console.log(num); //10
}
}
var f = fn();
f()
</script>
闭包作用:延伸变量的作用范围。
# 5.5 闭包案例
循环注册点击事件。
循环中的 setTimeout()。
计算打车价格。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
//1. 循环注册点击事件。
var lis = document.querySelector('.nav').querySelectorAll('li')
for(var i=0;i<lis.length;i++){
lis[i].index=i
lis[i].onclick=function(){
console.log(this.index);
}
}
// 使用闭包的方式得到当前小li的索引号
for (var i = 0; i < lis.length; i++) {
//利用for循环创建了4个立即执行函数
// 立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i变量
(function (i) {
lis[i].onclick = function () {
console.log(i);
}
})(i);
}
//2. 循环中的 setTimeout()。
setTimeout(function () {
for(var i = 0; i < lis.length; i++) {
(function (i) {
console.log(lis[i]*.*innerHTML)
})(i);
}
}, 3000)
// 3. 计算打车价格。
// 闭包应用-计算打车价格
// 打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格
// 如果有拥堵情况,总价格多收取10块钱拥堵费
var call = (function () {
var start = 13 //*起步价*
var sum = 0 //*总价*
return {
price: function (n) {
if (n <= 3) {
sum = start
} else {
sum = start + (n - 3) * 5
}
return sum
},
busy: function (flag) {
return flag ? sum + 10 : sum
}
}
})();
console.log(call.price(5));
console.log(call.busy(true));
</script>
</body>
</html>
# 5.6 闭包总结
# 1.闭包是什么?
闭包是一个函数 (一个作用域可以访问另外一个函数的局部变量)
# 2. 闭包的作用是什么?
延伸变量的作用范围
# 6. 递归
# 6.1 什么是递归?
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解:函数内部自己调用自己, 这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
利用递归遍历数据案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔',
}, {
id: 112,
gname: '美的'
},]
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
function getData(data, id) {
var o = {}
data.forEach(function (item) {
if (item.id === id) {
o = item
} else if (item.goods && item.goods.length > 0) {
o = getData(item.goods, id)
}
})
return o
}
console.log(getData(data, 1));
console.log(getData(data, 2));
console.log(getData(data, 112));
</script>
</body>
</html>
# 6.4 浅拷贝和深拷贝
1.浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.***(for in)k :属性名 obj[k]:属性值****
2.深拷贝拷贝多层, 每一级别的数据都会拷贝.(通过 for in + 函数递归 的方式遍历数据,拷贝数据)
3.Object.assign(target, ...sources) es6 新增方法可以浅拷贝
target:拷贝给谁,sources:拷贝谁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['pink', 'red']
};
var o = {};
// 浅拷贝 只拷贝一层,深层次的对象只拷贝引用
for (var key in obj) {
o[key] = obj[key]
}
console.log(o);
o.msg.age = 20
console.log(obj);
// es6 新增 方法Object.assign(target,resource)
Object.assign(o, obj)
console.log(o);
o.msg.age = 20
console.log(obj);
//*深拷贝*
function deepCopy(newObj, oldObj) {
for (var key in oldObj) {
// 获取属性值
var item = oldObj[key]
// 判断是否存在有数组类型的属性,因为数组也属于对象类型的一种,所以必须写在对象类型之前
if (item instanceof Array) {
newObj[key] = []
deepCopy(newObj[key], item)
}
// 判断是否存在有对象类型的属性
else if (item instanceof Object) {
newObj[key] = {}
deepCopy(newObj[key], item)
} else {
// 普通类型的属性直接赋值
newObj[key] = item
}
}
}
deepCopy(o, obj)
console.log(o);
o.msg.age = 20
console.log(obj);
</script>
</body>
</html>
# 正则表达式
# 1. 正则表达式概述
# 1.1 什么是正则表达式
正则表达式( Regular Expression **)**是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。
正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,**正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)**等 。
其他语言也会使用正则表达式,本阶段我们主要是利用 JavaScript 正则表达式完成表单验证。
# 1.2 正则表达式的特点
灵活性、逻辑性和功能性非常的强。
可以迅速地用极简单的方式达到字符串的复杂控制。
对于刚接触的人来说,比较晦涩难懂。比如: ^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式.
比如用户名: /^[a-z0-9_-]{3,16}$/
# 2.1 创建正则表达式
# 1. 通过调用 RegExp 对象的构造函数创建
var 变量名 = new RegExp(/表达式/);
# 2. 通过字面量创建
var 变量名 = /表达式/;
// 注释中间放表达式就是正则字面量
# 2.2 测试正则表达式 test
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
regexObj.test(str)
1.regexObj 是写的正则表达式
2.str 我们要测试的文本
3.就是检测 str 文本是否符合我们写的正则表达式规范.
# 3.1 正则表达式的组成
一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
特殊字符非常多,可以参考:
MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
jQuery 手册:正则表达式部分
正则测试工具: http://tool.oschina.net/regex
# 3.2 边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。
如果 ^ 和 $ 在一起,表示必须是精确匹配。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
var rg = //*正则表达式里面不需要加引号,不管是数字型还是字符串型*
/*abc*/ // */abc/ 只要包含有 abc这个字符串返回的都是true*
console.log(rg.test('abc')) //*true
console.log(rg.test('abcd')) //*true
console.log(rg.test('adbcd')) //*true
console.log('---------------------------------')
var reg = /*^**abc*/ console.log(rg.test('abc')) //*true
console.log(rg.test('abcd')) //*true
console.log(rg.test('adbcd')) //*false
console.log('--------------------------------')
var reg = //*精确匹配,要求必须是abc字符串才符合规范*
/*^**abc**$*/ console.log(rg.test('abc')) //true
console.log(rg.test('abcd')) //false
console.log(rg.test('adbcd')) //false
</script>
</body>
</html>
# 3.3 字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
# 1. [] 方括号
/[abc]/.test('andy') // true
后面的字符串只要包含 abc 中任意一个字符,都返回 true
# 2. [-] 方括号内部 范围符-
/^[a-z]$/.test(c') // true **/^[a-z]$/ 多选一 26 个英文字符任何一个字母返回的都是 true****
方括号内部加上 - 表示范围,这里表示 a 到 z 26 个英文字母都可以。
# 3. [^] 方括号内部 取反符^
/[^abc]/.test('andy') // false
方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
注意和边界符 ^ 区别,边界符写到方括号外面。
# 4. 字符组合
/[a-z1-9]/.test('andy') // true
方括号内部可以使用字符组合,这里表示包含 a 到 z 的 26 个英文字母和 1 到 9 的数字中的都可以。
# 3.4 量词符
量词符用来设定某个模式出现的次数。
{}中间不能有空格
# 表单数据格式验证案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.wrong {
color: red;
font-size: 12px;
font-weight: bold;
}
.right {
color: green;
font-size: 12px;
font-weight: bold;
}
</style>
</head>
<body>
<input type="text" placeholder="请输入用户名" class="uname" /> <span></span>
<script>
var uname = document.querySelector('.uname')
var span = document.querySelector('span')
var reg = /*^*[*a-zA-Z0-9_-*]{6,16}*$*/
uname.addEventListener('blur', function () {
if (reg.test(uname.value)) {
span.className = 'right'
span.innerHTML = "用户名合法"
} else {
span.className = 'wrong'
span.innerHTML = "用户名不合法"
}
})
</script>
</body>
</html>
# 3.5 括号总结
1.大括号 量词符. 里面表示重复次数
2.中括号 字符集合。匹配方括号中的任意字符.
3.小括号 表示优先级
可以在线测试: https://c.runoob.com/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script>
// 中括号 字符集合.匹配方括号中的任意字符.
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}*$*/; // 它是让abcc重复三次
console.log(reg.test('abc'))
console.log(reg.test('abcabcabc'))
console.log(reg.test('abccc'));
</script>
</body>
</html>
# 3.6 预定义类
正则中的或: |
预定义类指的是某些常见模式的简写方式。
# 案例:表单验证
1.手机号码: /^1[3|4|5|7|8][0-9]{9}$/
2.QQ: [1-9][0-9]{4,} (腾讯 QQ 号从 10000 开始)
3.昵称是中文: ^[\u4e00-\u9fa5]{2,8}$
# 4. 正则表达式中的替换
# 4.1 replace 替换
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp**/**substr,replacement)
1.第一个参数: 被替换的字符串 或者 正则表达式
2.第二个参数: 替换为的字符串
3.返回值是一个替换完毕的新字符串
# 4.2 正则表达式参数
/表达式/[switch]
switch(也称为修饰符) 按照什么样的模式来匹配. 有三种值:
g:全局匹配
i:忽略大小写
gi:全局匹配 + 忽略大小写
# ES6
# 什么是 ES6 ?
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。
ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。
# 为什么使用 ES6 ?
- 变量提升特性增加了程序运行时的不可预测性
- 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码
# ES6 的新增语法
# let
ES6 中新增的用于声明变量的关键字。
- let 声明的变量只在所处于的块级有效
if (true){
let a = 10;
}
console.log(a)// a is not defined
注意:使用 let 关键字声明的变量才具有块级作用域,使用 var 声明的变量不具备块级作用域({}内表示为块级作用域)特性。
- 不存在变量提升
console.log(a); // a is not defined
let a = 20;
- 暂时性死区(在块级作用域内声明的变量,与{}绑定在一起)
var tmp = 123;
if(true){
tmp = 'abc';
let tmp;
}
- 经典面试题(1)
var arr = [];
for(var i = 0; i < 2; i++){
arr[i]=function () {
console.log(i);
}
}
arr[0]();
arr[1]();
(opens new window) 经典面试题图解:此题的关键点在于变量 i 是全局的,函数执行时输出的都是全局作用域下的 i 值。
- 经典面试题(2)
let arr =[];
for(let i = 0; i < 2; i++){
arr[i]=function() {
console.log(i);
}
}
arr[0]();
arr[1]();
经典面试题图解:此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的 i 值. let 关键字就是用来声明变量的
使用 let 关键字声明的变量具有块级作用域
在一个大括号中 使用 let 关键字声明的变量才具有块级作用域 var 关键字是不具备这个特点的
目的:防止循环变量变成全局变量
使用 let 关键字声明的变量没有变量提升
使用 let 关键字声明的变量具有暂时性死区特性
# const
作用:声明常量,常量就是值(内存地址)不能变化的量。
普通属性用 const 修饰的化,值不能改变,如果是对象的话,用 const 修饰,可以改变对象的内部属性,不能改变对象的内存地址
- 具有块级作用域
if(true){
const a = 10;
}
console.log(a)// a is not defined
- 声明常量时必须赋值
const PI;// Missing initializer in const declaration
- 常量赋值后,值不能修改。
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.
# let、const、var 的区别
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值。
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
# 解构赋值
ES6 中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
# 数组解构
let[a, b, c](a,b,c为变量名) =[1, 2, 3];
console.log(a)
console.log(b)
console.log(c)
如果解构不成功,变量的值为undefined。
let[foo]=[];
let[bar, foo]=[1];
#### 对象解构
letperson={name:'zhangsan', age:20};
let{name, age }=person;
console.log(name);// 'zhangsan'
console.log(age); // 20
将解构出来的对象名进行重命名
let{name: myName,age: myAge} = person;// myName myAge 属于别名**
console.log(myName); // 'zhangsan'
console.log(myAge);// 20
# 箭头函数
ES6 中新增的定义函数的方式。
()=>{}
const fn =()=>{}
函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
function sum(num1,num2){
return num1 + num2;
}
箭头函数
const sum =(num1,num2)=>num1+num2;
!!如果形参只有一个,可以省略小括号
function fn (v){
return v;
}
箭头函数
const fn = v=> v;
箭头函数不绑定 this 关键字,箭头函数中的 this,指向的是函数定义位置的上下文 this。
# 注意点:
//对象不产生作用域,在其里面定义的箭头函数实际上是在 window 全局作用域下
var age=100
var obj ={
_age_:20,
_say_:()=>{
_alert_(_this.age_)
}
}
_obj.say_()
# 剩余参数
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
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']
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
任意数求和
const sum = (...args) => {
let total = 0
args.forEach(item => (total += item))
return total
}
console.log(sum(10, 20))
console.log(sum(10, 20, 30, 40))
与解构赋值搭配使用
let arr = ['wangwu', 'zhangsan', 'lisi']
let [p1, ...p2] = arr
console.log(p1)
console.log(p2)
</script>
</body>
</html>
# Array 的扩展方法
# 扩展运算符(展开语法)
- 扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
let ary =[1,2,3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3
console.log(1,2,3) //1 2 3
- 扩展运算符可以应用于合并数组。
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二**
ary1.push(...ary2);
- 将伪数组或可遍历对象转换为真正的数组,就可以调用数组相关的方法
let oDivs =document.getElementsByTagName('div'); // 返回值为一个元素集合,是伪数组
oDivs =[...oDivs];
# 构造函数方法: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(aryLike,item =>item *2)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
var obj = {
0: '张三',
1: '李四',
2: '王五',
length: 3
}
var newArray = Array.from(obj)
console.log(newArray)
;(3)[('张三', '李四', '王五')]
var obj = {
0: '1',
1: '2',
length: 2
}
var newArray = Array.from(obj, item => item * 3)
console.log(newArray) //(2) [3, 6]
</script>
</body>
</html>
# 实例方法:find()
用于找出第一个符合条件的数组成员,如果没有找到返回 undefined
let ary =[
{
id: 1,
name: '张三'
},
{
id:2,
name:'李四'
}
];
let target = ary.find((item,index)=>item.id==2);
# 实例方法:findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary =[1,5,10,15];
let index = ary.findIndex((value,index)=>value >9);
console.log(index); // 2
### 实例方法:includes()
表示某个数组是否包含给定的值,返回布尔值。
[1,2,3].includes(2) // true
[1,2,3].includes(4) // false
# String 的扩展方法
# 模板字符串
ES6 新增的创建字符串的方式,使用反引号定义。
let name = `zhangsan`;
模板字符串中可以解析变量。${}
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 sayHello = function(){
return '哈哈哈哈 追不到我吧 我就是这么强大';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet);// 哈哈哈哈 追不到我吧 我就是这么强大 哈哈哈哈
# **实例方法:startsWith()**和 endsWith()
startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
# 实例方法:repeat()
repeat 方法表示将原字符串重复 n 次,返回一个新字符串。
'x'.repeat(3) // "xxx"
'hello'.repeat(2)// "hellohello"
# Set 数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
应用场景:记录用户浏览网站的搜索记录
Set 本身是一个构造函数,用来生成 Set 数据结构。
const s = new Set();
size 属性:可以看到 size 这个数据结构中包含了多少值
Set 函数可以接受一个数组作为参数,用来初始化。
const set =new Set([1,2,3,4,4]);**
# 实例方法
- add(value):添加某个值,返回 Set 结构本身,可以链式调用
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为 Set 的成员
- clear():清除所有成员,没有返回值
# 遍历
Set 结构的实例与数组一样,也拥有 forEach 方法,用于对每个成员执行某种操作,没有返回值。
s.forEach(value =>console.log(value))
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const s1 = new Set()
// *使用add 方法向s1中添加元素
s1.add(1).add(2).add(3).add(4)
console.log(s1.ize)
const array1 = [...s1]
console.log(array1)
// 使用delete方法从s1中删除元素
s1.delete(2)
const array2 = [...s1]
console.log(array2)
// 使用 has方法判断某个元素是否在s1中,返回值是一个布尔值
let isexist = s1.has(1)
console.log(isexist)
// 使用 clear 方法 清空s1中的所有元素
// s1.clear()
// const array3 = [...s1]
// console.log(array3);
// 使用forEach进行遍历s1
s1.forEach(item => console.log(item))
</script>
</body>
</html>