# javaScript高级
# 1. 核心知识点
- 面向对象编程
- ES6中的类和对象
- 类的继承
- 面向对象案例
# 2. 学习目标
- 能够说出什么是面向对象
- 能够说出出类和对象之间的关系
- 能够使用class创建自定义类
- 能够说出什么是继承
- 能够实现继承的方式
# 3.具体知识点
# 1.编程思想介绍
# 1.1编程思想分类
面向过程(pop)
特点
面向过程就是分析出解决问题所需要的步骤,然后使用函数一步一步实现,然后在使用的时候在一个一个依次调用举例
1. webAPI阶段我们写的程序都是面向过程 2. 实现tab栏导航先分析步骤: 1. 获取元素 2. 给元素注册事件 3. 通过函数实现具体的功能 3. 把大象放冰箱分几步: 1. 打开门 2. 装像 3. 关门
面向对象(oop)
特点
面向对象就是将事物分解为一个一个具体的对象,然后由对象之间分工与合作实现举例
把大象放冰箱如果用面向对象,使用面向对象实现? 1.先找对象 --- 大象对象 和 冰箱对象 2.实现对象中的功能 大象有进去的功能 冰箱对象有开门和关门的功能总结
面向对象是以对象划分功能的,而不是步骤 优点: 代码灵活,易于维护适合多人开发的大型项目特点
封装性
代码封装成一个函数继承性
子继承父多态性
同一个对象,不同时刻具有不同的状态
面向过程和面向对象对比
面向对象
优点: 代码灵活,易于维护,易复用,易扩展 缺点: 性能较低面向过程
优点: 性能较高,容易理解 缺点: 代码灵活性差,复用性低
# 2. ES6中的类和对象
# 2.0 什么是es6?
ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。
ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
# 2.1 ECMAScript发展历史
- 1997 年 ECMAScript 1.0 诞生。
- 1998 年 6 月 ECMAScript 2.0 诞生,包含一些小的更改,用于同步独立的 ISO 国际标准。
- 1999 年 12 月 ECMAScript 3.0诞生,它是一个巨大的成功,在业界得到了广泛的支持,它奠定了 JS 的基本语法,被其后版本完全继承。直到今天,我们一开始学习 JS ,其实就是在学 3.0 版的语法。
- 2000 年的 ECMAScript 4.0 是当下 ES6 的前身,但由于这个版本太过激烈,对 ES 3 做了彻底升级,所以暂时被"和谐"了。
- 2009 年 12 月,ECMAScript 5.0 版正式发布。ECMA 专家组预计 ECMAScript 的第五个版本会在 2013 年中期到 2018 年作为主流的开发标准。2011年6月,ES 5.1 版发布,并且成为 ISO 国际标准。
- 2013 年,ES6 草案冻结,不再添加新的功能,新的功能将被放到 ES7 中;2015年6月, ES6 正式通过,成为国际标准。
- 从2015年开始,每次es的版本都是以年命名 es2015 es2016...
# 2.1为什么要学类和对象?
原因
学类和对象本质上就是为了更好的学习面向对象,面向对象更贴近我们的实际生活,我们可以使用对象描述任何一个具体的事物,因为完事万物皆对象举例
华为P30手机 --- 具体的对象 小米10手机 --- 具体的对象 苹果11手机 --- 具体的对象 手机 ---- 抽象 手机是一个泛指,无法使用具体的手机属性描述,但是手机又具有其他每一台具体手机的特性,那么手机就属于一个大的类别, 在程序中就叫类 人(类) : 中国人, 外国人 动物(类); 爬行类,水路两栖类,飞行类 食物(类): 能吃的,好吃的,难吃的,有毒的....
# 2.2什么是对象?
概念
万物皆对象,对象是一个具体的事物,看的见摸得着.在JS中,一个对象是由一组无序的属性和方法组合而成的一个集合属性
事物的特征,在对象中使用属性表示(名词)方法
事物的行为或者功能,在对象中使用方法表示(动词)
# 2.3什么是类?
通俗理解概念
一类人,一类事物. 类就是将很多具体的对象中公共的部分抽象成一个整体(模板). 本质: 将对象中公共的方法或者属性封装成一个模板 作用: 通过类可以实例化一个对象. 其实我们基础阶段所学的构造函数就可以称为是一个类(构造函数中就是一些公共的信息)类的作用
类的作用和构造函数的作用一样,也是用来创建对象
# 2.4总结类和对象
类
类是一个泛指的,是一个抽象的具有公共属性的集合. 人类(包含了所有人的基本特征,有五官,会吃饭,会搞对象)对象
对象是一个具体的事物,之前说对象是通过构造函数创建的,从今以后大家要注意了,构造函数就是一个特殊的类,对象都是通过类创建的, (类是爹,对象是儿子)
# 3.创建类
# 3.1 语法
class 自定义类名 { }
//创建对象(实例化一个对象)
var 自定义对象 = new 类名()
# 3.2注意事项
- 通过类创建对象必须使用new关键字
- 类的命名规范和构造函数的命名规范一样(帕斯卡命名)
- 创建对象的时候,类名后必须加小括号
# 3.3课堂练习
创建一个明星类 和 对象
class Start { } var ldh = new Start();
# 3.4 如何类中设置属性
通过constructor构造函数设置属性
语法: class 自定义类名 { constructor(形参, 形参) { } }constructor详解
1. constructor 叫构造函数,是类中默认的. 2. constructor 构造函数用来接收创建对象时候传递的参数,创建对象并返回 3. 通过 new + 类名创建对象的时候,程序就会自动调用类中 constructor 4. 如果我们在类中没有定义 constructor 函数,程序在执行的时候也会自动创建的注意事项
在 class 类中所有的函数都不需要写 function 关键字课堂练习演示
创建一个明星类 class Start { //constructor 函数就是用来给对象设置属性的 constructor(uName) { this.uName = uName; } } //实例化一个对象 var ldh = new Start('刘德华'); console.log(ldh);分析代码过程

总结
- 创建类使用 class 关键字,类的自定义名称复合帕斯卡命名法
- 类中的构造函数
constructior用来创建对象并给对象设置属性 - 通过 new 加类名创建对象时候,程序会自动调用
constructior构造函数 - 创建对象使用类名,类中的所有函数都不加
function关键字
# 3.5如何在类中设置方法
语法
class 类名 { //构造函数,设置属性 constructor() { } 方法名() { } }语法详解注意事项
- 在类中直接设置方法,方法名前不需要添加function关键字
- 在类中函数之间不需要有任何的分割符
课堂案例练习
使用类的方式创建几个对象 (父母,男女朋友,喜欢的英雄人物...)
# 3.类继承
# 3.1 什么是继承?
通俗理解
子承父业,孩子继承父辈的资源(钱,权,房子,颜值...), 历史世袭制程序角度理解
子类继承父类中的属性和方法 特别注意: 继承指的是类与类之间的关系,不是对象和类之间的关系,也不是对象和对象之间的关系
# 3.2 为什么学继承?
举例
//中国学生类别 class C_Student { constructor(lg) { this.language = lg; } ks() { alert('考外语'); } } //外国学生类别 class A_Student { constructor(lg) { this.language = lg; } ks() { alert('考汉语'); } } var zs = new C_Student('张三'); var ls = new A_Student('李四');作用
子类继承父类中的方法和属性,减少重复代码
# 3.3如何实现继承?
语法
class 子类名 extends 父类 { }语法详解
- 在es6中类与类之间的继承使用
extends关键字 - 子类继承父类后,子类就可以使用父类中的属性和方法
- 在es6中类与类之间的继承使用
课堂演示类继承
//父类 class Student { ks(ksName) { alert(ksName); } } //子类继承父类 class C_student extends Student {} class A_student extends Student {} var zs = new C_student(); var ls = new A_student(); zs.ks('汉语'); ls.ks('英语');
# 3.4super关键字介绍
代码举例演示
class Father { constructor(uName) { this.uName = uName; } money() { console.log(this.uName + '有100万'); } } class Son extends Father { constructor(uName) { this.uName = uName; //指向的是 子类创建的对象 } } var lz = new Father('我是爹'); lz.money(); // money 中的this 指向的是 父类的对象 var zs = new Son('我是儿子'); zs.money(); //会报错如何解决?
在es6中规定,如果子类继承父类,则需要在子类的构造函数 constructor 中通过 super 关键字来调用父类中的构造函数 class Father { constructor(uName) { this.uName = uName; } money() { console.log(this.uName + '有100万'); } } class Son extends Father { constructor(uName) { super(uName); } } var lz = new Father('我是爹'); lz.money(); // money 中的this 指向的是 父类的对象 var zs = new Son('我是儿子'); zs.money(); //不会报错 super() 会自动调用父类中的构造函数并将对应的值传递给父类super关键字另外一种用法
1. 子类除了在构造函数中通过 super 调用父类的构造函数外 2. 子类还可以通过 super 关键字调用父类中的普通函数 例如: class Father { constructor(uName) { this.uName = uName; } money() { console.log('老子能赚大钱'); } } class Son extends Father{ constructor(uName) { super(uName); } money() { console.log('儿子我也能赚钱'); //通过super关键字调用父类中的普通函数 super.money(); } } var zs = new Son('张三'); zs.money();super关键字总结
- 子类继承父类时候,子类构造函数中必须设置 super关键字(如果子类中没有构造函数则不需要)
- 子类中如果需要调用父类中的普通函数,可以通过 super.函数名()实现调用
- 在继承中方法的执行是按照就近原则执行的,如果子类中有方法就先执行子类中的如果没有就执行父类中的方法
# 3.5 课堂案例
1. 父类中实现一个加法计算,子类继承父类该方法
2. 父类中实现一个加法计算,子类继续扩展一个减法计算
# 3.6super关键字和 this 关键字
代码演示如下:
class Fater { constructor(uName) { this.uName = uName; } money() { alert('老子很有钱'); } } class Son extends Fater{ constructor(uName) { //必须放到this关键字前 super(uName); this.uName = uName; } subMoney() { alert('儿子我能能花钱'); } } var zs = new Son('张三'); zs.subMoney(); //注意: // 1. zs 这个对象要想使用父类中的money方法,就必须使用 extends 关键字继承父类 // 2. 当继承父类的时候,必须在子类的构造函数中使用super调用父类的构造函数 // 3. 使用super调用父类构造函数的的时候必须放到this关键字之前,(老子优先,要尊老爱幼) // 4. 继承程序就是要先执行父类中的构造函数,然后在执行子类中的构造函数,所以必须写到开始位置 zs.money();
# 3.7类使用注意属性总结
在ES6中没有变量提升,所以必须先定义类,然后才能通过类创建对象
类中共有属性或者方法必须加
this调用class Father { constructor(uName) { this.uName = uName; } eat() { alert('正在吃饭'); //报错 //1. uName 是constructor函数中的一个形参,在eat另外一个函数中是无法访问其他函数中的参数的 //2. 修改方式通过 this.uName //3. 访问当前对象身上的属性,要加this console.log(uName); //报错 //1. 要访问当前对象中的方法,也需要加this关键字,dance是属于对象的,所以必须加this //2. 修改方式方法前 设置this关键字 this.dance() dance(); } dance() { alert('吃完饭去跳舞'); } } var zs = new Father('张三'); zs.eat();构造函数中的this 和 方法中的this指向问题
- 构造函数中的this指向的就是当前类创建的对象
- 方法中的this指向当前方法的调用者
# 4. 面向对象版tab栏切换
功能
- 切换功能
- 添加功能
- 关闭功能
- 修改功能
搭建HTML结构设置样式
实现功能思路
将整个tab栏抽象成一个对象
//将tab栏抽象成一个类 class Tab { constructor(id) { //获取当前tab对象 this.tab = document.querySelector(id); //获取所有的li this.lis = document.querySelectorAll('li'); //获取所有的内容盒子 this.content = document.querySelectorAll('.content'); } } //通过类创建tab栏对象 new Tab("#tab");设置一个init方法,初始化给每一个li注册点击事件
constructor(id) { ... //创建对象的时候立即调用 this.init(); } init() { for(var i = 0; i < this.lis.length; i++) { //给每一个li绑定一个索引号 this.lis[i].index = i; //演示给每一个li注册一个事件 this.lis[i].onclick = function() { console.log(this.index); } //当要实现真正的切换功能的时候,要调用切换功能的方法实现 //如下: this.lis[i].onclick = this.toggleTab; } }tab栏对象有切换功能
//that 用来保存构造函数中的this var that; class Tab { constructor(id) { that = this; } init() { this.lis[i].onclick = this.toggleTab; } //切换方法 toggleTab() { //这样写会报错: //原因: this.lis.length 中的this 指向的是当前方法的调用者 li //而当前li是一个具体的元素对象,不是一个伪数组,所以不支持length属性 //这里我们是希望获取构造函数中的this, 所以在类的外部定义一个全局变量保存一下构造函数中的 //this就可以了 for(var i = 0; i < this.lis.length; i++) { this.lis[i].className = ''; } //正确写法: for(var i = 0; i < that.lis.length; i++) { that.lis[i].className = ''; } this.className = 'liactive'; //显示对应的内容 for(var i = 0; i < that.content.length; i++) { that.content[i].style.display = 'none'; } that.content[this.index].style.display = 'block'; } }tab栏对象有添加功能
思路: 1. 点击添加按钮 2. 创建一个 li 和 一个新的内容 2. 将创建好的元素添加到对应的父元素中 具体实现: 1.在构造函数中先获取 添加按钮 constructor(id) { ... //获取添加按钮 this.addBtn = document.querySelector('.tabadd span'); } 2.在init函数中给添加按钮绑定点击事件 init() { // 点击添加 this.addBtn.onclick = function() { } //注意: //init这个函数是在构造函数中调用的,所以这个this 就是构造函数中的this //演示的时候在点击事件后面添加一个匿名函数, //实际操作的时候,要先添加一个 addTab函数,然后调用 //如下: this.addBtn.onclick = this.addTab; } 3. 添加一个 addTab 函数 //创建元素,追加元素,先代码演示 如何通过 insertAdjacentHTML 追加元素 addTab() { //调用清除样式的公共方法 (先自己写,然后抽象成一个这样的方法) that.clearClass(); //1.创建li标签 var li = '<li><span>测试3</span><span class="iconfont icon-guanbi"></span></li>'; //2.将创建好的li添加到ul中(备注: 一定要在构造函数中先获取ul) that.ul.insertAdjacentHTML('beforeend', li); //3.创建内容区域 var div = '<div class="content">新内容</div>'; //4. 将内容区域添加到内容区域中(备注: 一定要在构造函数中获取内容区域) that.tabscon.insertAdjacentHTML('beforeend', div); //5. 添加元素后,后天添加的元素并没有对应的事件,是因为给元素绑定事件的时候,在一开始就做了,当前我们动画创建元素后,元素并没有注册事件,所以需要在点击按钮的时候,需要从新获取一下页面的元素,并重新注册实现就可以解决 //5.1 首先要将构造函数中获取li 和 内容的元素单独剪切到一个函数中 getNode //5.2 在 init函数中调用 getNode这个函数 //5.3 在当前函数中再调用 init函数就可以 that.init(); } 4. 添加一个 clearClass 函数,清除公共样式 clearClass() { for(var i = 0; i < this.lis.length; i++) { this.lis[i].className = ''; this.content[i].style.display = 'none'; } } 5.1 添加一个单独用来获取 li 和 内容的函数 getNode() { //获取所有的li this.lis = document.querySelectorAll('li'); //获取所有的内容盒子 this.content = document.querySelectorAll('.content'); }tab栏对象有删除功能
思路: 1. 点击 删除 按钮,删除对应的li 及内容区域 2. 点击 删除 按钮, 删除按钮的索引号就是其父元素对应的索引号 实现: 1. 在 getNode() 函数中先获取所有的删除按钮 (如果在构造函数中获取,会报错,因为要实时获取页面中的元素) getNode() { //获取所有的li this.lis = document.querySelectorAll('li'); //获取所有的内容盒子 this.content = document.querySelectorAll('.content'); //获取所有的删除按钮 this.delBtns = document.querySelectorAll('.icon-guanbi'); } 2. 在init函数中记得要给每一个删除按钮注册一个点击事件 init() { this.getNode(); // 点击添加 for(var i = 0; i < this.lis.length; i++) { //给每一个li绑定一个索引号 this.lis[i].index = i; //给每一个li注册一个事件, 调用切换功能 this.lis[i].onclick = this.toggleTab; //给每一个删除按钮注册一个点击事件 this.delBtns[i].onclick = this.removeTab; } this.addBtn.onclick = this.addTab; } 3. 定义一个 removeTab 删除函数 removeTab(e) { //首先要阻止事件冒泡 e.stopPropagation(); //获取当前删除按钮的索引 = 父元素的索引 var index = this.parentNode.index; //删除当前li that.lis[index].remove(); //删除当前对应的内容 that.content[index].remove(); //删除后从新给页面中的元素绑定一次事件 that.init(); //问题1: 删除元素后,应该让前一个元素是选中的状态(前一个元素被点击过) //解决如下: index--; that.lis[index].click(); //问题2: 如果用户直接点击的就是第一个,那么会报错,因为第一个索引是0, index-- 就变成-1 //解决如下 index < 0 ? null : that.lis[index].click(); //问题3: 如果用户点击的不是当前选中的,那么就不需要修改样式了,直接将当前元素移除 //解决如下: if(document.querySelector('.liactive')) { return; }else { //删除当前,前一个元素设置选中状态 index--; index < 0 ? null : that.lis[index].click(); } }tab栏对象有编辑功能
思路: 1. 双击实现编辑, 双击事件 ondblclick 2. 双击禁止选中文字 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); css实现禁止文字选中: -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Chrome/Safari/Opera */ -khtml-user-select: none; /* Konqueror */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; 3. 双击时候,动态创建一个输入框, 将当前标签中的文字赋值给输入框 4. 当鼠标离开时候,将输入框中的值赋值给标签 实现: 1. 在 getNode() 中 获取所有的 span (不能写到构造函数中) getNode() { ... //获取li 中所有第一个span this.spans = document.querySelectorAll('.firstnav li span:nth-child(1)'); } 2.在 init() 函数中给每一个span 标签注册双击事件 init() { for(var i = 0; i < this.lis.length; i++) { ... //每一个span标签注册双击事件 this.spans[i].ondblclick = that.editTab; } } 3.新建一个 editTab函数 editTab() { //1.阻止双击选中文字 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); //2. 获取span标签中的文字 var value = this.innerHTML; //3. 创建一个input 标签,并赋值 var input = '<input type="text" value='+value+'>'; //4. 将input 标签添加到span标签中 this.innerHTML = input; //5.双击标签时候,默认选中输入框的文字 var input1 = this.children[0]; input1.select(); //6. 当鼠标离开时候,将输入框中的值赋值给span标签 input1.onblur = function() { this.parentNode.innerHTML = this.value; } //7. 当按下回车时候,当输入框中的值赋值给span标签 input1.onkeyup = function(e) { if(e.keyCode == 13) { this.blur(); } } }
# javaScript高级
# 1. 核心知识点
- 构造函数原理
- 原型
- 构造函数+原型实现继承
# 2. 学习目标
- 能够使用构造函数创建对象
- 能够说出原型的作用
- 能够说出访问成员的规则
- 能够使用ES5新增的方法
# 3. 内容介绍
# 1. 构造函数和原型
# 1.1概述
- 昨天学习了面向对象,在js中通过类创建对象,通过 extends 实现类的继承
- 在es5之前 js 并没有类的概念,接下来我们学习一下 构造函数 和 原型实现面向对象 + 继承
# 1.2回顾创建对象方式
通过字面量方式创建对象
var obj = {}通过内置构造函数创建对象
var obj = new Object();通过自定义构造函数创建对象
function Start(uName) { this.uName = uName; this.sing = function() { console.log('正在唱歌...'); } } var ldh = new Start('刘德华'); var zxy = new Start('张学友');构造函数
1. 构造函数是一个特殊的函数,用来创建对象 2. 构造函数使用 new 关键字创建对象 3. 构造函数创建对象的4件事情
# 1.3 静态成员和实例成员
成员
构造函数中的方法和属性都叫成员 例如: function People(uName) { this.uName = uName; this.sing = function() { } } uName属性就是一个成员 sing 方法就是一个成员 构造函数中的成员分为: 实例成员 和 静态成员实例成员
实例成员就是构造函数内部,通过this关键字添加(绑定的)成员, 比如 uName, sing 实例成员的特点是: 实例成员只能通过实例化的对象才能访问 例如: function People(uName) { this.uName = uName; this.sing = function() { } } //要访问 uName实例成员,必须先实例化对象,如下所示: var ldh = new People('刘德华'); ldh.uName静态成员
静态成员,在构造函数身上直接添加的成员 例如: function People(uName) { this.uName = uName; this.sing = function() { } } // gender 就是静态成员 People.gender = '男'; 静态成员特点: 静态成员只能通过构造函数访问,不能通过实例成员访问
# 2.构造函数的问题
# 2.1 构造函数存在浪费内存的问题
通过构造函数可以很方便的创建出一个或者多个对象
但是在创建对象的过程中存在严重的内存浪费
代码举例: function People(uName) { this.uName = uName; this.sing = function() { } } var ldh = new People('刘德华'); var zxy = new People('张学友'); 1. ldh 和 zxy 这两个对象都有一个公共的方法 sing 2. 通过打印对边这两个 sing的结果不相等 console.log(ldh.sing === zxy.sing) 3. 原因就是 sing 是一个方法,也是一个对象,那么会在内存中重新开辟新的空间位置
# 2.2 如何解决对象使用同一个函数,不开辟新的内存空间?
通过原型对象 prototype 来解决
# 3. prototype介绍
# 3.1 概念
js中规定,每一个构造函数都有一个 prototype属性[该属性本质上就是一个对象]
# 3.2 作用
通过原型对象 prototype 可以用来设置共享的方法,对象的实例就可以使用这些共享的方法
# 3.3 代码演示
function People(uname) {
this.uname = uname;
this.sing = function() {
alert('正在唱歌');
}
}
console.dir(People);

# 3.4 通过原型设置共享方法
function People(uName) {
this.uName = uName;
}
//给原型对象添加公共的方法
//因为prototype 就是一个对象,所以给对象添加属性和方法直接通过点的方式
People.prototype.sing = function() {
alert('我会唱歌');
}
var ldh = new People('刘德华');
var zxy = new People('张学有');
ldh.sing();
zxy.sing();
console.log(ldh.sing === zxy.sing);
# 3.5 总结
- 原型是什么? 原型是一个对象 prototype
- 原型的作用是什么? 设置公共方法的
- 一般情况下公共属性定义到构造函数中,公共的方法定义到原型对象中
- 我们es6中,构造函数中就是在初始化公共的属性,而方法都在构造函数外部
# 4.对象原型__proto__
# 4.1 为什么对象的实例可以访问构造函数原型对象上的方法?
每一个对象都会有一个
__proto__对象,该对象指向的就是构造函数中的prototype代码演示
function People(uname) { this.uname = uname; } People.prototype.sing = function() { alert('我会唱歌'); } var ldh = new People('刘德华'); console.dir(ldh); console.log(ldh.__proto__ === People.prototype); // true
方法查找规则
- 首先先看当前实例对象身上是否有该方法,如果有就执行对象身上的方法
- 如果没有方法,那么对象会通过
__proto__去构造函数原型对象prototype查找
构造函数 原型对象
prototype对象原型__proto__之间的关系
注意事项
prototype 称为原型对象 __proto__ 称为对象原型 (对象原型指向原型对象)
# 5. consturctor构造函数
# 5.1 什么是constructor?
对象原型(__proto__) 和 原型对象(prototype) 中都有一个 constructor属性, constructor称为构造函数
# 5.2 代码演示
function People(uname) {
this.uname = uname;
}
console.dir(People);
var ldh = new People('刘德华');
console.dir(ldh);
//打印
console.log(People.prototype.constructor);
console.log(ldh.__proto__.constructor);
//总结:
constructor 就是用来记录对象通过哪个构造函数创建的,指向原来的构造函数

# 5.3 课堂案例分析
1.
function People(uname) {
this.uname = uname;
}
People.prototype.sing = function() {
alert('正在唱歌');
}
People.prototype.dance = function() {
alert('正在跳舞');
}
var ldh = new People('刘德华');
//1. 如上写法, People.prototype.constructor 指向谁?
console.log(People.prototype.constructor); // 构造函数 People
2.
function People(uname) {
this.uname = uname;
}
People.prototype = {
sing: function() {
alert('正在唱歌..');
},
dance: function() {
alert('正在跳舞..');
}
}
var ldh = new People('刘德华');
//1. 如上写法, People 原型对象构造函数指向谁? // Object()
console.log(People.prototype.constructor);
//原因: 是将一个对象赋值给 People.prototype. 而我们之前知道字面量创建对象的本质就是内置构造函数创建对象
//2. 如何解决让构造函数指向当前构造函数 People?
People.prototype = {
//利用constructor 指回原来的构造函数
constructor: People,
sing: function() {
alert('正在唱歌..');
},
dance: function() {
alert('正在跳舞..');
}
}
# 6. 构造函数,实例,原型对象之间的关系

- 总结
- 构造函数中原型对象 prototype 用来设置公共的方法
- 原型对象中的构造函数 constructor 指向当前构造函数
- 构造函数创建的实例,实例对象中对象原型
__proto__指向 原型对象prototype, 所以实例对象可以访问构造函数中设置的公共方法 - 实例对象中的
__proto__中的构造函数constructor又指向了构造函数
# 7.原型链
# 7.1. 为什么要学原型链?
分析如下代码:
function People(uname, age) {
this.uname = uname;
this.age = age;
}
var ldh = new People('刘德华', 35);
console.log(ldh.age);
console.log(ldh.age.toString());
//问题: 当前对象 ldh 为什么可以调用toString() 这个方法? 当前对象中也没有toString方法,那是如何做到的?
# 7.2 什么是原型链?
原型链: 多个对象原型 `__proto__` 之间形成的一个链装结构
function People(uname, age) {
this.uname = uname;
this.age = age;
}
var ldh = new People('刘德华', 35);
console.dir(ldh);


# 7.3 回答问题
1. 方法的查找规则,如果当前对象中有方法,则执行该对象的方法,如果没有那么会 沿着 prototype 继续查找
2. 当前对象 ldh没有 toString方法, 那么就 通过 __proto__ 到 原型对象上查找,如果没有
3. 继续沿着 原型对象.__proto__ 向上查找,发现 Object原型对象上有 toString 方法
4. 所以当前对象可以执行toString 方法,这就是原型链带来的好处
# 7.4对象成员查找规则
- 当访问一个对象的属性或者方法的时候,先查找当前这个对象自身有没有该属性或者方法
- 如果没有,就找他的原型
__proto__指向的prototype - 如果还没有就继续找原型对象的原型
__proto__指向的prototype - 依次类推,如果找到Object还没有,那么就返回 null
# 8.原型对象中的this指向
# 8.1 this指向总结
普通函数中的this
function fn() { console.log(this); // this 指向的是window , 调用当前函数是通过 window.fn() }构造函数中的this
function People() { this.name = '张三'; // this 指向的是构造函数最后创建的实例对象 }方法中的this
function People() { this.eat = function() { this; // this 指向当前方法的调用者 -> 构造函数实例对象 } }事件中的this
div.onclick = function() { this; // this 指向当前事件源对象 }原型对象中的this
function People() { } People.prototype.sing = function() { this; // this指向当前方法调用者, 构造函数实例对象 } var ldh = new People(); ldh.sing();
# 9. 通过原型对象应用
# 9.1 通过原型对象扩展方法
//内置对象 数组中有很多方法,但是就是没有求和的方法,通过原型对象学习,我们可以给任意的一个内置对象拓展方法
1. 先打印数组构造函数原型对象
console.log(Array.prototype);
2. 通过原型对象给数组扩展一个求和方法,如下:
Array.prototype.sum = function() {
var sum = 0;
for(var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
var ary = [1, 2, 3];
console.log(ary.sum());
# 10.ES5原生继承原理
# 10.1 es5继承
es5中没有类的概念,不支持 extends 关键字实现继承,但是继承是面向对象的最主要特征,es5中实现继承通过构造函数+原型对象 来模拟实现继承 称为组合继承
# 10.2 学es5原生继承,先学call()
call() 使用一: 可以调用函数
function fn() { alert(24); } fn.call(); //调用函数call() 使用二: 可以修改this的指向
function fn() { console.log(this); // this 默认指向window对象 } //如果要将 fn 中的this指向其他对象, 例如指向 ldh 这个对象,实现如下: var ldh = { name: '刘德华' } fn.call(ldh); // 此时就将fn函数中的this 指向了 ldh 这个对象
# 10.3 利用构造函数继承父类型属性
function Father(uname) {
//this 指向的是Father 创建的对象
this.uname = uname;
}
function Son(uname) {
// this 指向当前Son创建的对象
Father.call(this, '张三');
}
var s1 = new Son('张三');
console.log(s1);
- 分析:
- Father 构造函数中的this 指向的是当前 构造函数创建的实例对象
- Son 构造函数中的this 指向的是当前Son 构造函数创建的实例对象
- 由于Son中没有通过this关键字绑定属性,那么就要借用Father构造函数中的this,但是Father中的this指向的是Father创建的实例
- 通过 call()在调用Father构造函数时候,将Father中的this 指向当前 Son中的this,实现继承
- 通过断点调试演示过程
# 10.4 利用原型对象继承父类型方法
不严谨的继承代码分析演示
function Father(uname) { this.uname = uname; } Father.prototype.money = function() { alert(1230000); } function Son(uname) { //继承父类中的属性 Father.call(this, uname); } var zs = new Son('小张'); console.log(zs); //按照原型链的逻辑,查找方法的规则,当前对象如果没有方法,那么就要通过 __proto__ 去原型对象上找,对应的图形
function Father(uname) { this.uname = uname; } Father.prototype.money = function() { alert(1230000); } function Son(uname) { //继承父类中的属性 Father.call(this, uname); } // 将 Father的原型对象赋值给当前 Son 原型对象 Son.prototype = Father.prototype; //分析: //1. 这样Son 实例对象是可以继承父类中的方法 //2. 当给Son 原型对象设置方法的时候,Father 中也有了, 因为 是两个对象的赋值,地址是相同的 Son.prototype.kaos = function() { alert('儿子要参加考试'); } // 3. 发现父类中也有考试这个方法,就是因为 Father原型对象现在和 Son原型对象使用了同一个内存地址导致的 console.log(Father.prototype); //结论: 这样的继承是不规范的,要避免!!!!合理的继承代码演示
function Father(uname) { this.uname = uname; } Father.prototype.money = function() { alert(1230000); } function Son(uname) { //继承父类中的属性 Father.call(this, uname); } Son.prototype = new Father(); var zs = new Son('小张'); //配图分析: //1. Father 实例对象 通过__proto__ 得到 原型对象的方法 //2. 将Father 实例对象赋值给 Son原型对象,那么Son原型对象就可以借用Father 实例对象的 __proto__访问 money 方法 //3.此是单独给 Son原型对象添加方法,也不会影响 Father原型对象了,因为现在已经是两个不同单独的对象了 Son.prototype = new Father(); Son.prototype.kaos = function() { alert('儿子要参加考试'); } //有新的问题出现, 当前 zs 的构造函数指向了Father zs.__proto__.constructor //zs 这个对象是通过 Son 构造函数创建的, zs.__proto__.constructor 应该指向的是 Son构造函数 //思考为什么显示却是 Father 构造函数? //分析: //1. Son.prototype = new Father(); //2. Son.prototype.constructor 就指向了 Father 这个构造函数 //解决: Son.prototype = new Father(); //通过constructor 将构造函数执向当前Son构造函数即可 Son.prototype.constructor = Son; Son.prototype.kaos = function() { alert('儿子要参加考试'); }
# 11.Es6类实现继承,类的本质
类的本质: 就是一个函数 , es6中的类就是构造函数的另外一种写
class People { }
console.log(typeof People);
1. es5 构造函数都有原型对象 ----> es6中类 也有原型对象
//console.log(People.prototype);
2. es5 构造函数原型对象都有 constructor, constructor指向当前构造函数 ----> es6中类也有
//console.log(People.prototype.constructor);
3. es5 构造函数可以通过原型对象添加方法 --- es6 也可以
// People.prototype.sing = function() {alert(123);}
// var zs = new People();
// zs.sing();
4. es5 对象原型__proto__ 指向原型对象 --- es6 也可以
// var zs = new People();
// console.log(zs.__proto__ === People.prototype);
# 12. 新增的方法
数组方法
- forEach 遍历数组
数组名.forEach(function(value, index, arry){ }) // 1. value 代表数组中的值 // 2. index 代表数组中的索引值 // 3. arry 代表当前数组 //案例演示: 通过forEach遍历数组求和 var ary = [1, 2, 3, 4]; var sum = 0; ary.forEach(function(value){ sum += value; }) console.log(sum);filter 筛选数组,返回数组
数组名.filter(function(vale, index, arry){ }) 1. value 代表数组中的值 2. index 代表数组中值的索引 3. 代表当前数组 注意: filter 筛选数组,内部还是要遍历数组的,但是最主要的是用来筛选条件的,将满足条件的返回一个数组 // 代码演示: 将数组中的偶数返回. var ary = [1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 0,]; var newAry = ary.filter(function(value){ return value % 2 == 0; }); console.log(newAry); // 将数组中的非0数字返回 var ary = [1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 0,]; var newAry = ary.filter(function(value){ return value != 0; }); console.log(newAry); //将数组中能够被3整除的数返回 var ary = [1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 0,]; var newAry = ary.filter(function(value){ return value % 3 == 0; }); console.log(newAry);some() 查找数组中是否有满足条件的元素,返回布尔类型
数组名.some(function(value, index, arry){}); 1. value 代表当前数组中的值 2. index 代表当前数组中的索引 3. arry 代表当前数组 //代码演示: 数组中是否有 大于等于20的值 var ary = [1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 0,]; var res = ary.some(function(value){ return value >= 5; }) console.log(res);
# 13. 查询商品案例
根据数据动态显示
准备HTML结构,自己写
在js中先定义数据格式
var data = [ { id: 1, pname: '小米手机', price: 1299 },{ id: 2, pname: '华为手机', price: 4299 },{ id: 3, pname: '华为平板', price: 12999 },{ id: 4, pname: '苹果手机', price: 5299 },{ id: 6, pname: '锤子手机', price: 998 },{ id: 7, pname: '一加手机', price: 4399 },{ id: 8, pname: '诺基亚手机', price: 456 } ]
# javaScript高级
# 1. 核心知识点
- 函数的定义和调用
- this指向
- 严格模式
- 高阶函数
- 闭包
- 递归
# 2. 学习目标
- 能够说出函数的多种定义和调用方式
- 能够说出和改变函数内部this的指向
- 能够说出严格模式的特点
- 能够把函数作为参数和返回值传递
- 能够说出闭包的作用
- 能够说出递归的条件
- 能够说出深拷贝和浅拷贝
# 3. 开始学习
# 1. 函数定义
# 1.1通过function
function fn() {} 命名函数
# 1.2通过函数表达式
var fn = function() {} 匿名函数
# 1.3 通过构造函数定义
var fn = new Function();
1. 介绍
☞ new Function() 构造函数中所有的形参必须都是字符串格式
☞ new Function() 构造函数中,形参个数任意个,但最后一个参数表示函数体
var fn = new Function('a', 'b', 'console.log(123)');
☞ 如果要给函数传递参数,如下写法:
var fn = new Function('a', 'b', 'console.log(a+b)');
fn(1, 2);
//将数字1 传递给形参 a, 数字2传递给形参b
2. 总结:
☞ 所有函数都是 Function() 构造函数的实例
function fn () {}
var fn1 = function () {};
var fn2 = new Function();
console.log(fn instanceof Function);
console.log(fn1 instanceof Function);
console.log(fn2 instanceof Function);
☞ 所有的函数都是一个对象,对象有 __proto__
function fn () {}
var fn1 = function () {};
var fn2 = new Function();
console.dir(fn);
console.dir(fn1);
console.dir(fn2);
☞ 函数之间的三角关系如下图所示:
//代码验证 Function构造函数是否有原型对象
//console.dir(Function);
//代码验证 Function原型对象的构造函数是否指向当前构造函数
//console.log(Function.prototype.constructor);
//代码验证 任何的一个函数是否通过 Function这个构造函数创建的
//var fn = function(){};
//console.log(fn instanceof Function);
//代码验证 函数对象原型是否指向构造函数原型对象
//var fn = function(){};
//console.log(fn.__proto__ === Function.prototype);

# 2. 函数调用方式
# 1.1 普通函数调用
function fn() {}
fn(); //函数名调用
fn.call(); //通过call()调用
# 1.2 对象中方法调用
var o = {
fn: function() {}
}
o.fn() //对象名.函数名()
# 1.3 构造函数调用
function Start() {}
new Start(); // new 关键字调用函数
# 1.4 事件函数调用
事件源.事件类型 = function() {} //事件执行时候调用
# 1.5 定时器函数调用
setInterval(function(){}, 500); // window调用该函数,每隔一段调用一次
# 1.6自调用函数
(function(){})(); //函数自己调用自己
# 3. 函数内部this指向
# 1. 1 总结
this的指向,只要调用函数的时候才能确定,一般this的指向都是函数的调用者
普通函数中的this指向window
构造函数中的this指向实例对象
原型对象中方法this指向实例对象
function Start() {} Start.prototype.sing = function() { this ; // 指向当前方法的调用者,实例对象}方法中this指向当前方法调用者
事件对中this指向当前事件源
定时器函数this指向window对象
自调用函数this指向window对象
# 1.2改变函数内部this指向(es5继承)
call()
call() 作用: 1. 调用函数 function People() { alert(123); } //People.call(); 2. 改变this指向 var o = { myage: 123 } function People() { console.log(this); } // People.call(o); //将o对象赋值给this, 现在this就指向新的对象 o,不再是window对象 3. 通过call() 实现属性继承 function Father(uname) { this.uname = uname; } function Son(uname) { //该构造函数中的this指向当前实例对象, Father.call()调用的时候,将this 赋值给 Father中的this Father.call(this, uname); } var zs = new Son('张三'); console.log(zs);- apply()
apply() 作用: 1. 调用函数 function People() { alert(23); } // People.apply(); 与call的用法一样 2. 改变this指向 var obj = { username: '张三' } function People() { console.log(this); } // People.apply(obj); //调用函数的时候,将obj这个对象赋值给this,所以this现在就执行 obj对象 3. apply() 和 call() 之间的区别 ,后面的参数不一样 // apply() 在传递参数的时候,必须是数组形式的,如下代码所示: function fn(a, b) { console.log(a); console.log(b); console.log(a + b); console.log(this); } //fn.apply(null, [1, 2]); 虽然实参是数组格式,但是 形参 a 和 b 还是分别得到实参中的值 //如果不改变函数内部this指向,可以设置 null // call() 在传递参数的时候,正常设置就可以,如下代码所示: function fn(a, b) { console.log(a); console.log(b); console.log(a + b); console.log(this); } // fn.call(null, 1, 2); 4.apply() 的应用场景最主要的是操作数组,因为 apply的参数就是一个数组 // 例如: 通过apply的方式快速求数组中的最大值 ☞先演示; 通过apply调用Math.max()求一组数字的最大值 //Math.max.apply(null, [1, 2, 3]) ☞在推导演示: 通过apply实现求数组中的最大值 var ary = [1, 2, 20, 4, 5]; Math.max.apply(null, ary); //备注: 当前对象依然要指向Math这个对象,所以建议将 null 改成 Mathbind()
1. bind() 不会调用函数,返回一个函数 //代码演示: function fn() { alert(123); } fn.bind(); // 不会调用函数 //如果要执行该函数,bind的返回值就是一个函数, 执行返回值即可 //var fn1 = fn.bind(); //console.log(fn1); // fn1() 2. bind() 可以改变this指向 var obj = { uname: '张三' } function fn() { console.log(this); } var fn1 = fn.bind(obj); fn1(); // 将obj 对象赋值给 this, this指向的就是当前 obj对象 3. bind() 可以设置参数,参数的设置方式与 call的方式一样 function fn(a, b) { console.log(a + b); } (fn.bind(null, 1, 2))(); // 如果不改变对象的指向,那么就设置unll 4. bind() 引用: 当不需要调用函数,但又要改变this指向,可以使用bind 例如: 页面中点击当前按钮禁用,3秒钟后让该按钮启用 var btns = document.querySelectorAll('input'); for(var i = 0; i < btns.length; i++) { btns[i].onclick = function() { //this,指向当前点击按钮 this.disabled = true; // 3秒钟种后开启 setTimeout(function(){ //错误的写法: setTimeout中的this指向的是window对象 this.disabled = false; }, 3000) } } //分析; 正确写法, 需要将定时器中的this 指向 事件中的this,按钮对象 //定时器自己就会调用,那么这里只需要改变this指向,又不需要调用函数,所以用bind //规律: 要将this改变指向谁,那么就在bind方法中传递谁 //正确写法如下: var btns = document.querySelectorAll('input'); for(var i = 0; i < btns.length; i++) { btns[i].onclick = function() { this.disabled = true; // 3秒钟种后开启 setTimeout(function(){ this.disabled = false; }.bind(this), 3000) } } //分析: bind调用setTimeout的时候, 定时器中的this指向window,现在我们需要将window指向外部的按钮对象this,所以在bind中传递 this, 这个this是事件中的this对象 5. bind() 实现tab栏切换 //必须记住的几个步骤 1. 先将class 外部的 that 注释掉,代码中的所有 that 都要注意 2. 在绑定事件的代码中修改代码如下: for(var i = 0; i < this.lis.length; i++) { //切换要修改 this.lis[i].onclick = this.toggleTab.bind(this.lis[i], this); //删除要修改 this.delBtns[i].onclick = this.removeTab.bind(this.delBtns[i],this); } //添加要修改 this.addBtn.onclick = this.addTab.bind(this.addBtn, this); //修改完成后,必须要在对应的方法上设置一个参数,叫that, 例如: toggleTab(that) { //可以调用一个公共的功能 that.clearClass(); //当前li设置类名 this.className = 'liactive'; that.content[this.index].style.display = 'block'; }
# 1.3 bind,call,apply总结
- 相同点: 都可以改变this指向
- 不同点:
- call 和 apply 都可以调用函数
- bind 不能调用函数
- call 和 apply 的参数不一样
- call 常用来实现继承
- apply 常用来和数组配合使用
# 4.es6严格模式
# 1.0代码模式
☞ HTML结构中有严格模式,松散模式,过渡模式
☞ 不同模式下对代码的规范要求不一样,严格模式最严格,必须按照对应的格式写
https://www.cnblogs.com/shireyhu/p/7825920.html
☞ js设计语法中也包含 严格模式 和 松散模式
☞ 松散模式,代码语法比较宽松,基本上就是想怎么写就怎么写
☞ 严格模式,代码规范比较严格,写出的代码比较正经
# 1.1 严格模式(ie需要10以上兼容)
- 消除了js语法中的不合理,不严谨的地方 (例如: 变量不声明也可以使用)
- 消除代码不安全的问题
- 禁用了一些保留字作为变量名(例如: class , enum ....)
# 1.3严格模式使用
为整个脚步开启严格模式
☞ 在整个js文件的开始位置设置 'use strict'; ☞ 或者在script标签的开始位置 <script> 'use strict'; </script>给函数单独开启严格模式
function fn() { 'use strict'; 后面的代码按照严格模式执行 } function fn1() { 按照普通的代码格式执行 }
# 1.4 严格模式的变化
变量的变化
☞ 在普通模式下,变量不定义也能赋值 ☞ 在严格模式下,变量先声明再使用 'use strict'; num = 123; console.log(num); ☞ 在严格模式下,变量声明后,变量不能被删除 'use strict'; var num = 123; console.log(num); delete num;this指向问题
☞ 在普通模式下, 函数中的this 指向 window ☞ 在严格模式下, 函数中的this 指向 undefined ☞ 在严格模式下, 构造函数如果不加 new 调用会报错,因为 this指向的是 undefined function fn(uname) { this.uname = '张三'; } fn(); ☞ 在严格模式下, 在定时器中的 this 指向的是 window ☞ 在严格模式下, 事件,对象的 this 还是指向当前调用者函数变化
☞ 在严格模式下, 函数的参数不能重名 function fn(a, a) { alert(a + a); } fn(1, 2); ☞ 在严格模式下, 函数必须在顶层, 不能在 条件判断中定义函数,循环中定义函数 if(true) { function fn1() { alert(456); } } fn1(); for(var i = 1; i <= 2; i++) { function fn2() { alert(789); } } fn2();
# 4. 高阶函数
# 1.1 特点
如果函数的参数是一个函数或者返回值是一个返回值: 称为高阶函数
例如: 定时器,函数作为参数 setInterval(function(){ }, 2000); setInterval(fn, 2000); function fn() {} 例如:注册事件,函数作为参数 btn.addEventListener('click', function(){}); btn.addEventListener('click', fn); function fn() {} 例如: bind方法,返回一个函数 function fn() { alert(123); } var fn1 = fn.bind(); //面向对象 + 高阶函数 模拟实现计算器案例 (bind改变this指向)
# 5. 闭包
# 1.1 作用域
全局变量
函数外部定义的变量全局变量局部变量
函数内部定义的变量, 局部变量特点函数调用完成后,局部变量的值随之消失
# 1.2 概念
通俗理解: 闭包, 首先闭包是一种现象; 一个函数能够访问其他函数中的变量
概念:能够访问另一个函数作用域的变量的函数

# 1.3闭包代码演示
//分析如下代码是否有闭包函数
function fn() {
// 局部变量
var sum = 123;
function fn1() {
//局部变量
var res = sum + 123;
return res;
}
return fn1;
}
var res = fn();
var s = res();
console.log(s);
// 总结: 在闭包函数中,返回值是一个函数是最常见的一种写法, 所以闭包函数就是一个高价函数
# 1.4作用
1. 延伸了函数内部变量的作用范围
2. 通过闭包访问其他函数中的变量
# 1.4 应用
闭包获取点击按钮索引
1. 非闭包写法: var lis = document.querySelectorAll('li'); for(var i = 0; i < lis.length; i++) { //这里必须要动态给li添加一个索引,才可以访问到,多一行代码,显的啰嗦 //稍有不慎,同学丢掉这句话就容易报错 lis[i].index = i; lis[i].onclick = function() { console.log(this.index); } } 2. 闭包写法: for(var i = 0; i < lis.length; i++) { (function(i){ lis[i].onclick = function() { console.log(i); } })(i) }3秒之后打印所有元素的内容
1. 错误的写法,并分析为什么 // 3秒之后,外层的for循环早已结束, i 变成了4,所以会报错 var lis = document.querySelectorAll('li'); for(var i = 0; i < lis.length; i++) { setTimeout(function(){ console.log(lis[i].innerHTML); }, 3000) } 2. 闭包写法解决: var lis = document.querySelectorAll('li'); for(var i = 0; i < lis.length; i++) { (function(i){ setTimeout(function(){ console.log(lis[i].innerHTML); }, 3000) })(i) }计算打车价格案例
1. 起步加 13 (3公里) 2. 每多一公里增加 5元 用户输入公里计算总结 3. 如果遇到拥堵,总价多收10元思考题:
非闭包案例
var name = "我是全局变量哦"; var object = { name: '我是局部变量哦', getName: function() { return function() { console.log(this.name); } } } object.getName()();闭包案例
var name = "我是全局变量"; var object = { name: '我是局部变量', getName: function() { var that = this; return function() { console.log(that.name); } } } object.getName()();
# 1.5 闭包总结
概念
闭包: 闭包就是函数的一种现象, 在一个作用域中访问另外一个函数中变量的函数作用
1. 闭包的存在,可以让局部变量也能访问 2. 延长了数据的生命周期
# 6. 递归
# 1.1 概念
如果一个函数内部调用函数本身就叫递归函数
本质: 函数内部自己调用自己
function fn() {
fn();
}
fn();
//避免栈溢出,要写结束条件(和循环一样)
# 1.2 案例
求 1 - n 的阶乘
1. 非递归的版本 function fn(n) { var sum = 1; for(var i = 1; i <= n; i++) { sum *= i; } return sum; } console.log(fn(5)); 2. 递归版本 function fn(n) { if(n == 1) { return 1; } return n * fn(n-1); } fn(2);求斐波那契数列案例
1, 1, 2, 3, 5, 8, 13, 21 ... 第一项和第二项的和是第三项 求第n个数字的斐波那契数列 function fn(n) { if(n == 1 || n == 2) { return 1; } return fn(n-1) + fn(n - 2) } fn(4);根据id遍历对象
var data = [ { id: 1, name: '家电', goods: [{ id: 11, name: '冰箱' },{ id:12, name: '洗衣机' }] },{ id: 2, name: '服装' } ]
# 7. 拷贝
# 1.1 浅拷贝
概念
浅拷贝,拷贝数据一层 或者更深层次的对象只拷贝对象地址的应用 通俗理解: 将一个数据赋值给另外一个数据, 如果其中一个数据发生改变另外一个也发生改变通过Object.assign(target,sources)实现浅拷贝
Object.assign(target,sources) var obj = { uname: '张三', gender: '男', msg: { age: 18 } } var o = {}; Object.assign(o, obj); o.msg.age = 23; console.log(o); console.log(obj);浅拷贝内部实现的原理

# 1.2 深拷贝
概念
将一个数据赋值给另外一个数据, 如果其中一个数据发生改变,另外一个没有改变通过JSON.stringify实现
var obj = { uname: '张三', gender: '男', msg: { age: 18 } } var o = {}; var res = JSON.stringify(obj); // 先将原来的对象转为字符串,字符串是一个简单类型 o = JSON.parse(res); // 然后这个字符串再转化为对象,赋值给 o, 那 o 中就是原来对象中的所有值 o.msg.age = 23; console.log(o); console.log(obj);递归方式实现深拷贝
var obj = { uname: '张三', gender: '男', like : ['吃', '喝'], msg: { age: 18 } } var o = {}; function fn(oldObj, newObj) { //遍历原来的对象 for(key in oldObj) { var item = oldObj[key]; // 如果是数组 if(item instanceof Array) { //当前对象属性就是一个数组 newObj[key] = []; //将当前数组赋值给对象 fn(item, newObj[key]); }else if(item instanceof Object) { //当前对象属性就是一个对象 newObj[key] = {}; fn(item, newObj[key]); }else { newObj[key] = item; } } } fn(obj, o); o.msg.age = 23; console.log(o); console.log(obj); // 拷贝过程中,如果遇到对象,是将对象重复拷贝一份,将最新拷贝的内容复制给新对象
# javaScript高级
# 1.核心知识点
- 正则表达式概念
- 正则表达式js中的使用
- 正则表达式的特殊字符
- 正则表达式的替换
# 2.学习目标
- 能够说出正则表达式的作用
- 能够写出简单的正则表达式
- 能够使用正则表达式对表单验证
- 能够使用正则表达式替换内容
# 3.开始学习
# 1. 正则表达式
# 1.1概念
正则表达式: 使用单个字符串来描述、匹配一系列符合某个句法规则的字符串
在线验证正则表达式:
https://c.runoob.com/front-end/854
# 1.2作用
验证表单
1. 注册信息填写手机号: https://mail.163.com/register/index.htm?from=force/&cmd=register.entrance 2. 注册信息填写身份证号信息: https://apply.bjhjyd.gov.cn/apply/user/person/register.html过滤(替换)页面中的敏感词
提取(京东搜索演示)

# 1.3特点
灵活性,逻辑性强
可以快速实现匹配字符串的控制
实际开发中,一般都是复制写好的正则表达式,能够根据需求会修改即可
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ 邮箱正则表达式
# 2.正则表达式使用
# 1.1 通过RegExp方式创建
var 自定义正则对象 = new RegExp(/表达式/);
注意:
1. 正则是一种检索或者过滤条件的语法
2. 使用的是 //,不能使用其他斜杠
# 1.2通过字面量创建正则表达式
var reg = / /;
注意:
1. 不需要加引号
# 1.3检测正则表达式
正则表达式.test(str);
备注:
test()正则表达式中一个方法,检测是否匹配正则规则,返回布尔类型的结果,true 表示符合 false 表示不符合
例如:
var reg = /123/;
reg.test('123');
# 3. 正则表达式字符
# 1.0正则表达式基本语法介绍
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
简单模式:
var reg = /abc/;
解释: 表示一个值中只要包含连续三个值是abc就可以
例如:
var reg = /abc/;
var str = '123abc123';
console.log(reg.test(str)); //true
var str = 'abc123';
console.log(reg.test(str)); //true
var str = '123abc';
console.log(reg.test(str)); //true
var str = '1a2b3c';
console.log(reg.test(str)); //false
var str = 'aabbcc';
console.log(reg.test(str)); //fasle
# 1.1 边界符^$
^符号
^: 匹配输入的开始,以谁开始 (精确匹配)
例如:
//规则: 以小写字母a开始就可以
var reg = /^a/;
var str = 'Abc';
console.log(reg.test(str)); //false
var str = 'Aabc';
console.log(reg.test(str)); //false
var str = 'abc';
console.log(reg.test(str)); //true
var str = 'aabc';
console.log(reg.test(str)); //true
var str = 'a';
console.log(reg.test(str)); //true
思考:举一反三
//规则: 以数字1开始就可以
var reg = '^1';
var str = 123;
console.log(reg.test(str)); //true
var str = 2123;
console.log(reg.test(str)); //false
//规则: 以字母abc开始就可以
var reg = '^abc';
var str = 'abc';
console.log(reg.test(str)); //true
var str = 'a123b123c';
console.log(reg.test(str)); //false
var str = 'aabbcc';
console.log(reg.test(str)); //false
var str = 'abc123abc';
console.log(reg.test(str)); //true
$$: 匹配输入的结束 例如: // 规则: 匹配以小写字母a结束 var reg = /a$/; var str = 'apple'; console.log(reg.test(str)); //false var str = 'appleA'; console.log(reg.test(str)); //false var str = 'appleaa'; console.log(reg.test(str)); //true var str = 'a'; console.log(reg.test(str)); //true //举一反三: var reg = /中国$/; var str = '我爱你中国'; console.log(reg.test(str)); //true var str = '中国我爱你中国'; console.log(reg.test(str)); //true var str = '中国我爱你'; console.log(reg.test(str)); //false var str = '中我爱你国'; console.log(reg.test(str)); //false思考题:
//规则: 以小写字母a开始,以小写字母a结束 ,那就是字母a,不能有其他的 var reg = /^a$/; var str = 'a'; console.log(reg.test(str)); //true var str = 'aa'; console.log(reg.test(str)); //false //规则: 以abc开始以abc结束,就是abc不能有其他的 var reg = /^abc$/; var str = 'abc'; console.log(reg.test(str)); //true var str = 'aabbcc'; console.log(reg.test(str)); //false var str = '123abc123'; console.log(reg.test(str)); //false //精确适配以上都是
# 1.2字符类[]
[][]: 一个字符集合。匹配方括号中的任意字符 例如: //规则: 匹配 abcd中任意一个字符 var reg = /[abcd]/; var str = 'a'; console.log(reg.test(str)); //true var str = 'b'; console.log(reg.test(str)); //true var str = 'ac'; console.log(reg.test(str)); //true var str = 'bfc'; console.log(reg.test(str)); //true var str = 'efg'; console.log(reg.test(str)); //false var str = 'Abc'; console.log(reg.test(str)); //true var str = 'A'; console.log(reg.test(str)); //false思考题:
//规则: 适配以a,b,c三个中任意一个开始 var reg = /^[abc]/; var str = 'a'; console.log(reg.test(str)); //true var str = 'buffa'; console.log(reg.test(str)); //true var reg = /^[abc]/; var str = 'cccc'; console.log(reg.test(str)); //true //规则: 必须是 a, b, c 三个字母中的一个,有其他的不行, 三选一 var reg = /^[abc]$/; var str = 'cccc'; console.log(reg.test(str)); //false var str = 'a'; console.log(reg.test(str)); //true var str = 'ab'; console.log(reg.test(str)); //false[-][-]: 表示范围 例如: //规则: 匹配 a 到 f 之间的任意一个 var reg = /[a-f]/; var str = 'abcdef'; console.log(reg.test(str)); //true var str = 'ag'; console.log(reg.test(str)); //true var str = 'g'; console.log(reg.test(str)); //false 举一反三: //规则: 以 a - f 中任意一个字母开头 var reg = /^[a-f]/; var str = 'ga'; console.log(reg.test(str)); //false var str = 'afa'; console.log(reg.test(str)); //true var str = 'efg'; console.log(reg.test(str)); //true //规则: 必须是 a - f 中的任意一个字母, 多选一 var reg = /^[a-f]$/; var str = 'efg'; console.log(reg.test(str)); //false var str = 'af'; console.log(reg.test(str)); //false var str = 'c'; console.log(reg.test(str)); //true课堂案例
用户名中包含 a到z 或者 A 到Z 正则如何写? (用户名中必须有一个是字母)
var reg = /[a-zA-Z]/用户名中只能包含 a到z 或者 A到Z 中的一个字母 (用户名必须是一个字母)
var reg = /^[a-zA-Z]$/用户名中包含 a到z 或者 A 到Z 或者 0 - 9 (用户名中至少包含一个字母或者一个数字)
var reg = /[a-zA-Z0-9]/
^取反//规则: 匹配只要不是 abc这三个字母中的任何一个都可以 var reg = /[^abc]/; var str = 'a'; console.log(reg.test(str)); //false var str = 'd'; console.log(reg.test(str)); //true var str = 'def'; console.log(reg.test(str)); //true 举一反三: //规则: 只要不是以 a b c 三个字母中任意一个字母开头就可以 var reg = /^[^abc]/; var str = 'def'; console.log(reg.test(str)); //true var str = 'abc'; console.log(reg.test(str)); //false var str = 'cawq'; console.log(reg.test(str)); //false
# 1.3量词符
*出现次数 >= 0 //匹配: 字母a出现次数 >= 0 备注:要出现只能出现a,不能有其他的字符 var reg = /^a*$/; var str = 'aaa'; console.log(reg.test(str)); //true var str = ''; console.log(reg.test(str)); //true var str = 'bcf'; console.log(reg.test(str)); //false 举一反三: //匹配: a - z 中任意一个字母出现次数 >=0次 var reg = /^[a-z]*$/; var str = '123abcdef123'; console.log(reg.test(str)); //false var str = ''; console.log(reg.test(str)); //true var str = 'aabbcc'; console.log(reg.test(str)); //true+出现次数 >= 1 //匹配: 字母a出现次数 >= 1 要出现只能出现字母a var reg = /^a+$/; var str = 'bcf'; console.log(reg.test(str)); //false var str = ''; console.log(reg.test(str)); //false var str = 'aabb'; console.log(reg.test(str)); //false var str = 'aa'; console.log(reg.test(str)); //true 举一反三: //匹配: a - z 中任意一个字母出现次数最少一次 var reg = /^[a-z]+$/; var str = ''; console.log(reg.test(str)); //false var str = 'abcdef'; console.log(reg.test(str)); //true var str = 'aabbccddeeff'; console.log(reg.test(str)); //true var str = '123abcdef123'; console.log(reg.test(str)); //false?出现次数 1次 或者 0次 // 匹配: 字母a出现1次或者0次 var reg = /^a?$/; var str = 'aa'; console.log(reg.test(str)); //false var str = 'a'; console.log(reg.test(str)); //true var str = ''; console.log(reg.test(str)); //true 举一反三: // 匹配:a - z 之间任何一个字母可以出现一次 或者 0次 var reg = /^[a-z]?$/; var str = 'aa'; console.log(reg.test(str)); //false var str = 'a'; console.log(reg.test(str)); //true var str = ''; console.log(reg.test(str)); //true{n}出现具体n次 //匹配: 字母a必须出现3次 var reg = /^a{3}$/; var str = 'a'; console.log(reg.test(str)); //false var str = 'aa'; console.log(reg.test(str)); //false var str = 'aaa'; console.log(reg.test(str)); // true var str = ''; console.log(reg.test(str)); //false var str = 'bdc'; console.log(reg.test(str)); //false{n,}出现大于等于 n 次 // 匹配: 字母a出现的次数大于等于3 var reg = /^a{3,}$/; var str = 'a'; console.log(reg.test(str)); //false var str = 'aa'; console.log(reg.test(str)); //false var str = 'aaa'; console.log(reg.test(str)); // true var str = 'aaaaaa'; console.log(reg.test(str)); // true{n,m}出现次数大于等于 n 小于等于m // 匹配: 字母a出现的次数大于等于3小于等于4次 var reg = /^a{2,4}$/; var str = 'aaa'; console.log(reg.test(str)); //true var str = 'aaaa'; console.log(reg.test(str)); //true var str = 'aa'; console.log(reg.test(str)); //true var str = 'aaadc'; console.log(reg.test(str)); //false var str = ''; console.log(reg.test(str)); //false 注意: 数字之间不能出现空格 举一反三: //匹配: 字母 a-z 出现总次数等于4 (从字面a-z中只能选4个值) var reg = /^[a-z]{4}$/; var str = 'aaaa'; console.log(reg.test(str)); //true var str = 'aaaabbbb'; console.log(reg.test(str)); //false var str = 'abcdffff'; console.log(reg.test(str)); //false var str = 'hjkl'; console.log(reg.test(str)); //true //匹配: 字母 a - z 出现总次数大于等于4 (从字面a-z中最少选出4个) var reg = /^[a-z]{4,}$/; var str = 'aaa'; console.log(reg.test(str)); //false var str = 'aaaabbbb'; console.log(reg.test(str)); //true var str = 'abcdffff'; console.log(reg.test(str)); //true var str = 'hjkl'; console.log(reg.test(str)); //true //匹配: 字母 a -z 出现次总次数 大于等于2 小于等于5 (从字面a-z中最少选出2个到5个) var reg = /^[a-z]{2,5}$/; var str = 'aaa'; console.log(reg.test(str)); //true var str = 'aaaabbbb'; console.log(reg.test(str)); //false var str = 'hjkl'; console.log(reg.test(str)); //true
# 4. 用户名案例
# 1.1 呈现要求
- 如果用户名输入合法,则提示信息为:用户名合法,颜色为绿色
- 如果用户名输入不合法,提示用户名不符合规范,颜色为红色
# 1.2用户名输入要求
- 用户名只能为英文字母或者数字或下划线组成,且用户名长度为6-16位
# 1.3具体实现
// 要求:用户名只能为英文字母,数字,下划线组成,且用户名长度为6-16位
var reg = /^[a-zA-Z0-9_]{6,16}$/;
//获取当前输入框
var int = document.querySelector('input');
int.onfocus = function() {
this.value = '';
}
int.onblur = function() {
var v = this.value;
if(reg.test(v)) {
this.style.borderColor = 'green';
}else {
this.style.borderColor = 'red';
this.value = '输入有误';
}
}
# 5.预定义类
备注: 使用在线工具演示即可
# 1.1 数字类
\d : 匹配0-9之间的任意一个数字 相当于 [0-9]
\D: 匹配0-9以外的所有字符 相当于[^0-9]
# 1.2 字符类
\w : 匹配任意的字母,数字和下划线 相当于 [a-zA-Z0-9_]
\W : 除所有的字母,数字,和下划线以外的字符 相当于 [^a-zA-Z0-9_]
# 1.3特殊符号
\s: 匹配空格(包含换行符,制表符,空格符等) 相当于[\t\r\n\v\f]
\S: 匹配除特殊字符外 相当于[^\t\r\n\v\f]
备注: \r 表示回车 \n 表示换行 \v 垂直制表符 \f换页符
# 1.4课堂案例
☞ 补充:
| 表示或者的意思
例如:
var reg = /a|b/;
//匹配字串中只要包含字母a 或者 b 即可
var reg = /^a|b$/;
//匹配字符串中以a开始或者以b结束
var reg = /^[a,b]|[c,d]$/;
//匹配字符串中以a或者b开头,或者 以 c或者d结束
var reg = /^[a,b]{2}|[1,2]{3}$/;
//匹配字符串中以字母a或者b开始数量为2个 或者 以数字 1 或者2结束. 数量为3个
var reg = /\d{3}-\d{8}|\d{4}-\d{7}/;
//匹配一个三位数-8位数 或者 一个四位数-7位数
☞ 补充:
() 表示一个整体
var reg = /^foot{2}$/; //匹配字符串中字母t出现2次
var reg = /^(foot){2}$/; //匹配整个单词foot出现2次
//参考答案:
1. 座机正则 010-12345678 或 0313-1234567
var reg = /^([0-9]{3}-[0-9]{8}|[0-9]{4}-[0-9]{7})$/;
var reg = /^(\d{3}-\d{8}|\d{4}-\d{7})$/;
非严谨写法:
var reg = /^(\d{3,4}-\d{7,8})$/;
2.手机号正则: 152-11111111 或者 131-11111111
var reg = /^1[3578][0-9]{9}$/
3. QQ正则(1开始后面最少5位数字最多8位数字)
var reg = /^[1-9][0-9]{5,8}$/;
4. 短信验证码正则(6位数字)
var reg = /^[0-9]{6}$/;
5. 登录密码(字母a-zA-Z0-9_)(最少6位)
var reg = /^[\w\d]{6,}$/;
# 6.正则替换
# 1.1语法
正则对象.replace(当前值,替换为哪个值);
# 1.2正则参数
//[switch]
g : 全局匹配
i: 忽略大小写
gi: 全局忽略大小写
例如:
var str = 'a中国';
var newstr = str.replace(/a/, '爱');
课堂案例:
var tar = document.querySelector('textarea');
var btn = document.querySelector('input');
var div = document.querySelector('div');
btn.onclick = function() {
var v = tar.value;
var reg = /激情|日本|川建国/g;
v = v.replace(reg, '我爱中国');
div.innerHTML = v;
}