JS高级01
回顾上阶段
Javascript组成
- ECMAScript: 基础语法 (变量, 表达式, 循环, 判断, 函数, 对象等)
- DOM: document 操作标签(获取, 增加, 插入, 删除, 移动), 事件(鼠标, 键盘, 表单), 标签属性等, 和标签打交道
- BOM: window对象, 定时器, 计时器, (location, navigator, history)等, 和浏览器打交道
与现阶段区别
之前学的JS基础语法, 这个阶段学习一些高级用法和思想
现在学习:
- 面向过程开发 / 面向对象开发 区别
- 面向对象开发
- 类 - 实例对象
- 继承
- 构造函数 - 了解JS对象底层运作的机制
- 显示原型 prototype
- 隐式原型
__proto__
- 原型链
- 各种函数
- 闭包
- 高阶函数
- 递归函数
- ES6新增语法使用 (现在学的是ES5版本, ES6只是新增了一些关键字和语法)
1. 面向对象编程
面向过程开发
分析项目所需要的步骤
用基础代码, 把这些步骤一步一步实现, 依次执行
面向对象开发
分析项目所需要的对象
用对象调用他们身上的方法
面向过程和对象区别
例1: 把大象装进冰箱
- 面向过程思想:
- 面向对象思想:
- 大象对象
- 走路方法
- 冰箱对象
- 开门方法
- 关门方法
- 大象对象
例2: 造飞机
一个人叠纸飞机, 一步一步来就ok - (面向过程思想编码)
造真飞机, 功能复杂庞大, 代码量多, 需要多个对象参与 - (面向对象思想编码)
* A对象 -> 负责造外壳
* B对象 -> 造发动机
* C对象 -> 造机仓
* D对象 -> 造轮子
* ...
总结
面向过程:小项目
面向对象:多人合作大项目
面向对象使用场景
把某个功能封装成插件, 我们可以把相关代码放到类里, 然后实例化对象出来, 调用, 例如前面学过的Swiper等插件
面向对象就是一种思想, 代码还是JavaScript
类与实例对象
-
什么是类?
类是一个模板, 里面有属性和方法
-
类能干什么?
定义一套模板, 批量产生实例对象
-
什么是实例对象?
new 出来的对象
1.0 类模板 - 属性
记住语法格式: 创建类和实例对象
// ES6新增的class关键字定义类
/*
class 类名 {
constructor (形参1, 形参2) {
this.属性名 = 形参1
this.属性名 = 形参2
}
}
*/
// 定义类
class Star{
constructor (theName, theAge) {
// 添加属性
this.name = theName;
this.age = theAge;
}
}
// 实例化对象
var per1 = new Star("刘德华", 18);
console.log(per1);
var per2 = new Star("张学友", 20);
console.log(per2);
1.1 new的作用
- 创建一个空白对象{}
- 执行并替代constructor里this的值
- 在this(空白对象{})身上添加属性和对应的值
- constructor没有return, 就默认返回这个对象到调用处
class Star{
constructor (theName, theAge) {
this.name = theName;
this.age = theAge;
}
}
// 实例化对象
var per1 = new Star("刘德华", 18);
console.log(per1);
var per2 = new Star("张学友", 20);
console.log(per2);
// 总结:
// 1. 类是模板, 定义属性和方法的地方
// 2. new的作用:
// (1): 创建一个空白对象{}
// (2): 替代constructor里默认this的值
// (3): 执行constructor函数, 在this身上添加属性和值
// (4): 自动返回这个对象{name:值, age:值}
执行顺序和对应关系如图
1.2 类模板 - 方法
类里除了属性以外, 还可以定义方法, 让所有的实例对象都有这个模板里的方法使用
class Star {
constructor(theName, theAge) {
this.name = theName;
this.age = theAge;
}
// 方法写在这里
sing (songName) {
console.log(`${
this.name}会唱${
songName}`);
}
}
// 实例化对象
var per1 = new Star("刘德华", 18);
console.log(per1);
var per2 = new Star("张学友", 20);
console.log(per2);
// 使用属性
console.log(per1.name);
// 使用方法
per1.sing("冰雨");
// 总结:
// 1. 类是模板 - 定义属性和方法
// 2. new 类名() - 创建实例对象
// 3. 对象.属性 - 原地返回属性的值
// 4. 对象.方法() - 调用方法执行(跟普通函数调用一模一样)
1.3 this的指向
this是函数内的隐藏变量, 无需声明直接使用
this变量的值, 在代码执行时, 才能知道里面的值 (运行绑定)
class Star {
constructor(theName, theAge) {
this.name = theName;
this.age = theAge;
}
sing(songName) {
console.log(`${
this.name}会唱${
songName}`);
}
}
var per1 = new Star("刘德华", 18);
per1.sing("冰雨");
var per2 = new Star("张学友", 20);
per2.sing("饿狼传说");
// 总结:
// new 触发constructor , 函数里 this指向实例对象 (特殊记忆)
// 普通函数里, this指向调用者 (口诀通用)
图示如下 - 包括执行流程
1.4 练习
// 练习1: 定义人类Person, 属性有name, sex, 方法有run(方法里打印一句"人类会跑")
class Person {
constructor (tName, tSex) {
// 形参运行时, 用的传入的具体的值
this.name = tName; // 把具体的值, 保存在this对象的属性name上 {name: 值}
this.sex = tSex
}
run () {
console.log("人类会跑");
}
}
// 练习2: 用Person类, 实例化2个对象, 并且传入名字和性别的值, 打印2个实例对象 (本题: 模板, 实例对象之间的关系)
class Person {
constructor (tName, tSex) {
// 形参运行时, 用的传入的具体的值
this.name = tName; // 把具体的值, 保存在this对象的属性name上 {name: 值}
this.sex = tSex
}
}
var per1 = new Person("小黄", "男");
var per2 = new Person("小花", "女");
console.log(per1);
console.log(per2);
// 练习3: 定义学生类Student, 属性有name, sex, hobby, 方法有study(方法里代码: console.log(this.name + “在学习”))
class Student {
constructor (name, sex, hobby) {
this.name = name;
this.sex = sex;
this.hobby = hobby;
}
study () {
console.log(this.name + "在学习");
}
}
// 练习4: 用Student类, 实例化1个对象, 传入名字,性别,爱好, 实例对象调用study方法, 观察打印结果 (本题: 思考this.name, 里的this到底是谁?)
class Student {
constructor (name, sex, hobby) {
this.name = name;
this.sex = sex;
this.hobby = hobby;
}
study () {
console.log(this.name + "在学习");
}
}
var stu = new Student("小明", "男", "打游戏");
stu.study();
// 练习5: 定义工具类Tool, 无属性, 实现2个方法, 一个求3个数的和, 一个求3个数最大值, 方法里都返回最后的结果, 实例化对象, 调用方法, 传参后, 接收返回的结果打印
class Tool {
getSum (num1, num2, num3) {
return num1 + num2 + num3;
}
getMax (numA, numB, numC) {
return Math.max(numA, numB, numC); // Math.max() 会返回最大值在原地, 但是外面调用getMax()方法的地方还要这个最大值, 所以需要return出去
}
}
var toolObj = new Tool();
var result1 = toolObj.getSum(20, 30, 40);
console.log(result1);
console.log(toolObj.getMax(10, 15, 18));
总结: 多个方法, 使用相同的属性值(this.xxx), 则constructor里this.xxx接收, 如果只有函数自己用, 就用形参传递 (无论写哪里都是对的)
2. class更多用法
2.0 继承 - extends
什么是继承
子类的实例对象, 继承父类里的属性和方法
// 1. 父类 - 求和, 求乘积方法
class Father {
constructor(theX, theY) {
this.x = theX;
this.y = theY;
}
getSum() {
console.log(this.x + this.y);
}
getProduct() {
console.log(this.x * this.y);
}
}
// 2. 子类也想继承使用
// 继承语法: class 子类 extends 父类 {}
class Son extends Father {
}
// 3. 实例化子类对象, 调用方法
var son = new Son(10, 29);
son.getSum();
son.getProduct();
console.log(son.x, son.y);
// 总结: 子类的实例对象, 能够继承并使用父类的属性和方法(多亏了extends关键字)
2.1 继承 - super 调用 父类constructor
需求: 子类要实现取随机数方法, 但是也想有求和, 求乘积的方法, 所以继承
class Father {
constructor(theX, theY) {
this.x = theX;
this.y = theY;
}
getSum() {
console.log(this.x + this.y);
}
getProduct() {
console.log(this.x * this.y);
}
}
class Son extends Father {
// 子类自己也有属性
constructor(tx, ty, randNum){
super(tx, ty); // 要执行父类的constructor, 把x和y属性和值绑定上
this.randN = randNum;
}
// 子类自己的方法
getRandom() {
console.log(Math.floor(Math.random() * this.randN));
}
}
var son = new Son(10, 29, 100);
son.getSum();
son.getProduct();
son.getRandom();
console.log(son.x, son.y, son.randN);
// 总结:
// 1. super的作用
// (1): 触发父类(extends后面)的类的constructor函数执行
// (2): 修改父类constructor里的this为当前new的实例对象
// (3): super()执行完父类的constructor后返回到这里, 再执行完子类constructor后返回实例对象到new处
// 2. 如果子类constructor使用this, super必须在constructor的第一行
代码的执行顺序和this的值 (根据序号看)
2.2 继承 - super 调用 父类普通方法(了解)
需求: 子类求随机数的地方, 取值范围要参数1和参数2的乘积作为取值范围
class Father {
constructor(theX, theY) {
this.x = theX;
this.y = theY;
}
getSum() {
console.log(this.x + this.y);
}
getProduct() {
console.log(this.x * this.y);
return this.x * this.y;
}
}
class Son extends Father {
constructor(tx, ty, randNum){
super(tx, ty);
this.randN = randNum;
}
getRandom() {
// 在这里通过 super.父类普通函数名() 即可跳转到父类相应方法执行后, 返回结果到这里
console.log(Math.floor(Math.random() * this.getProduct()));
}
}
var son = new Son(10, 29, 100);
son.getRandom();
// 总结:
// this指向当前函数的调用者
// new触发constructor执行, 改变了this指向, 指向为实例对象
代码执行过程
2.3 练习
/*Person类里属性: name和sex
Person类里方法: run - 打印一个字符串即可
say - 打印当前调用者名字
Student类继承自Person类
Student里属性: hobby
Student里方法: play - 返回拼接的字符串: 调用者名字和爱好
实例化子类对象, 传入3个实际参数的值
提示, 子类里的super()应该把收到的实参值传递给父constructor函数, 把值绑定到实例对象的name和sex属性上
最后调用run和say观察打印结果
调用play方法拿到返回值, 然后打印
*/
class Person {
constructor(name, sex) {
this.name = name;
this.sex = sex;
}
run() {
console.log('abc');
}
say() {
return this.name;
}
}
class Student extends Person {
constructor(name, sex, hobby) {
super(name, sex);
this.hobby = hobby;
}
play() {
return this.name + this.hobby;
}
}
var zn = new Student('张宁', '男', '玩游戏');
zn.run();
console.log(zn.say());
console.log(zn.play());
3. 严格模式
什么是严格模式
JavaScript 除正常模式,还有严格模式(strict mode)
IE10以上支持, 以下被忽略 (不支持时, 当做普通字符串)
目的:
- 消除Javascript语法的不合理、不严谨之处【例如变量,不声明就报错】
- 为未来新版本的Javascript做好铺垫 (未来指的是ES6)
3.0 开启严格模式
开启严格模式:“use strict” (只需要在顶部 - 声明一串字符串即可)
<script>
"use strict"; // 当前脚本开启严格模式
</script>
<script>
function fn(){
'use strict'; // 当前函数内, 开启严格模式
}
</script>
3.1 严格模式规则
严格模式对Javascript的语法和行为,在运行时都做了一些改变
- 变量必须 - 先声明 -再使用
- 全局函数里的this 不再指向 window
- 函数形参, 不能重名
"use strict"
// 1. 变量必须先声明再使用
// function myFn(){
// var a = 10;
// console.log(a);
// }
// myFn();
// 2. 全局函数内, this不再指向window
function myFn() {
console.log(this); // undefined
}
myFn();
setTimeout(function () {
console.log(this); // window
}, 1000);
// 3. 函数形参不能重名
// function fn(a, a){
// console.log(a);
// }
// fn(100, 500);
// 总结:
// 严格模式下, 变量必须先声明再使用, 全局函数this不再指向window
以后我们都使用ES6的语法, 而非严格模式
4. 模板字符串
4.0 模板字符串_基础使用
ES6 - 新定义的字符串
作用: 简化字符串的拼接
使用: 模板字符串必须用 `` 包含 (~那个键), 可以嵌入标签, 变量的部分使用${xxx}
<body>
<div id="one">
</div>
<div id="two">
</div>
<div id="three">
</div>
<script>
// 方式1: dom创建
var oneName = "小明";
var oneDiv = document.getElementById("one");
// 创建p夹着span的
var p = document.createElement("p");
var span = document.createElement("span");
oneDiv.appendChild(p);
p.appendChild(span);
span.innerHTML = oneName;
// 方式2: 用标签字符串拼接
var theName = "小花"
var twoDiv = document.getElementById("two");
twoDiv.innerHTML = "<p><span>" + theName + "</span></p>";
// 方式3: ES6新出的字符串
var threeName = "小黄";
var threeDiv = document.getElementById("three");
threeDiv.innerHTML = `<p>
<span>${
threeName}</span>
</p>`;
// 总结:
// 1. 模板字符串``, 跟单引'', 双引"", 一样都是字符串
// 2. 模板字符串方便拼接标签/还可以回车整理格式
// 3. 模板字符串里使用变量, 必须用${}括起来, 运行时, 会把变量的值放到这个字符串里使用
</script>
</body>
5. class封装tab栏
了解插件如何封装和调用以及复用 - 比如之前, 使用new Swiper()
先演示如何使用定义好的插件 tab.js (new Tab())
5.0 tab栏_标签和样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>案例_封装tab栏_标签和样式准备</title>
<style>
.box {
border: 1px solid #000;
box-sizing: border-box;
}
.box * {
box-sizing: border-box;
}
.box .header {
width: 100%;
height: 50px;
display: flex;
border-bottom: 1px solid #222;
}
.box .header span {
transition: all 0.3s;
background-color: #fff;
line-height: 50px;
text-align: center;
cursor: pointer;
font-size: 16px;
border-right: 1px solid #000;
flex: 1;
}
.box .header span.ac,
.box .header span:hover {
background-color: #ccc;
}
.box .main {
width: 100%;
}
.box .main div {
width: 100%;
text-align: center;
font-size: 60px;
font-weight: 800;
display: none;
}
.box .main div.active {
display: block;
}
</style>
</head>
<body>
<!-- 标签栏容器 -->
<div class="box">
<!-- 导航 -->
<div class="header">
<span class="ac">tab1</span>
<span>tab2</span>
<span>tab3</span>
</div>
<!-- 内容 -->
<div class="main">
<div class="active">内容1</div>
<div>内容2</div>
<div>内容3</div>
</div>
</div>
</body>
</html>
5.1 tab栏_js创建
// 1. 标题数组元素 - 用span装起来 - 生成标签字符串 - 放到header容器内
var tabTitleArr = ["tab1", "tab2", "tab3"];
var spanStr = '';
for (var i = 0; i < tabTitleArr.length; i++) {
// 第一个span要高亮
spanStr += `<span class=${
i == 0 ? 'ac' : ''}>${
tabTitleArr[i]}</span>`;
}
var headerDiv = document.querySelector(".header");
headerDiv.innerHTML = spanStr;
// 2. 内容数组元素 - 用div装起来 - 生成标签字符串 - 放到main容器内
var contentArr = ["内容1", "内容2", "内容3"];
var divStr = ``;
for (var j = 0; j < contentArr.length; j++) {
divStr += `<div class=${
j == 0 ? 'active' : ''}>${
contentArr[j]}</div>`;
}
var mainDiv = document.querySelector(".main");
mainDiv.innerHTML = divStr;
5.2 tab栏_class类定义
我们想要多个tab栏, 所以需要tab栏模板, 封装一个类
// 2. 定义类
class Tab {
constructor(query, configObj) {
// 3. 以前保存在var的变量上, 现在保存在对象的属性上
// 接收容器和配置
this.box = document.querySelector(query);
this.headerDiv = this.box.querySelector(".header");
this.mainDiv = this.box.querySelector(".main");
// 导航数据和正文数据
this.tabTitleArr = configObj.tabTitleArr;
this.contentArr = configObj.contentArr;
// 4. 调用初始化标签方法
this.init();
}
init() {
var spanStr = '';
for (var i = 0; i < this.tabTitleArr.length; i++) {
spanStr += `<span class=${
i == 0 ? 'ac' : ''}>${
this.tabTitleArr[i]}</span>`;
}
this.headerDiv.innerHTML = spanStr;
var divStr = ``;
for (var j = 0; j < this.contentArr.length; j++) {
divStr += `<div class=${
j == 0 ? 'active' : ''}>${
this.contentArr[j]}</div>`;
}
this.mainDiv.innerHTML = divStr;
}
}
// 1. 模拟swiper使用, new 类, 传入标签容器选择器, 传入配置对象
new Tab("#box", {
tabTitleArr: ["tab1", "tab2", "tab3"],
contentArr: ["内容1", "内容2", "<span style='color: red;'>内容3</span>"]
})
5.3 tab栏_点击切换
功能 - 点击切换
var that; // 5. 保存实例对象
class Tab {
constructor(query, configObj) {
that = this; // 保存实例对象
// 接收容器和配置
this.box = document.querySelector(query);
this.headerDiv = this.box.querySelector(".header");
this.mainDiv = this.box.querySelector(".main");
// 导航数据和正文数据
this.tabTitleArr = configObj.tabTitleArr;
this.contentArr = configObj.contentArr;
// 调用初始化标签方法
this.init();
}
init() {
var spanStr = '';
for (var i = 0; i < this.tabTitleArr.length; i++) {
// 2. 自定义属性 - 索引
spanStr += `<span ind="${
i}" class=${
i == 0 ? 'ac' : ''}>${
this.tabTitleArr[i]}</span>`;
}
this.headerDiv.innerHTML = spanStr;
var divStr = ``;
for (var j = 0; j < this.contentArr.length; j++) {
divStr += `<div class=${
j == 0 ? 'active' : ''}>${
this.contentArr[j]}</div>`;
}
this.mainDiv.innerHTML = divStr;
// 1. 事件委托 - header标签 - 点击事件
this.headerDiv.onclick = this.toggleDiv;
}
toggleDiv(ev){
// 3. 获取点击标签的索引值
var ind = ev.target.getAttribute("ind");
// 当前span高亮
var spanList = that.headerDiv.querySelectorAll("span");
for (var i = 0; i < spanList.length; i++) {
spanList[i].className = "";
}
ev.target.className = "ac";
// 4. 索引对应div出现
var divList = that.mainDiv.querySelectorAll("div");
for (var j = 0; j < divList.length; j++) {
divList[j].className = "";
}
divList[ind].className = "active";
}
}
new Tab("#box", {
tabTitleArr: ["tab1", "tab2", "tab3"],
contentArr: ["内容1", "内容2", "<span style='color: red;'>内容3</span>"]
})
5.4 tab栏_编写使用文档
好了, 功能实现后, 可以自己写一个文档, 告诉别人怎么去使用了
- index.css - 样式文件
- index.js - 主要功能
- 使用插件者, 引入index.css和index.js, 然后准备标签结构, 只需要写一个new Tab()传入相关参数就有了tab栏切换的案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tab栏_封装插件</title>
<!-- 把案例的css代码放到css文件夹/index.css中 -->
</head>
<body>
<!-- 引入固定结构+固定类名的标签 (id自己定义一个) -->
<div class="box" id="box">
<div class="header">
</div>
<div class="main">
</div>
</div>
<!-- 把封装的class类, 放入到一个js文件夹/index.js文件中 -->
<script src="./js/index.js"></script>
<script>
// 使用插件的步骤:
// (1): 引入插件封装的样式 (index.css)
// (2): 引入插件的功能代码 (index.js)
// (3): 准备标签结构(固定类名和固定结构)
// (4): 实例化使用, 传入配置
// 直接new 类名() 然后传入相应的参数就可以得到一个tab栏
new Tab("#box", {
tabTitleArr: ["体育", "军事", "娱乐"],
contentArr: ["我是体育页面", "军事内容好啊", "这里可以放图片啊或者完整的标签结构啊, 但是注意用模板字符串哦, 假设我是娱乐新闻"]
})
</script>
</body>
</html>
经验值
问题:ES6类继承,出现语法报错
- 描述:使用ES6对其它类进行继承时,语法报错如下:
- 分析:
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
在访问“ this”或从派生构造函数返回之前,必须在派生类中调用super 构造函数
总结: 在继承其它构造函数之前,需要使用super关键字对父级的形参进行确认;再使用this
- 解决:
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
constructor(x, y) {
super(); // 要在使用this之前使用super()
this.x = x;
}
}
new Son();
- 总结:无论父亲上面有没有属性,只要在子类中构造器函数内部操作this,就需要super 把父级相关的 属性进行确认;
面试题
事件委托以及优缺点
优点:节省内存, 给动态创建的标签绑定事件
缺点:
- 不冒泡,不支持。
- 层级过多,不建议用(建议就近委托)
class、extends是什么,super有什么作用
class - 创建类 - 语法糖
extends - 子类继承父类里的方法, 如果子类没有constructor会使用默认的
super - 子类constructor里, super()确认父类里的属性, 绑定到子类的实例对象上(还有值)
如有不足,请多指教,
未完待续,持续更新!
大家一起进步!