ES6
一、面向对象编程介绍
1.1 两大编程思想
- 面向过程
- 面向对象
1.2 面向过程编程POP(Process-oriented programming)
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步步实现,使用的时候再一个个的依次调用就可以了。
面向过程就是按照我们分析好了的步骤,按照步骤解决问题。
1.3 面向对象编程OOP(Object Oriented Programming)
面向对象是把事务分解成一个个对象,然后由对象之间分工与合作。
举个例子:将大象装进冰箱,面向对象做法-->先找出对象,并写出这些对象的功能:
①大象对象
- 进去
②冰箱对象
- 打开
- 关闭
③使用大象和冰箱的功能
面向对象是以对象功能来划分问题,而不是步骤。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
- 封装性
- 继承性
- 多态性
1.4 面向过程和面向对象的对比
面向过程
- 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如:单片机就采用的面向过程编程。
- 缺点:没有面向对象的易维护、易复用、易拓展等特性。
面向对象
- 优点:易维护、易复用、易拓展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
- 缺点:性能比面向过程低
用面向过程的方法写出来的程序就是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。
二、ES6中的类和对象
面向对象
面向对象更贴近我们的实际生活,可以使用面向对象描述现实世界事物,但是事物分为具体的事物和抽象的事物。
面向对象的思维特点:
①.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
②.对类进行实例化,获取类的对象
面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点,不断的创建对象,使用对象,指挥对象做事情。
2.1 对象
现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的实物。例如,一本书、一辆汽车、一个人可以是“对象”,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的:
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
2.2 类class
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
类抽象了对象的公共部分,它泛指某一大类(class)
对象特指某一个,通过类实例化一个具体的对象
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 继承
现实中的继承:子承父业,比如我们都继承了父亲的姓。
程序中的继承:子类可以继承父亲的一些属性和方法。
语法:
class Father { //父类
}
class Son extends Father { // 子类继承父类
}
实例:
class Father {
constructor(surname){
this.surname = surname;
}
say() {
console.log('你的姓是:' + this.surname);
}
}
class Son extends Father {
}
var demo = new Son('刘');
demo.say();
// 输出:你的姓是:刘
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前面(必须先调用父类的构造方法,在使用子类构造方法)
案例:
class Father {
constructor(surname) {
this.surname = surname;
}
saySurname() {
console.log('我的姓是' + this.surname);
}
}
class Son extends Father { // 这样子类就继承了父类的属性和方法
constructor(surname, fristname) {
super(surname); // 调用父类的constructor(surname)
this.fristname = fristname;
}
sayFristname() {
console.log("我的名字是:" + this.fristname);
}
}
var damao = new Son('刘', "德华");
damao.saySurname();
damao.sayFristname();
// 输出:我的姓是刘 我的名字是:德华
super关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。
注意:
继承中的属性或者方法查找原则,就近原则
1、继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的。
2、继承中,如果子类里面没有,就去找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)。
语法:
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
say() {
// super.say() super调用父类得方法
return super.say() + '的儿子';
}
}
var demo = new Son();
console.log(demo.say());
// 输出:我是爸爸的儿子
三个注意点:
1、在ES6中类没有变量提升,所以必须先定义类,才能通过实例化对象。
2、类里面的共有属性和方法一定要加this使用。
3、类里面的this指向问题。
4、constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者。
四、面向对象案例
面向对象版tab栏切换
功能需求:
1、点击 tab栏,可以切换效果。
2、点击 + 号, 可以添加 tab 项和内容项。
3、点击 x 号, 可以删除当前的tab项和内容项。
4、双击tab项文字或者内容项文字,可以修改里面的文字内容。
抽象对象:Tab对象
1、该对象具有切换功能
2、该对象具有添加功能
3、该对象具有删除功能
4、该对象具有修改功能
面向对象版tab栏切换 添加功能
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
面向对象版tab栏切换 删除功能
1、点击 × 可以删除当前的li选项卡和当前的section。
2、X是没有索引号的, 但是它的父亲li 有索引号, 这个索引号正是我们想要的索引号。
3、所以核心思路是: 点击 x 号可以删除这个索引号对应的 li 和 section。
4、但是,当我们动态删除新的li和索引号时,也需要重新获取 x 这个元素. 需要调用init 方法。
面向对象版tab栏切换 编辑功能
1、双击选项卡li或者 section里面的文字,可以实现修改功能
2、双击事件是: ondblclick
3、如果双击文字,会默认选定文字,此时需要双击禁止选中文字
4、window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
5、核心思路: 双击文字的时候, 在 里面生成一个文本框, 当失去焦点或者按下回车然后把文本框输入的值给原先元素即可。
var that;
class Tab {
constructor(id) {
// 获取大盒子元素
that = this;
this.main = document.querySelector(id);
// 只要new一下Tab就会自动调用constructor,所以放在里面会更好
// 获取+为添加功能做准备
this.add = this.main.querySelector(".tabadd");
// 获取ul元素,为创建元素追加做准备
// li父元素
this.ul = this.main.querySelector(".fisrstnav ul:first-child");
// section父元素
this.fsection = this.main.querySelector(".tabscon");
this.init();
}
// 初始化,让项目一启动就显示的页面
// 初始化操作让相关的元素绑定事件
init() {
this.updatedNode();
// (1)给tab切换绑定点击事件
for (var i = 0; i < this.lis.length; i++) {
// 需要每个列表项的索引号li
this.lis[i].index = i;
// 点击调用切换功能
this.lis[i].onclick = this.toggleTab;
// 给所有×号添加点击事件
this.remove[i].onclick = this.removeTab;
// 双击点击事件ondblclick
this.spans[i].ondblclick = this.editTab;
this.sections[i].ondblclick = this.editTab;
}
// 给+号添加点击事件
this.add.onclick = this.addTab;
}
// 因为我们动态添加元素,需要重新获取对应的元素
// 获取所有的li和section
updatedNode() {
// 或取盒子里面的列表项li
this.lis = this.main.querySelectorAll("li");
// 获取所有的sections
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() {
// 调用全局this来干掉所有人,留下我自己
that.clearClass();
this.className = "liactive";
// 因为this指向的是上一级的init(),而里面没有sections 属性
// 解决办法,声明一个全局变量,拿到sections
// this.sections[this.index].className = "conactive";
that.sections[this.index].className = "conactive";
}
// 定义一个清除所有样式的函数
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 li =
'<li class="liactive"><span>Tab</span><span class="iconfont icon-guanbi"></span></li>';
var section = '<section class="conactive">tab</section>';
// 2、追加到对应的父元素里面
that.ul.insertAdjacentHTML("beforeend", li);
that.fsection.insertAdjacentHTML("beforeend", section);
that.init();
}
//3、删除功能
removeTab(e) {
// 阻止li的冒泡事件
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 的时候,原来的选中状态li保持不变
if (document.querySelector(".liactive")) return;
// 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态
index--;
// 当我们点击×号,减少了一个li和section,显示前一个li的选中样式,手动调用我们的点击事件 不需要鼠标触发
that.lis[index] && that.lis[index].click(); //如果为真,则执行点击事件
}
//4、修改功能
editTab() {
// 拿到文本框里面的文字
var str = this.innerHTML;
// 双击禁止选定文字
window.getSelection
? window.getSelection().removeAllRanges()
: document.selection.empty();
// 双击生成一个文本框
this.innerHTML = '<input type="text"/>';
// 把原来span里面的内容赋值给文本框
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();
}
};
}
}
// 传参大盒子tab,传给constructor的id
var tab = new Tab("#tab");
五、构造函数和原型
5.1 概述
在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6之前, JS 中并没用引入类的概念。
ES6, 全称 ECMAScript 6.0 ,2015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
在 ES6之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。
创建对象可以通过一下三种方式:
1、对象字面量
var obj = {};
2、new Object()
var obj = new Object();
3、自定义构造函数
function Star(uname){
this.uname = uname;
this.study = function() {
console.log('我爱学习!')
}
}
var hk = new Star('javaScript');
console.log(hk);
hk.study();
5.2 构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
在JS中,使用构造函数时要注意一下两点:
1、构造函数用于创建一类对象,其首字母要大写
2、构造函数要和new一起使用才有意义
new 在执行时会做四件事情:
①在内存中创建一个新的空对象。
②让 this 指向这个新的对象。
③执行构造函数里面的代码,给这个新对象添加属性和方法。
④返回这个新对象(所以构造函数里面不需要 return )。
JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
-
静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问。
静态成员在构造函数本身上添加的成员,sex就是静态成员。Star.sex = '男'; // 静态成员只能通过构造函数来访问 console.log(Star.sex); // 错误示范 console.log(hk.sex); // 不能通过对象来访问
-
实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问,不可以通过构造函数来访问实例成员。
实例成员就是构造函数内部通过this添加的成员 uname、age、sing就是实例成员
5.3 构造函数的问题
构造函数方法很好用,但是存在浪费内存的问题。
5.4 构造函数原型prototype
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
function Star(uname){
this.uname = uname;
}
Star.prototype.study = function() {
console.log('我爱学习!')
}
var hk = new Star('javaScript');
var hk2 = new Star('ES6');
console.log(hk);
hk.study();
hk2.study();
// 一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放到原型对象身上
问答?
1.原型是什么 ?
一个对象,我们也称为 prototype 为原型对象。
2.原型的作用是什么 ?
共享方法。
5.5 对象原型 _ proto _
对象都会有一个属性 _ proto _ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 _ proto _ 原型的存在。
-
_ proto _对象原型和原型对象 prototype 是等价的
-
_ proto _对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
5.6 constructor构造函数
对象原型( _ proto _)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
Star.prototype.study = function(){
console.log('我爱学习');
};
Star.prototype.movie = function(){
console.log('我爱看电影');
}
// 用对象的方法来写的话需要手动的利用constructor这个属性指回原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor:Star,
study:function(){
console.log('我爱学习');
},
movie:function(){
console.log('我爱看电影');
}
}
5.7 构造函数、实例、原型对象三者之间的关系
5.8 原型链
①只要是对象就有_ proto _原型,指向原型对象
②我们的Star原型对象里面的 _ proto _原型指向的是Object.prototype
③我们Object.prototype原型对象里面的 _ proto _原型指向为null
5.9 JavaScript的成员查找机制(规则)
①当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
②如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
③如果还没有就查找原型对象的原型(Object的原型对象)。
④依此类推一直找到 Object 为止(null)。
⑤_proto _对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
5.10 原型对象this指向
构造函数中的this 指向我们实例对象。
原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象。
5.11 扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
// Array.prototype里面没有sum求和这个方法,我们可以扩展内置对象方法
Array.prototype.sum = function(){
var sum = 0;
for(var i = 0; i < this.length; i++) {
sun += this[i];
}
return sum;
}
var arr = [1, 2, 3];
console.log(arr.sum()); // 输出为6
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum()) // 输出为66
六、继承
ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
6.1 call()
调用这个函数, 并且修改函数运行时的 this 指向
fun.call(thisArg,arg1,arg2,...)
- thisArg:当前调用函数 this 的指向对象
- arg1,arg2:传递的其他参数
function fn (x,y) {
console.log('我爱学习');
console.log(this); // 没有改变this指向的话指向的是window
console.log(x+y); // 因为用call改变了传参和指向,所以输出的是{name:"study"} 3
}
var obj = {
name:'study'
};
fn.call(obj,1,2)
6.2 借用构造函数继承父类型属性
核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。
// 父类
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 子类
function Student(name, age, sex, score) {
// 将父构造函数的this改为子构造函数的this
Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
this.score = score;
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);
6.3借用原型对象继承父类型方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:
①将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
②本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
③将子类的 constructor 从新指向子类的构造函数
// 父类
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.money = function(){
console.log(1000);
}
// 子类
function Student(name, age, sex, score) {
// 将父构造函数的this改为子构造函数的this
Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
this.score = score;
}
// 错误示范 Student.prototype = Person.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
// 正确编写 Student.prototype = new Person();
Student.prototype = new Person();
// 这样会让原型对象指向Person,我们需要修改原型对象的指向,用constructor来指回原来的构造函数
Student.prototype.constructor = Student;
Student.prototype.exam = function(){
console.log('孩子需要考试');
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);
6.4 类的本质
1.class本质还是function,我们也可以简单的认为类就是构造函数的另外一种写法。
2.类的所有方法都定义在类的prototype属性上
3.类创建的实例,里面也有__proto__ 指向类的prototype原型对象
4.所以ES6的类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
5.所以ES6的类其实就是语法糖.
6.语法糖:语法糖就是一种便捷写法. 简单理解, 有两种方法可以实现同样的功能, 但是一种写法更加清晰、方便,那么这个方法就是语法糖
拓展:构造函数的特点
(1)构造函数有原型对象prototype
(2)构造函数原型对象prototype里面有constructor指向构造函数本身
(3)构造函数可以通过原型对象添加方法
(4)构造函数创建的实例对象有_ proto _原型指向构造函数的原型对象
七、ES5中的新增方法
7.1 ES5新增方法概述
ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:
-
数组方法
-
字符串方法
-
对象方法
7.2 数组方法
迭代(遍历)方法:forEach()、map()、filter()、some()、every();
array.forEach(function(currentValue, index, arr))
-
currentValue:数组当前项的值
-
index:数组当前项的索引
-
arr:数组对象本身
var arr = [1, 2, 3,];
arr.forEach(function(value,index,array){
console.log('每个数组元素' + value);
console.log('每个数组元素的索引号'+ index);
console.log('数组本身' + array);
sum += value; //6
})
迭代(遍历)方法:forEach()、map()、filter()、some()、every();
array.filter(function(currentValue, index, arr))
-
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
-
注意它直接返回一个新数组
-
currentValue: 数组当前项的值
-
index:数组当前项的索引
-
arr:数组对象本身
var arr = [12, 66, 4, 88];
var newArr = arr.filter(function(value,index){
return value>=20;
});
console.log(newArr); // 返回的是一个新数组 [66, 88]
迭代(遍历)方法:forEach()、map()、filter()、some()、every();
array.some(function(currentValue, index, arr))
-
some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
-
注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.
-
如果找到第一个满足条件的元素,则终止循环. 不在继续查找.
-
currentValue: 数组当前项的值
-
index:数组当前项的索引
-
arr:数组对象本身
var arr = ['red','yellow','blue'];
var arr1 = arr.some(function(value){
return value == 'red';
})
console.log(arr1) // true
7.3查询商品案例
1.把数据渲染到页面中 (forEach)
2.根据价格显示数据(filter)
3.根据商品名称显示数据(some)
<!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,
},
];
// 1、获取需要操作的元素
// 获取操作表单元素
var tbody = document.querySelector("tbody");
// 获取按商品价格查找的按钮
var search_price = document.querySelector(".search-price");
// 获取商品价格区间输入框
var start = document.querySelector(".start");
var end = document.querySelector(".end");
// 获取按商品名称查找按钮
var search_pro = document.querySelector(".search-pro");
// 获取商品名称输入框
var product = document.querySelector(".product");
// 拿到数据
setDate(data);
// 2、数据渲染
function setDate(mydata) {
tbody.innerHTML = "";
mydata.forEach((value) => {
// 创建tr元素
var tr = document.createElement("tr");
tr.innerHTML =
"<th>" +
value.id +
"</th><th>" +
value.pname +
"</th><th>" +
value.price +
"</th>";
tbody.appendChild(tr);
});
}
// 按价格查找,用filter筛选 search-price
// 当我们点击了按钮,就可以根据我们的商品价格去筛选数组里面的对象
search_price.addEventListener("click", function () {
var newData = data.filter((value) => {
return value.price >= start.value && value.price <= end.value;
});
// console.log(newData);
// 因为筛选出来的是新数组,别忘了渲染到页面上
setDate(newData);
});
// 如果查询数组中唯一的元素, 用some方法更合适,因为它找到这个元素,就不在进行循环,效率更高]
// search-pro按商品名称查找,用some,判断是否等于,等于就输出当前输入的商品名称的价格,用数组
search_pro.addEventListener("click", function () {
// 定义个数组来存放some查询到的信息,查询到就结束循环
var arr = [];
data.some((value) => {
if (value.pname === product.value) {
arr.push(value);
return true;
}
});
// 记得渲染到页面
setDate(arr);
});
</script>
</body>
</html>
forEach和some的区别
forEach和(filter)在遍历的时候,就是查找到该元素return true了,还会继续迭代
some在遍历的时候,查找到该元素之后 return true,就会终止迭代,终止遍历
7.4 字符串方法
trim() 方法会从一个字符串的两端删除空白字符。
str.trim()
trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
7.5 对象方法
1.Object.keys() 用于获取对象自身所有的属性
Object.keys(obj)
-
效果类似 for…in
-
返回一个由属性名组成的数组
var obj = {
id: 1,
name: "小米",
price: 1999,
};
var arr = Object.keys(obj);
console.log(arr); // 拿到的是id name price,以数组的形式
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
var obj = {
id: 1,
name: "小米",
price: 1999,
};
// 1、以前的对象添加和修改属性的方式
obj.num = 1000;
obj.price = 2000;
console.log(obj);
// 2、Object.defineProperty()定义新属性或修改原有的属性
Object.defineProperty(obj, "num", {
value: 1000,
writable:false // 不允许修改这个属性值,默认为false
});
console.log(obj);
八、函数进阶
(一)、函数的定义和调用
1.1函数的定义方式
-
函数声明方式 function 关键字 (命名函数)
function fun() {}
-
函数表达式 (匿名函数)
var fun = function() {}
-
new Function() ------->构造函数
var fn = new Function('参数1(形参)','参数2(形参)'..., '函数体(console.log('123'))')
-
Function 里面参数都必须是字符串格式
-
第三种方式执行效率低,也不方便书写,因此较少使用
-
所有函数都是 Function 的实例(对象)
-
函数也属于对象
1.2 函数的调用方式
①普通函数
function fn() {
}
fn(); // fn.call();
②对象的方法
var obj = {
sayHi:function()
}
obj.sayHi();
③构造函数
function Star() {};
new Star();
④绑定事件函数
btn.onclick = function(){}; //点击了这个按钮就可以调用这个函数
⑤定时器函数
setInterval(function(){},1000); // 这个函数是定时器自动1秒钟调用一次
⑥立即执行函数
(function(){
})(); // 立即执行函数是自动调用
(二)、this
2.1 函数内this的指向
这些 this 的指向,是当我们调用函数的时候确定的。 调用方式的不同决定了this 的指向不同一般指向我们的调用者。
2.2 改变函数内部this指向
JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有 bind()、call()、apply() 三种方法。
①call方法
call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.call(thisArg, arg1, arg2, ...)
-
thisArg:在 fun 函数运行时指定的 this 值
-
arg1,arg2:传递的其他参数
-
返回值就是函数的返回值,因为它就是调用函数
-
因此当我们想改变 this 指向,同时想调用这个函数的时候,可以使用 call,比如继承
var obj = {
name:'hk'
}
function fn(a,b) {
console.log(a+b);
}
fn.call(obj,1,2)
function Father(uname,age,sex){
this.uname = uname;
this.age = age;
this.sex = sex;
}
function Son(uname,age,sex){
Father.call(this,uname,age,sex);
}
var son = new Son('刘德华',15,'男');
console.log(son);
②apply方法
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
fun.apply(thisArg, [argsArray])
-
thisArg:在fun函数运行时指定的 this 值
-
argsArray:传递的值,必须包含在数组里面
-
返回值就是函数的返回值,因为它就是调用函数
-
因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
var obj = {
name:'study'
};
function fn(arr){
console.log(arr); // 打印的不是一个数组,而是字符串形式 'student'
}
fn.apply(obj,['student']);
// apply的主要应用:比如说我们可以利用apply借助于数学内置对象求最大值
var arr = [1,66,3,99,4]
// Math.max();是用来求数字的最大值
// var max = Math.max.apply(null,arr); apply如果是字符串就转换为字符串,数字就转换为数字
var max = Math.max.apply(Math,arr);
console.log(max); // 99
③bind方法
bind() 方法不会调用函数。但是能改变函数内部this 指向
fun.bind(thisArg, arg1, arg2, ...)
-
thisArg:在 fun 函数运行时指定的 this 值
-
arg1,arg2:传递的其他参数
-
返回由指定的 this 值和初始化参数改造的原函数拷贝
-
因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind
// bind() 绑定捆绑的意思
// 1、不会调用原来的函数,可以改变原来函数内部的this指向
// 2、返回的是原函数改变this之后产生的新函数
// 3、如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
// 4、我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
var obj = {
name:'study'
}
function fn (a,b) {
console.log(a+b);
}
var f = fn.find(obj,1,2);
f();
bind()方法的应用
<button>点击</button>
<script>
var btn = document.querySelector('button');
btn.onclick = function() {
this.disabled = true; //这个this指向的是btn这个按钮 点击禁用
setTimeout(function(){
// this.disabled = false; 此时定时器里面的this指向的是window
this.disabled = false;
}.bind(this),3000); //这个this指向的是btn这个对象
}
// 当有多个按钮时可以这么写
var btns = document.querySelectorAll('button');
for(var i = 0; i < btns.length; i++) {
btn[i].onclick = function(){
this.disabled = true;
setTimeout(function(){
this.disabled = false;
}.bind(this),3000)
}
}
class Tab {
constructor(id) {
// 获取大盒子元素
that = this;
this.main = document.querySelector(id);
// 只要new一下Tab就会自动调用constructor,所以放在里面会更好
// 获取+为添加功能做准备
this.add = this.main.querySelector(".tabadd");
// 获取ul元素,为创建元素追加做准备
// li父元素
this.ul = this.main.querySelector(".fisrstnav ul:first-child");
// section父元素
this.fsection = this.main.querySelector(".tabscon");
this.init();
}
// 初始化,让项目一启动就显示的页面
// 初始化操作让相关的元素绑定事件
init() {
this.updatedNode();
// (1)给tab切换绑定点击事件
for (var i = 0; i < this.lis.length; i++) {
// 需要每个列表项的索引号li
this.lis[i].index = i;
// 点击调用切换功能
this.lis[i].onclick = this.toggleTab.bind(this.lis[i], this);
// 给所有×号添加点击事件
this.remove[i].onclick = this.removeTab.bind(this.remove[i], this);
// 双击点击事件ondblclick
this.spans[i].ondblclick = this.editTab;
this.sections[i].ondblclick = this.editTab;
}
// 给+号添加点击事件
this.add.onclick = this.addTab.bind(this.add, this);
}
// 因为我们动态添加元素,需要重新获取对应的元素
// 获取所有的li和section
updatedNode() {
// 或取盒子里面的列表项li
this.lis = this.main.querySelectorAll("li");
// 获取所有的sections
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(that) {
// 调用全局this来干掉所有人,留下我自己
that.clearClass();
this.className = "liactive";
// 因为this指向的是上一级的init(),而里面没有sections 属性
// 解决办法,声明一个全局变量,拿到sections
// this.sections[this.index].className = "conactive";
that.sections[this.index].className = "conactive";
}
// 定义一个清除所有样式的函数
clearClass() {
for (var i = 0; i < this.lis.length; i++) {
this.lis[i].className = "";
this.sections[i].className = "";
}
}
// 2、添加功能
addTab(that) {
that.clearClass();
// 思路,
// 1、点击+号创建一个li和section
var li =
'<li class="liactive"><span>Tab</span><span class="iconfont icon-guanbi"></span></li>';
var section = '<section class="conactive">tab</section>';
// 2、追加到对应的父元素里面
that.ul.insertAdjacentHTML("beforeend", li);
that.fsection.insertAdjacentHTML("beforeend", section);
that.init();
}
//3、删除功能
removeTab(that, e) {
// 阻止li的冒泡事件
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 的时候,原来的选中状态li保持不变
if (document.querySelector(".liactive")) return;
// 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态
index--;
// 当我们点击×号,减少了一个li和section,显示前一个li的选中样式,手动调用我们的点击事件 不需要鼠标触发
that.lis[index] && that.lis[index].click(); //如果为真,则执行点击事件
}
//4、修改功能
editTab() {
// 拿到文本框里面的文字
var str = this.innerHTML;
// 双击禁止选定文字
window.getSelection
? window.getSelection().removeAllRanges()
: document.selection.empty();
// 双击生成一个文本框
this.innerHTML = '<input type="text"/>';
// 把原来span里面的内容赋值给文本框
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();
}
};
}
}
// 传参大盒子tab,传给constructor的id
var tab = new Tab("#tab");
2.3 call apple 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.1 什么是严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名
3.2 开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
①为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或‘use strict’;)。
<script>
"use strict";
// 下面的代码就按照严格模式进行了
console.log("这是严格模式。");
</script>
因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
<script>
// 立即执行函数开启严格模式
(function (){
"use strict";
var num = 10;
function fn() {}
})();
</script>
②为函数开启严格模式
要给某个函数开启严格模式,需要把“use strict”; (或 'use strict'; ) 声明放在函数体所有语句之前。
// 此时只是给fn函数开启严格模式
function fn(){
"use strict";
return "这是严格模式。";
}
function fun() {
// 里面的还是按照普通模式进行
}
将 "use strict" 放在函数体的第一行,则整个函数以 "严格模式" 运行。
3.3 严格模式中的变化
严格模式对 Javascript 的语法和行为,都做了一些改变。
①变量规定
Ⅰ、在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用。
Ⅱ、严禁删除已经声明变量。例如,delete x; 语法是错误的。
<script>
'use strict';
// 错误用法(1) 我们的变量名必须先声明再使用
num = 10;
console.log(num)
// 错误用法(2) 我们不能随意删除已经声明好的变量
var num = 10;
console.log(num);
delete num;
</script>
②严格模式下this指向问题
Ⅰ、以前在全局作用域函数中的 this 指向 window 对象。
Ⅱ、严格模式下全局作用域中函数中的 this 是 undefined。
Ⅲ、以前构造函数时不加 new也可以 调用,当普通函数,this 指向全局对象
Ⅳ、严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错
Ⅴ、new 实例化的构造函数指向创建的对象实例。
Ⅵ、定时器 this 还是指向 window 。
Ⅶ、事件、对象还是指向调用者。
<script>
'use strict';
// 严格模式下全局作用域函数中的this是undefined
function fn() {
console.log(this); // undefined
}
fn();
// 严格模式下,如果构造函数不加new调用,this会报错
function Star() {
this.sex = '男'; // 因为在严格模式下this指向的是undefined,所以会报错
}
Star();
// 定时器this还是指向window
setTimeout(function(){
console.log(this);
})
</script>
③函数变化
Ⅰ、函数不能有重名的参数。
②、函数必须声明在顶层.新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。
更多严格模式要求参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
// 严格模式下函数里面的参数不允许重名
//错误用法
function fn(a,a){
console.log(a+a);
}
fn(1,2)
(四)、高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
<script>
function fn(callback){
callback&&callback();
}
fn(function(){alert('hi')}
</script>
<script>
function fn(){
return function() {}
}
fn();
</script>
此时fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。 最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
(五)、闭包
5.1 变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
5.2 什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。 ----- JavaScript 高级程序设计。
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
<script>
// 闭包:我们fn2这个函数作用域访问了另外一个函数fn1里面的局部变量num
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 参数 ,这就表明产生了闭包。
// 我们fn外面的作用域可以访问fn内部的局部变量
// 闭包的主要作用:延伸了变量的作用范围
function fn() {
var num = 10;
// function fun() {
// console.log(num);
// }
// return fun;
return function(){
console.log(num);
}
}
var f = fn();
f();
// 类似于
//var f = function fun(){
// console.log(num);
//}
5.4 闭包案例
1.循环注册点击事件。
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
var lis = document.querySelector(".nav").querySelectorAll("li");
// 利用闭包的方式得到当前小li的索引号
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function (i) {
lis[i].onclick = function () {
console.log(i);
};
// 这个i相当于拿到循环的i的参数传递给function(i)
})(i);
}
</script>
2.循环中的 setTimeout()。
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用:3秒钟之后,打印所以li元素的内容
var lis = document.querySelector(".nav").querySelectorAll("li");
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML);
}, 3000);
})(i);
}
</script>
3.计算打车价格。
<script>
// 闭包应用-计算打车价格
// 打车起步价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 (falg) {
return falg ? total + 10 : total;
},
};
})();
console.log(car.price(5));
console.log(car.yd(true));
</script>
5.5 闭包总结
①闭包是什么?
闭包是一个函数 (一个作用域可以访问另外一个函数的局部变量)
②闭包的作用是什么?
延伸变量的作用范围
(六)、递归
6.1 什么是递归?
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解:函数内部自己调用自己, 这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
// 递归函数:函数内部自己调用自己,这个函数就是递归函数
var num = 1;
function fn(){
console.log('打印6次')
if(num == 6){
return; //递归里面必须加退出条件
}
num++
fn();
}
fn();
6.2 利用递归求数学题
1.求 1 * 2 *3 ... * n 阶乘。
function fn(n){
if(n==1){
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3)); //6
// 思路:
// 第一次递归调用 3* fn(2)
// 第二次递归调用 3 * (2 * fn(1))
// 第三次递归调用 3 * (2 * 1)
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)); //2
console.log(fb(6)); // 8
3.根据id返回对应的数据对象
6.3 利用递归求:根据id返回对应的数据对象
<script>
var data = [
{
id: 1,
name: "家电",
goods: [
{
id: 11,
gname: "冰箱",
goods: [
{
id: 111,
gname: "海尔",
},
{
id: 111,
gname: "海尔",
},
],
},
{
id: 12,
gname: "洗衣机",
},
],
},
{
id: 2,
name: "服饰",
},
];
// 我们想要做输入id号,就可以返回的数据对象
// 1、利用forEach去遍历里面的每一个对象
function getID(mydata, id) {
var obj = {};
mydata.forEach((value) => {
if (value.id == id) {
// console.log(value);
obj = value;
// 2、我们想要最里层的数据 11 12可以用递归函数
// value.goods && value.goods.length > 0 如果里面有goods,并且goods的长度大于0
} else if (value.goods && value.goods.length > 0) {
// 再重新调用一次
obj = getID(value.goods, id);
}
});
return obj;
}
console.log(getID(data, 1));
console.log(getID(data, 2));
console.log(getID(data, 11));
console.log(getID(data, 111));
</script>
6.4 浅拷贝和深拷贝
1.浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.
2.深拷贝拷贝多层, 每一级别的数据都会拷贝.
3.Object.assign(target, ...sources) es6 新增方法可以浅拷贝
// 浅拷贝 只是拷贝一层,如果修改了拷贝对象里面的值,原对象也会发生改变,只是拷贝了其地址
var obj = {
id:1,
name:'study',
msg:{
age: 20
}
}
var o ={};
Object.assign(o,obj);
// 深拷贝拷贝多层,每一级别的数据都会拷贝,相当于把原来的数据复制一份,相当于开辟了一个新的空间来存放复制的数据,修改了复制的数据不会影响原数据
var obj = {
id:1,
name:'study',
msg:{
age:20
},
color:['red','yellow','blue']
};
var o = {};
// 递归来实现深拷贝
function deeCopy(newobj,oldobj){
// 遍历的是旧的obj对象
for(var k in oldobj){
// 判断我们的属性值属于那种数据类型
// 1、获取属性值 oldobj[k]
var item = oldobj[k];
// 2、判断这个值是否是数组类型
if(item instanceof Array){
newobj[k] = []; // 相当于o.color = []
deeCopy(newobj[k],item)
}else if (item instanceof Object){
// 3、判断这个值是否是对象
newobj[k] = {};
deeCopy(newobj[k],item)
}else{
// 4、判断是否是简单数据类型
newobj[k] = item;
}
}
}
deeCopy(o,obj);
console.log(o);
九、正则表达式
(一)、正则表达式概述
1.1 什么是正则表达式
正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象。
正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
其他语言也会使用正则表达式,本阶段我们主要是利用 JavaScript 正则表达式完成表单验证。
1.2 正则表达式的特点
①灵活性、逻辑性和功能性非常的强。
②可以迅速地用极简单的方式达到字符串的复杂控制。
③对于刚接触的人来说,比较晦涩难懂。比如: ^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$。
④实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式. 比如用户名: /[1]{3,16}$/
(二)、正则表达式在JavaScript中的使用
2.1 创建正则表达式
在 JavaScript 中,可以通过两种方式创建一个正则表达式。
①通过调用RegExp对象的构造函数创建
var 变量名 = new RegExp(/表达式/);
var regexp = new RegExp(/123/);
②通过字面量创建
var 变量名 = /表达式/;
// 注释中间放表达式就是正则字面量
var rg = /123/;
2.2 测试正则表达式test
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
regexObj.test(str)
var rg = /123/;
console.log(rg.test(123)); // true
console.log(rg.test('abc')); //false
①regexObj 是写的正则表达式
②str 我们要测试的文本
③就是检测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 边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。
如果 ^ 和 $ 在一起,表示必须是精确匹配。
var rg = /abc/; //正则表达式里面不需要加引号,不管是数字型还是字符串型
// /abc/ 只要包含abc这个字符串返回的都是true
console.log(rg.test('abc')); // true
console.log(rg.test('abcd')); // true
console.log(rg.test('abc')); // true
var reg = /^abc/;
console.log(rg.test('abc')); // true
console.log(rg.test('abcd')); // true
console.log(rg.test('aabcd')); //false
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
3.3 字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
①[] 方括号
// /[abc]/ 只要包含有a 或者包含有b 或者包含有b 或者包含有c 都返回为true
/[abc]/.test('andy') // true
var rg = /^[abc]$/; // 三选一,只有是a或者b 或者是c 这三个字母才返回true
console.log(rg.test('aa')); // false
console.log(rg.test('a')); // true
console.log(rg.test('b')); // true
console.log(rg.test('c')); // true
console.log(rg.test('abc')); // false
后面的字符串只要包含 abc 中任意一个字符,都返回 true 。
②[-] 方括号内部 范围符-
// 26个英文子母任何一个字母返回true
/^[a-z]$/.test('c') // true
方括号内部加上 - 表示范围,这里表示 a 到 z 26个英文字母都可以。
③[^] 方括号内部 取反符^
/[^abc]/.test('andy') // false
// 就是相当于不能包含字符串abc
方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false。
注意和边界符 ^ 区别,边界符写到方括号外面。
④字符组合
// 26个英文字母(大写和小写都可以)任何一个字母返回true,或者_或者-都可以
var reg = /^[a-zA-Z0-9_-]$/;
/[a-z1-9]/.test('andy') // true
方括号内部可以使用字符组合,这里表示包含 a 到 z 的26个英文字母和 1 到 9 的数字都可以。
3.4 量词符
量词符用来设定某个模式出现的次数。
var reg = /^a*$/ //重复出现零次或多次
console.log(reg.test('')); // true
console.log(reg.test('a')); // true
console.log(reg.test('aa')); // true
var reg1 = /^a+$/; // 重复出现一次或更多次
console.log(reg1.test('')); //false
console.log(reg1.test('a')); // true
console.log(reg1.test('aa')); // true
var reg2 = /^a?$/; 重复零次或者一次 0||1
console.log(reg2.test('')); // true
console.log(reg2.test('a')); // true
console.log(reg2.test('aaa')); // false
var reg3 = /^a{3}$/; {3}重复3次,只能为3次
console.log(reg3.test('')); // false
console.log(reg3.test('a')); // false
console.log(reg3.test('aaa')); // true
var reg4 = /^a{3,}$/; {3,}重复大于等于3次
console.log(reg3.test('a')); // false
console.log(reg3.test('aaa')); // true
console.log(reg3.test('aaaa')); // true
var reg3 = /^a{3,16}$/; {3,16}重复大于等于3并且小于等于16
console.log(reg3.test('')); // false
console.log(reg3.test('aaaa')); // true
console.log(reg3.test('aaaaaaaaaaaaaaaa')); // true
3.5 案例:用户名验证
功能需求:
1.如果用户名输入合法, 则后面提示信息为 : 用户名合法,并且颜色为绿色
2.如果用户名输入不合法, 则后面提示信息为: 用户名不符合规范, 并且颜色为绿色
分析:
1.用户名只能为英文字母,数字,下划线或者短横线组成, 并且用户名长度为 6~16位.
2.首先准备好这种正则表达式模式 /$[a-zA-Z0-9-_]{6,16}^/
3.当表单失去焦点就开始验证.
4.如果符合正则规范, 则让后面的span标签添加 right 类.
5.如果不符合正则规范, 则让后面的span标签添加 wrong 类.
<input type="text" class="uname" /> <span>请输入用户名</span>
<script>
var uname = document.querySelector(".uname");
var span = document.querySelector("span");
var reg = /^[a-zA-Z0-9_-]{6,16}$/;
uname.onblur = function () {
// 鼠标移开开始验证 正确的
if (reg.test(this.value)) {
span.className = "right";
span.innerHTML = "用户名格式输入正确";
} else {
// 错误的
span.className = "wrong";
span.innerHTML = "用户名格式输入不正确";
}
};
</script>
3.6 括号总结
1.大括号 量词符. 里面表示重复次数
var reg = /^abc{3}$/ // 它只是让c重复3次 abccc
2.中括号 字符集合。匹配方括号中的任意字符.
var reg = /^[abc]$/ // a也可以 b也可以 c也可以 a||b||c 多选一
3.小括号 表示优先级
var reg = /^(abc){3}$/; // 它是让abc重复三次
可以在线测试: https://c.runoob.com/
3.7 预定义类
预定义类指的是某些常见模式的简写方式。
3.8 案例:表单验证
分析:
1.手机号码: /^1[3|4|5|7|8][0-9]{9}$/
2.QQ: [1-9][0-9]{4,} (腾讯QQ号从10000开始)
3.昵称是中文: [2]{2,8}$
window.onload = function () {
// /^$/精确匹配 1为1开头 [3|4|5|7|8] 可以为3或4或5或7或8 \d{9}随机9为数字
var regtel = /^1[3|4|5|7|8]\d{9}$/; //手机号码的正则表达式
var tel = document.querySelector("#tel");
// [1-9]开头数字为1-9 \d{4,} 大于等于四位数
var regqq = /^[1-9]\d{4,}$/;
var qq = document.querySelector("#qq");
// [\u4e00-\u9fa5]表示输入的是汉字 {2,8}大于等于2位,小于等于8位
var regnc = /^[\u4e00-\u9fa5]{2,8}$/;
var nc = document.querySelector("#nc");
// \d{6} 为任意6位数字
var regmsg = /^\d{6}$/;
var msg = document.querySelector("#msg");
// 为大写字母或者小写字母或者数字或者_和-,长度为大于等于6小于等于16
var regpwd = /^[a-zA-Z0-9_-]{6,16}$/;
var pwd = document.querySelector("#pwd");
var surepwd = document.querySelector("#surepwd");
regxp(tel, regtel); //手机号码
regxp(qq, regqq); //qq
regxp(nc, regnc); //昵称
regxp(msg, regmsg); // 短信验证
regxp(pwd, regpwd); // 密码
function regxp(ele, reg) {
ele.onblur = function () {
if (reg.test(this.value)) {
// nextElementSibling当前表单的下一个元素 span
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> 格式不正确,请重新输入';
}
};
}
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> 两次密码不一致';
}
};
};
(四)、正则表达式中的替换
4.1 replace替换
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp/substr,replacement)
// 以前的替换replace
var str = 'andy和red';
// replace('要替换的参数','替换为')
var newStr = str.replace('andy','baby');
console.log(newStar);
// 最常用的用法,替换敏感词,正则表达式替换
<textarea name="" id="message"></textarea><button>提交</button>
<div></div>
<script>
var text = document.querySelector('textarea');
var btn = document.querySelector("button");
var div = document.querySelector("div");
btn.onclick = function(){
div.innerHTML = text.value.replace(/激情/gi,'**')
}
</script>
1.第一个参数: 被替换的字符串 或者 正则表达式
2.第二个参数: 替换为的字符串
3.返回值是一个替换完毕的新字符串
4.2 正则表达式参数
/表达式/[switch]
switch(也称为修饰符) 按照什么样的模式来匹配. 有三种值:
-
g:全局匹配
-
i:忽略大小写
-
gi:全局匹配 + 忽略大小写
4.3 案例:敏感词过滤
十、ES6简介
(一)、什么是ES6?
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。
ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。
(二)、为什么使用ES6?
每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也有一些令人不满意的地方。
-
变量提升特性增加了程序运行时的不可预测性
-
语法过于松散,实现相同的功能,不同的人可能会写出不同的代码
十一、ES6新增语法
(一)、let
ES6中新增的用于声明变量的关键字。用来替代var
- let声明的变量只在所处于的块级有效
if (true) {
let a = 10; // let a只能在{}里面进行访问,不能在{}外面进行访问
} console.log(a) // a is not defined
注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。 let块级作用域防止循环变量变成全局变量
- 不存在变量提升
console.log(a); // a is not defined 只能先声明再使用不然会报错
let a = 20;
- 暂时性死区
var tmp = 123;
if (true) { // 在块级作用域声明了let变量,会让整个{}里面跟let块级作用域进行绑定 tmp跟外部 var tmp是没有关系的,所以{}里面的会报错,因为是先使用再声明
tmp = 'abc';
let tmp;
}
经典面试题
经典面试题图解:此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。 输出为:2 2 ,var全局作用域,相当于循环完结束之后才执行function函数
经典面试题图解:此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值。 输出为:0 1;let为块级作用域,每次循环都得执行一次function,所以最终对应的是0 1
(二)、const
作用:声明常量,常量就是值(内存地址)不能变化的量。
- 具有块级作用域
if (true) {
const a = 10; // 也是具有块级作用域,只能在{}里面进行使用,外面使用会报错,但是const声明的变量不可修改,相当于只能读
}
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的区别
1、使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
2、使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。
3、使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值。
使用场景:
如果你存储的数据不需要变化,尽量使用const关键字,例如:函数的定义,Π值或者数学公式中一些恒定的值,一些不变的值,因为使用const关键字声明的常量值不能变化,javaScript解析引擎不需要实时监控值的变化,所以const关键字要比let关键字效率高
(四)、解构赋值
ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
1、数组解构
// 叔祖解构允许我们按照一一对应的关系从数字中提取值然后将值赋值给变量
let [a, b, c] = [1, 2, 3];
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
如果解构不成功,变量的值为undefined。
let [foo] = []; // undefined
let [bar, foo] = [1]; //undefined
按照一定模式,从数组中或对象中提取值,将提取出来的值赋值给另外的变量。
2、对象解构
// 对象解构允许我们使用变量的名字匹配对象的属性,匹配成功将对象属性的值赋值给变量
let person = { 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。
const obj = { name: '张三'}
function fn () {
console.log(this);
return () => {
console.log(this)
}
}
const resFn = fn.call(obj); // 因为用call()修改了this 的指向,所以this指向obj
resFn();
// 箭头函数面试题
var age = 100;
var obj = {
age:20,
say:() => { // 因为say:()的上一级{}里面没有构造函数,所以this指向的是全局window作用域,所以执行的是全局变量 var age = 100;
alert(this.age)
}
}
obj.say();
(六)、剩余参数
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
function sum (first, ...args) { // ...表示接收所有的实参
console.log(first); // 10
console.log(args); // [20, 30]
}
sum(10, 20, 30)
const sum = (...args) => {
let total = 0;
args.forEach(item => total += item);
return total;
}
console.log(sum(10,20));
console.log(sum(10,20,30));
剩余参数和解构配合使用
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
十二、ES6的内置对象拓展
(一)、Array的扩展方法
1、扩展运算符(展开语法)
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]; // [1,2,3,4,5]
// 方法二
ary1.push(...ary2);
将类数组或可遍历对象转换为真正的数组
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
2、构造函数方法: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) // [2,4]
3、实例方法:find()
用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let ary = [{
id: 1,
name: '张三‘
}, {
id: 2,
name: '李四‘
}];
let target = ary.find((item, index) => item.id == 2);
4、实例方法:findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
5、实例方法:includes()
表示某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
(二)、String的扩展方法
1、模板字符串
ES6新增的创建字符串的方式,使用反引号定义。
let name = `zhangsan`;
模板字符串中可以解析变量。
let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello, my name is 张三
模板字符串中可以换行
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); // 哈哈哈哈 追不到我吧 我就是这么强大 哈哈哈哈
2、实例方法:startsWith()和endsWith()
-
startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
-
endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
3、实例方法:repeat()
repeat方法表示将原字符串重复n次,返回一个新字符串。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
(三)、Set数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构。
const s = new Set();
console.log(s.size); // 0
Set函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
console.log(set.size); // 4
// 利用set来进行数组去重
const set = new Set([1, 2, 3, 4, 4]);
const arr = [...set];
console.log(arr); // [1,2,3,4]
1、实例方法
-
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值 delete删除方法返回的是一个布尔值
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值
s.clear() // 清除 set 结构中的所有值
2、遍历
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
s.forEach(value => console.log(value))