一、面向对象编程介绍
1.2 面向对象的特性:
- 封装性
- 继承性
- 多态性
1.1 面向过程和面向对象的对比
二、ES6中的类和对象
2.1 对象
在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
对象是由属性和方法组成的:
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
2.2 类 class
类抽象了对象的公共部分,他泛指某一大类(class).
对象特指某一个,通过类实例化一个具体的对象
2.3 创建类和对象
类 constructor 构造函数:
constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动给我们创建一个constructor()
示例代码:
//1.创建类 class 创建一个 明星类
class Start {
constructor(uname, age) {
this.uanme = uname;
this.age = age;
}
}
//2.
var ldh = new Start('刘德华', 18);
var zxy = new Start('张学友', 20);
console.log(ldh);
console.log(zxy);
2.4 类中添加方法
(1)我们类里所有的方法函数都不需要加 function
(2) 多个函数方法之间不需要添加逗号分隔
//1.创建类 class 创建一个 明星类
class Start {
//类的共有属性放到 constructor 里面
constructor(uname, age) {
this.uanme = uname;
this.age = age;
}
sing(song) {
// console.log('唱歌');
console.log(this.uanme + song);
}
}
//2. 利用类创建对象 new
var ldh = new Start('刘德华', 18);
var zxy = new Start('张学友', 20);
console.log(ldh);
console.log(zxy);
//(1)我们类里所有的方法函数都不需要加 function
//(2) 多个函数方法之间不需要添加逗号分隔
ldh.sing('冰雨');
zxy.sing('彩虹');
三、类的继承
3.1 关键字 extends
//1.类的继承
class Father {
constructor() {
}
money() {
console.log(100);
}
}
class Son extends Father {
}
var son = new Son();
son.money();
3.2 关键字 super
super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数。
调用父类构造函数:
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
money() {
// console.log(100);
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
// this.x = x;
// this.y = y;
super(x, y);
}
}
var son = new Son(1, 2);
son.money();
调用父类中普通函数:
class Father {
say() {
return '我是爸爸';
}
}
class Son extends Father {
say() {
// console.log('我是儿子');
console.log(super.say());
//susper.say()就是调用父类的普通函数say()
}
}
var son = new Son();
son.say();
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
// 2. 子类中没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
3.3 子类继承父类方法同时扩展自己方法
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x, y) {
// 利用super调用父类的构造函数
// super 必须在子类this之前调用
super(x, y);
this.x = x;
this.y = y;
}
sub() {
console.log(this.x - this.y);
}
}
var son = new Son(5, 3);
son.sub();
son.sum();
super 必须在子类this之前调用。
3.4 使用类的三个注意点:
- 在ES6中没有变量提升,所以必须先定义类,才能通过类实例化对象
- 类里面的共有属性和方法一定要加this使用
- 类里面的this指向。
constructor 里面的this 指向的是创建的实例化对象。
方法里面的this指向的是方法的调用者。
四、构造函数和原型
4.1 构造函数
在Es6之前,对象不是基于类创建的,而是通过构造函数来定义对象和他们的特征。
创建对象的三种方式:
- 对象字面量
- new Object()
- 自定义构造函数
// 1. 对象字面量
var obj1 = {};
// 2. new Object()
var obj2 = new Object();
// 3. 自定义构造函数
function Start(uname, age) {
this.uanme = uname;
this.age = age;
this.sing = function () {
console.log('我在唱歌');
}
}
var ldh = new Start('刘德华', 20);
ldh.sing();
console.log(ldh);
4.2 实例成员和静态成员
- 实例成员:在构造函数内部通过this添加的成员。实例成员只能通过实例化对象进行访问
- 静态成员:在构造函数本身添加的成员。静态成员只能通过构造函数进行访问。
function Start(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我在唱歌');
}
}
var ldh = new Start('刘德华', 20);
//1.实例成员:在构造函数内部通过this添加的成员。uname age sing
//实例成员只能通过实例化对象进行访问
ldh.sing();
console.log(ldh.uname);
// console.log(Start.unmae); //不能通过构造函数来访问实例成员
// 静态成员:在构造函数本身添加的成员。
// 静态成员只能通过构造函数进行访问。
Start.sex = '男';
console.log(Start.sex);
console.log(ldh.sex); //不能通过实例化对象来访问静态成员
4.3 构造函数的问题
- 存在浪费内存的问题
4.4 构造函数原型 prototype
- 构造函数通过原型分配的函数是所有对象所共享的。
每一个构造函数都有一个prototype属性。 - 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
- 原型? 一个对象,我们也称为prototype为原型对象。
- 原型的作用? 共享方法。
function Start(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function () {
// console.log('我在唱歌');
// }
}
Start.prototype.sing = function () {
console.log('我在唱歌')
}
var ldh = new Start('刘德华', 20);
ldh.sing();
var zxy = new Start('张学友', 21);
zxy.sing();
一般情况下,我们将公共属性定义到构造函数里面,将公共的方法定义到prototype原型对象身上。
4.5 对象原型_proto_
对象都会有一个属性_proto_,指向构造函数里的prototype原型对象,之所以我们能使用构造函数里prototype原型对象里的属性和方法,就是因为对象有_proto_原型的存在。
- proto 对象原型和原型对象prototype是等价的
- _proto_对象原型的意义就是在于为对象的查找机制提供一个方向,或者说是一条线路,但是他是一个非标准属性,因此实际开发中,不可以使用这个这个属性,他只是内部指向原型对象prototype.
function Start(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function () {
// console.log('我在唱歌');
// }
}
Start.prototype.sing = function () {
console.log('我在唱歌')
}
var ldh = new Start('刘德华', 20);
var zxy = new Start('张学友', 21);
console.log(ldh.sing() === zxy.sing());//true
console.log(ldh); //对象身上系统自己添加了一个_proto_指向我们的构造函数原型对象prototype
console.log(ldh.__proto__=== Start.prototype);//true
方法的查找规则:首先先看ldh 对象身上是否有sing 方法,如果有就执行这个对象上的sing,如果没有sing 这个方法,因为有_proto_ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法。
4.6 constructor 构造函数
对象原型(proto)和构造函数原型对象(prototype)里面都有一个属性 constructor,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数。
function Start(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function () {
// console.log('我在唱歌');
// }
}
//很多情况下,我们需要手动利用constructor这个属性,指回原来的构造函数
// Start.prototype.sing = function () {
// console.log('我在唱歌')
// }
// Start.prototype.movie =function() {
// console.log('我在演电影');
// }
//当我们将原型对象的方法全部写在一个对象里,就会造成覆盖,这样的话constructor就不在指向原有的构造函数
//解决办法: 需要在原型对象内部借助constructor 属性指向原来的构造函数
Start.prototype = {
constructor: Start,
sing: function () {
console.log('我在唱歌')
},
movie: function () {
console.log('我在演电影');
}
}
var ldh = new Start('刘德华', 20);
var zxy = new Start('张学友', 21);
console.log(ldh.__proto__);
console.log(Start.prototype);
console.log(ldh.__proto__.constructor);
console.log(Start.prototype.constructor);
4.7 构造函数、实例、原型对象三者之间的关系
4.8 原型链
4.9 JavaScript 的成员查找机制(规则)
- 当访问一个对象的属性或者方法时,首先先查找这个对象自身有没有这个属性。
- 如果没有,就查找她的原型(也就是__proto__指向的prototype原型对象)
- 如果还没有,就查找原型对象的原型(Object的原型对象)
- 以此类推,一直找到Object为止(null)
- __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条线路
4.10 原型对象中this指向问题
构造函数和原型对象内部的this 指向的是实例对象。
4.11 扩展内置对象
// 原型对象的应用 扩展内置对象方法
console.log(Array.prototype);
Array.prototype.sum = function () {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
// Array.prototype = {
// sun: function () {
// var sum = 0;
// for (var i = 0; i < this.length; i++) {
// sum += this[i];
// }
// return sum;
// }
// }
var arr = [1, 2, 3];
console.log(arr.sum());
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());
注意: 数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {}, 只能是 Array.prototype.sum = function() {} 的方式
五、继承
ES6之前没有给我们提供extends 继承。我们可以通过 构造函数 + 原型对象
模拟实现继承,被称为 组合继承
5.1 call()
调用这个函数,并且修改函数运行时的this指向。
fn.call(thisArg, arg1, arg2)
- thisArg 当前调用函数的this的指向对象
- arg1, arg2 传递的其他参数
function fn(x, y) {
console.log(this);
console.log(x + y);
};
var obj = {
name: 'andy',
}
fn(); // window
fn.call(obj); // {name: 'andy'} 改变了this指向
fn.call(obj, 1, 2);
5.2 借用构造函数继承父类型属性
核心原理: 通过call() 把父类型的 this 指向子类型的 this,这样就可以实现子类型继承父类型的属性
function Father(uname, age) {
// this 指向父构造函数的实例对象
this.uname = uname;
this.age = age;
}
function Son(uname, age, score) {
// this 指向子构造函数的实例对象
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 20, 100);
console.log(son);
5.3 借用原型对象继承方法
//1.借用构造函数继承父类型属性
// 父构造函数
function Father(uname, age) {
// this 指向父构造函数的实例对象
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () {
console.log('父亲挣钱了');
}
// 子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的实例对象
Father.call(this, uname, age);
this.score = score;
}
//2.借用原型对象继承方法
// 这样直接赋值会有问题,如果直接继承,如果修改了子原型对象,父原型对象也随之改变
// 是因为将子原型对象指向了父原型对象的地址,因此父原型对象会随子原型对象变化
Son.prototype = Father.prototype;
Son.prototype = new Father();
//如果利用对象的形式修改了原型对象, 别忘了利用constructor 指向原来的构造函数
Son.prototype.construstor = Son;
Son.prototype.exam = function () {
console.log('子要考试');
}
var son = new Son('刘德华', 20, 100);
console.log(son);
son.money();
console.log(Father.prototype);
console.log(Son.prototype);
5.4 类的本质
- class的本质还是function
- 类的所有方法都定义到类的prototype属性上
- 类创建的实例里面也有 proto 属性,并且这个属性指向类的prototype原型对象
- 所以ES6的类它的大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象的语法而已
- 所以ES6的类就是语法糖
- 语法糖: 就是一种便捷写法,简单理解,有两种方法可以实现相同的功能,但是一种写法更加清晰、方便 ,那么这个方法就是语法糖。
六、ES5 中的新增方法
6.1 数组方法
迭代(遍历)方法: forEach()
- value :每个数组元素
- index: 每个数组元素的索引
- arry: 数组本身
var arr = [1, 2, 3];
var sum = 0;
arr.forEach(function (value, index, arry) {
console.log('每个数组元素' + value);
console.log('每个数组元素的索引' + index);
console.log('数组本身' + arry);
sum += value;
})
console.log(sum);
迭代(遍历)方法: filter()
- filter() 方法,筛选满足条件的值,返回一个新的数组。
- value :每个数组元素
- index: 每个数组元素的索引
- arry: 数组本身
var arr = [12, 34, 56, 89];
var newArr = arr.filter(function (value, index) {
// return value >= 20;
return value % 2 === 0;
})
console.log(newArr);some
迭代(遍历)方法: some()
- some()方法 用于查找数组中是否有满足条件的元素
- 返回值为布尔值 ,如果存在返回true ,否则返回false
- 检测到第一个满足条件的元素就会停止循环,不再查找
- value :每个数组元素
- index: 每个数组元素的索引
- arry: 数组本身
var arr = [34, 45, 89];
var flag = arr.some(function (value, index) {
return value > 80;
})
console.log(flag);
迭代(遍历)方法: map()
- map() 方法会将数组中元素依次传入方法中,并将方法的返回结果组成新数组返回。
- 传入的function可以有自己的三个形参,currentValue, index,arr分别代表当前元素,元素索引,元素所属数组对象;其中currentValue是必须的。
var numbers = [4, 9, 16, 25];
function myFunction() {
console.log(numbers.map(Math.sqrt));
}
输出结果为:
2,3,4,5
注意: map不会改变原数组,map不会检查空数组
迭代(遍历)方法: every()
- every:用于检测数组所有元素是否都符合指定条件(通过函数提供)
- every和some正好相反:如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测, 如果所有元素都满足条件,则返回 true。
- function形参同上
var ages = [32, 33, 16, 40];
2
3 function checkAdult(age) {
4 return age >= 18;
5 }
6
7 function myFunction() {
8 console.log(ages.every(checkAdult));
9 }
输出结果:false
6.2 字符串方法
trim()方法
- trim() 方法会从一个字符串的两端删除空白字符。
- trim() 方法并不影响字符串本身,它返回的是一个新的字符串
var str = ' hello ';
console.log(str);
var str1 = str.trim();
console.log(str1);
6.3 对象方法
Object.defineProperty() 方法
- Object.defineProperty() 方法 定义新属性或修改原有的属性。
Object.defineProperty(obj, prop, dexcriptor)
- obj :要修改的目标对象
- prop:修改的属性名
- 第三个参数 descriptor 说明: 以对象形式{} 书写
- -value :设置属性的值,默认为undefined
- -writable:值是否可以重写。true | false 默认是 false, 不可重写
- -enumerable:目标属性是否可以被枚举。 true | false , 默认false
- -configurable:目标属性是否可以被删除或者是可以再次修改特性 。 true | false ,默认false
var obj = {
id: 1,
pname: '小米',
price: 1999,
};
//1. 以前传统方式修改对象属性
// obj.pname = "大华";
// obj.price = 99;
// console.log(obj);
//2. 利用Object.defineProperty()修改对象的属性
Object.defineProperty(obj, 'price', {
value: '999',
})
console.log(obj);
//writable :false 不允许修改
Object.defineProperty(obj, 'id', {
writable: false,
});
obj.id = 2;
console.log(obj);
//enumerable :false 属性不能被枚举
Object.defineProperty(obj, 'address', {
value: '河南省',
writable: false,
enumerable: false,
//configurable: false 属性不能被删除 也不能被再次修改
configurable: false,
});
delete obj.address;
console.log(obj);
console.log(Object.keys(obj));
七、函数进阶
7.1 函数的定义和调用
7.1.1 函数的定义
- 自定义函数(命名函数)
- 函数表达式(匿名函数)
- 利用 new Function(‘参数1’, ‘参数2’, ‘函数体’)
- Function 里面参数都必须是字符串格式
- 第三种方式执行效率低,也不方便书写,因此较少使用
- 所有函数都是Function的实例对象
- 函数也属于对象
// 1. 自定义函数(命名函数)
function fn() { };
// 2. 函数表达式(匿名函数)
var f1 = function () { };
// 3. 利用 new Function('参数1', '参数2', '函数体')
var f2 = new Function('a', 'b', 'console.log(a + b)');
f2(1, 2);
7.1.1 函数的调用方式
//1. 普通函数
function fn() {};
fn();
//2. 对象中的方法
var obj = {
sayHi: function(){
console.log('hello');
}
}
obj.sayHi();
//3.构造函数
function Star() {};
new Star();
//4.绑定事件调用
btn.onclick = function(){}; //点击按钮调用
//5.定时器函数
setInterval(function(){}, 1000); //一秒之后调用
//6.立即执行函数
(function(){
console.log(123);
})(); // 立即执行函数是自动调用
7.2 this
7.2.1 函数内 this 指向
7.2.2 改变函数内 this 指向
1. call()方法及其应用
fun. call(thisArg, arg1, arg2,...)
//call 可以调用函数,也可以改变函数内部this指向
var o = {
name: 'andy',
}
function fn(x, y) {
console.log(this);
console.log(x + y);
}
fn.call(o, 2, 3);
//call 的主要作用可以实现继承
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
function Son(uname, age) {
Father.call(this, uname,age);
}
var son = new Son('刘德华', 23);
console.log(son);
2. apply()方法及其应用
apply0方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this指向。
fun. apply (thisArg, [argsArray])
- thisArg :在fun函数运行时指定的this值
- argsArray :传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
var o = {
name: 'andy',
};
function fn(arr) {
console.log(this);
console.log(arr);// 'pink'
}
fn.apply(o);
fn.apply(o, ['pink']);
//apply 调用函数,也可以改变函数内部 this 指向
//但是参数必须是数组(伪数组)
// apply 的主要应用 比如说我们可以利用 apply 借助于数学内置对象求最大值
// Math.max()
var arr = [23, 45, 12, 34];
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
console.log(max);
console.log(min);
3. bind()方法及其应用
bind0.方法不会调用函数。但是能改变函数内部this指向
fun. bind (thisArg, arg1, arg2,...)
- thisArg :在fun函数运行时指定的this值
- arg1 , arg2 :传递的其他参数
- 返回由指定的this值和初始化参数改造的原函数拷贝
<!-- <button>点击</button> -->
<button>1</button>
<button>2</button>
<button>3</button>
<script>
var o = {
name: 'andy'
}
function fn(x, y) {
console.log(this);
console.log(x + y);
}
var f = fn.bind(o, 1, 2)
f();
//1. 不会调用函数 只会改变原来函数内部this指向
//2. 返回的是原函数改变this指向之后的新函数
//3. 应用场景: 如果有的函数我们不需要立即调用,但是又想改变这个函数内部this指向
//4. 示例: 有一个按钮,点击之后禁用,3秒后重新开启
// var btn = document.querySelector('button');
// btn.onclick = function () {
// this.disabled = true; //这个this 指向 btn
// // var that = this;
// setTimeout(function () {
// //定时器里面this 指向 window
// // btn.disabled = false;
// // that.disabled = false;
// this.disabled = false;
// }.bind(this), 3000)
// }
//5.
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
this.disabled = true;
setTimeout(function () {
this.disabled = false;
}.bind(this), 2000);
}
}
</script>
7.2.3 call 、apply 、bind 总结
相同点:
都可以改变函数内部的this指向.
区别点:
-
call和apply会调用函数并且改变函数内部this指向.
-
call 和apply传递的参数不一样, call传递参数aru1, aru…形式apply必须数组形式[arg]3. bind 不会调用函数可以改变函数内部this指向.
主要应用场景:
-
call经常做继承
-
apply 经常跟数组有关系比如借助于数学对象实现数组最大值最小值
-
bind 不调用函数但是还想改变this指向.比如改变定时器内部的this指向.
7.3 严格模式
除了正常模式,还有严格模式。
1、设立严格模式的目的:
- 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为
- 消除代码运行的一些不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的Javascript做好铺垫
"use strict";
2、严格模式如何调用:
2.1 针对整个脚本文件
将"use strict"放在脚本文件的第一行,则整个脚本都将以"严格模式"运行。如果这行语句不在第一行,则无效,整个脚本以"正常模式"运行。
<script>
"use strict";
console.log("这是严格模式。");
</script>
<script>
console.log("这是正常模式。");
</script>
2.2 针对单个函数
将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行。
function strict(){
"use strict";
return "这是严格模式。";
}
function notStrict() {
return "这是正常模式。";
}
2.3 脚本文件的变通写法
因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。
(function (){
"use strict";
// some code here
})();
3. 严格模式中的变化
严格模式对JavaScript 的语法和行为,都做了一些改变。
3.1 变量规定
- 正常模式: 如果一个变量没有声明就赋值,默认是全局变量。严格模式:变量必须先声明在使用。
- 严谨删除已声明的变量。例如 delete x;语法是错误的。
<script>
'use strict'
// 1. 正常模式: 如果一个变量没有声明就赋值,默认是全局变量。严格模式:变量必须先声明在使用。
// num = 10;
// console.log(num);
// 2. 严谨删除已声明的变量。例如 delete x;语法是错误的。
var x = 1;
delete x;
</script>
3.2 严格模式下this指向问题
- 以前全局作用域函数中的 this 指向 window;严格模式下全局作用域函数中的 this 是 undefined。
- 正常模式下,构造函数不加new也可以调用,当普通函数,this 指向 window; 严格模式下,构造函数不加new调用,会报错。
- new 实例化的构造函数指向创建的对象实例。
- 定时器this 指向还是 window。
- 事件、对象还是指向调用者。
'use strict';
// 1. 以前全局作用域函数中的 this 指向 window;严格模式下全局作用域函数中的 this 是 undefined。
function fn() {
console.log(this);
}
fn();
// 2. 正常模式下,构造函数不加new也可以调用,当普通函数,this 指向 window; 严格模式下,构造函数不加new调用,会报错。
function Star() {
this.sex = 'nan';
}
Star();
console.log(window.sex);
// 3. new 实例化的构造函数指向创建的对象实例。
var ldh = new Star();
console.log(ldh.sex);
// 4. 定时器this 指向还是 window。
setTimeout(function () {
console.log(this);
}, 2000)
// 5. 事件、对象还是指向调用者。
3.3 函数变化
- 函数不能有重名的参数
- 函数必须申明在顶层,不允许在非函数的代码块内声明函数。(例如: if里,for里是不允许的,但是在函数里声明函数是可以的)
'use strct';
// 1. 函数不能有重名的参数
function f(a, a,) {
console.log(a + a);
}
f(1, 2);
// 2. 函数必须申明在顶层,不允许在非函数的代码块内声明函数。
if (true) {
function f1() {
console.log(111);
}
}
八、高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
<div></div>
<script>
// 高阶函数 -- 接收函数作为参数
function fn(x, y, callback) {
console.log(x + y);
callback && callback();
}
fn(1, 2, function () {
console.log("我是最后");
})
//
$('div').animate({
left: 500,
}, function () {
$('div').css('backgroundColor', 'puper')
})
</script>
九、闭包
9.1 什么是闭包
闭包 ,一个作用域可以访问另一个函数的局部变量
// 闭包(closure)指有权访问另一个函数作用域的变量
// 此时fn 就是闭包
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
fn();
9.2 闭包的作用
延伸了变量的作用范围。
// 闭包(closure)指有权访问另一个函数作用域的变量
// 此时fn 就是闭包
// 闭包的作用:延伸了变量的作用范围
// 在函数fn 外面的作用域可以访问 fn 内部的作用域
function fn() {
var num = 10;
// function fun() {
// console.log(num);
// }
// fun();
// return fun;
return function fun() {
console.log(num);
}
}
var f = fn();
// 相当于 f = function fun() {
// console.log(num);
// }
f();
9.3 闭包的几个小案例
1. 点击li输出索引号
<body>
<ul class='nav'>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
// 闭包应用---点击li输出当前li的索引号
//1.我们可以利用动态添加属性的方式
var lis = document.querySelector('.nav').querySelectorAll('li');
// for (var i = 0; i < lis.length; i++) {
// lis[i].index = i;
// lis[i].onclick = function () {
// console.log(this.index);
// }
// }
//2.利用闭包的方式得到当前小li 的索引号
for (var i = 0; i < lis.length; i++) {
//利用for循环创建4个立即执行函数
// 立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i的这个变量
(function (i) {
lis[i].onclick = function () {
console.log(i);
}
})(i)
}
</script>
</body>
2. 定时器中的闭包 — 3秒之后将所有小li 打印出来
<body>
<ul class='nav'>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
// 闭包应用---点击li输出当前li的索引号
//1.我们可以利用动态添加属性的方式
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>
</body>
2. 计算打车价格
<script>
// 打车起步价13(3公里内), 之后每多一公里增加 5块钱,用户输入公里数就可以计算打车价格,如果有拥堵情况,总价格多收取10块钱拥堵费
var car = (function () {
var start = 13; //起步价
var total = 0; //总价
return {
//正常总价
price: function (n) {
if (n <= 3) {
total = 13;
} else {
total = start + (n - 3) * 5;
}
return total;
},
// 拥堵总价
yd: function (flag) {
total = flag ? total + 10 : total;
return total;
},
}
})()
console.log(car.price(5)); //23
console.log(car.yd(true)); //33
console.log(car.price(1)); //13
console.log(car.yd(false));//13
</script>
十、递归
10.1 什么是递归
函数内部自己调用自己,那么这个函数就是递归函数。
与循环相似,防止栈溢出错误,因此递归必须加退出条件。
var num = 1;
function fn() {
console.log('我要输出6次');
if (num === 6) return;
num++;
fn();
}
fn();
10.2 利用递归求阶乘
求 123…*n
var count = 1;
var total = 1;
function fn(n) {
if (n === 1) return 1;
return n * (n - 1);
}
console.log(fn(3));
10.3 利用递归求斐波拉契
F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
兔子序列1、1、2、3、5、8、13、21
function fn(n) {
if (n === 1 || n === 2) return 1;
return fn(n - 1) + fn(n - 2)
}
console.log(fn(3));
10.4 利用递归遍历数据
<script>
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔冰箱',
}, {
id: 112,
gname: '美的冰箱',
}]
}, {
id: 12,
gname: '洗衣机',
}]
}, {
id: 2,
name: '服饰',
}];
//我们想要做输入id号,就可以返回的数据对象
// 利用forEach 去遍历里面的每一个对象
function getID(json, id) {
var o = {};
json.forEach(item => {
if (item.id == id)
// console.log(item);
o = item;
else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id);
}
});
return o;
}
console.log(getID(data, 1));
console.log(getID(data, 2));
console.log(getID(data, 11));
console.log(getID(data, 12));
console.log(getID(data, 111));
console.log(getID(data, 112));
</script>
十一、浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。
- 深拷贝拷贝多层,每一级别的数据都会拷贝。
- 利用 Object.assign(target, …source) 可以实现浅拷贝,是es6新增方法。
浅拷贝代码:
// 1. 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。
// 2. 深拷贝拷贝多层,每一级别的数据都会拷贝。
// 3. 利用 Object.assign(target, ...source) 可以实现浅拷贝,是es6新增方法。
var obj = {
id: 1,
name: 'andy',
msg: {
age: 20,
}
}
var o = {};
// for (var k in obj) {
// // k 属性名 obj[k] 属性值
// o[k] = obj[k];
// }
// console.log(o);
// o.msg.age = 200;
Object.assign(o, obj);
console.log(o);
console.log(obj);
深拷贝代码:
var obj = {
id: 1,
name: 'andy',
msg: {
age: 20,
},
color: ['pink', 'purple'],
}
var o = {};
function deepCopy(newObj, oldObj) {
for (var k in oldObj) {
//判断属性值的类型 oldObj[k]
var item = oldObj[k];
//1.属于数组
if (item instanceof Array) {
newObj[k] = [];
deepCopy(newObj[k], item);
} else if (item instanceof Object) {
// 2.属于对象
newObj[k] = {};
deepCopy(newObj[k], item);
} else {
// 3.属于简单数据类型
newObj[k] = item;
}
}
}
deepCopy(o, obj);
console.log(o);
o.msg.age = 200;
console.log(obj);
十二、正则表达式
12.1 什么是正则表达式
正则表达式适用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。
作用:
- 匹配。验证表单
- 替换。替换敏感词
- 提取。提取特定部分
12.2 正则表达式的特点
- 灵活性、逻辑性和功能性非常的强。
- 可以迅速地用及简单的方式达到字符串的复杂控制。
- 对于刚接触的人来说,比较晦涩难懂
12.3 正则表达式在js中的使用
1. 创建正则表达式
- 利用 RegExp 对象来创建 正则表达式
- 利用字面量创建 正则表达式
// - 利用 RegExp 对象来创建 正则表达式
var regexp = new RegExp(/123/);
console.log(regexp);
// - 利用字面量创建 正则表达式
var rg = /123/;
console.log(rg);
2. 正则表达式的检测
- test 方法用来检测字符串是否符合正则表达式要求的规范,符合返回true,不符合返回false.
// - 利用 RegExp 对象来创建 正则表达式
var regexp = new RegExp(/123/);
console.log(regexp);
// - 利用字面量创建 正则表达式
var rg = /123/;
console.log(rg);
// test 方法用来检测字符串是否符合正则表达式要求的规范
console.log(rg.test(123));
console.log(rg.test('abc'));
12.4 正则表达式中的特殊字符
1. 边界符
边界符(位置符)用来提示字符所处的位置,主要有两个字符。
如果 ^ 和 $ 在一起,表示必须是精确匹配。
var reg = /abc/; //正则表达式里面不需要加引号 不管是数字型还是字符串型
//1. /abc/ 只要包含有abc这个字符串返回的都是true
console.log(reg.test('abc')); //true
console.log(reg.test('abcd')); //true
console.log(reg.test('aabc')); //true
console.log('-----------------------');
var rg = /^abc/;
console.log(rg.test('abc')); // true
console.log(rg.test('abcd')); //true
console.log(rg.test('aabc')); //false
console.log('-----------------------');
var rg1 = /^abc$/; // 精确匹配,必须是abc字符串才符合规范
console.log(rg1.test('abc')); // true
console.log(rg1.test('abcd')); //false
console.log(rg1.test('aabc')); //false
console.log(rg1.test('abcabc')); //false
2. 字符类
字符类: [ ] 表示有一系列字符可供选择,只要匹配其中一个就可以了。[a-z],表示从a到z的范围。
var rg = /[abc]/; //只要包含有a 或者有b, 或者有c 就返回true
console.log(rg.test('andy')); //true
console.log(rg.test('baby')); //true
console.log(rg.test('color')); //true
console.log(rg.test('red')); //false
console.log('--------------------------');
var rg1 = /^[abc]$/; //三选一 只能是a 或者是b 或者是c 这三个字母才返回true
console.log(rg1.test('aa')); //false
console.log(rg1.test('a')); //true
console.log(rg1.test('b')); //true
console.log(rg1.test('c')); //true
console.log(rg1.test('abc')); //false
console.log('--------------------------');
var rg2 = /^[a-z]$/; //26个英文字母任何一个字母返回true
console.log(rg2.test('a')); //true
console.log(rg2.test('z')); //true
console.log(rg2.test('11')); //false
console.log(rg2.test('A')); //false
//字母组合
var rg3 = /^[a-zA-Z0-9-_]$/; //26个英文字母(包括大小写字母)还有任何一个字母还有数字返回true
console.log(rg3.test('a')); //true
console.log(rg3.test('B')); //true
console.log(rg3.test(9)); //true
console.log(rg3.test('-')); //true
console.log(rg3.test('_')); //true
console.log(rg3.test('!')); //false
//如果中括号 [ ]里面有^ 表示取反的意思 ,千万别和便捷付^ 混淆
var rg3 = /^[^a-zA-Z0-9-_]$/;
console.log(rg3.test('a')); //false
console.log(rg3.test('B')); //false
console.log(rg3.test(9)); //false
console.log(rg3.test('-')); //false
console.log(rg3.test('_')); //false
console.log(rg3.test('!')); //true
2. 量词符
量词符用来设定某个模式出现次数。
// * 相当于>=0 可以出现0次或很多次
// var rg = /^a*$/;
// console.log(rg.test('')); //true
// console.log(rg.test('a')); //true
// console.log(rg.test('aaaa'));//true
// + 相当于 >= 1 可以出现1次或更多次
// var rg = /^a+$/;
// console.log(rg.test('')); //false
// console.log(rg.test('a')); //true
// console.log(rg.test('aaaa'));//true
// ? 相当于 1 || 0, 出现1次或0次
// var rg = /^a?$/;
// console.log(rg.test('')); //true
// console.log(rg.test('a')); //true
// console.log(rg.test('aaaa'));//false
// {3} 重复3次
// var rg = /^a{3}$/;
// console.log(rg.test('')); //false
// console.log(rg.test('a')); //false
// console.log(rg.test('aaa')); //true
// console.log(rg.test('aaaa'));//false
// {3,} 大于等于3
// var rg = /^a{3,}$/;
// console.log(rg.test('')); //false
// console.log(rg.test('a')); //false
// console.log(rg.test('aaa')); //true
// console.log(rg.test('aaaa'));//true
// {3,6} 大于等于3 小于等于6 中间不能有空格
var rg = /^a{3,}$/;
console.log(rg.test('')); //false
console.log(rg.test('a')); //false
console.log(rg.test('aaa')); //true
console.log(rg.test('aaaa'));//true
console.log(rg.test('aaaaaaaa'));//false
4. 预定义类
预定义类: 某些常见模式的简写方式。
12.5 正则替换
replace()方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp/substr, replacement)
1.第一个参数: 被替换的字符串 或者 正则表达式
2.第二个参数: 替换为的字符串
3.返回值是一个替换完毕的新字符串
正则表达式参数: /表达式/[switch]
switch有三种值。g: 全局匹配;i: 忽略大小写;gi: 全局匹配和忽略大小写。
案例: 替换敏感字:
<body>
<textarea name="" id="text" cols="30" rows="10"></textarea><button>提交</button>
<div></div>
<script>
// var str = 'andy和merry';
// var newStr = str.replace('andy', 'shily');
// console.log(newStr);
var text = document.querySelector('#text');
var btn = document.querySelector('button');
var div = document.querySelector('div');
// var rg = /^激情|gay&/g;
btn.onclick = function () {
div.innerHTML = text.value.replace(/激情|gay/g, '**');
}
</script>
</body>
十三、关键字let 、const、var
13.1 let:
用于声明变量的关键字。
使用let关键字声明的变量具有块级作用域
在一个大括内,使用let关键字声明的变量才具有块级作用域, var 关键字不具备这个特点
放置循环变量变成全局变量
使用let关键字声明的变量没有变量提升
使用let关键字声明的变量具有暂时性死区
13.2 cosnt:
用于声明常量;
使用const关键字声明的常量具有块级作用域;
声明常量时必须赋初值;
常量赋值后,值不能修改,分为两种情况:(1)基本数据类型,值就是不可更改(2)复杂数据类型,数据结构内部的值可以更改,但是数据值本身不可更改;
(1)基本数据类型,值就是不可更改
const PI = 3.14;
PI = 100; //有误
(2)复杂数据类型,数据结构内部的值可以更改,但是数据值本身不可更改;
cosnt arr = [100, 200];
arr[0] = 123; //可行
arr = [1, 2]; //不可行
13.3 总结:
十四、解构赋值
ES6中允许从数组中提取值,按照对应位置,对应量赋值,对象也可以实现解构。
14.1 数组解构:
let arr = [1, 2, 3];
let [a, b, c, d, e] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
consoel.log(d); // undefined
consoel.log(e); // undefined
14.2 对象解构:
//对象解构允许我们使用变量的名字匹配对象的属性 匹配成功将对象属性的值赋值给变量
let person = {name: 'zhangsan', age: 12, sex: 'man' };
// 1.
let {name, age, sex} = person;
console.log(name); // zhansan
console.log(age); // 12
console.log(sex); // man
// 2.
let {name: myname} = person;
console.log(myname); // zhangsan
十五、箭头函数
箭头函数是用来简化函数定义语法的;
在箭头函数中,如果函数体中只有一句代码,并且代码的执行结果就是函数的返回值,函数体大括号可以省略;
在箭头函数中,如果形参只有一个,形参外侧的小括号也是可以省略;
箭头函数是没有this指向的,箭头函数里的this指向箭头函数定义位置的上下文;
let obj = {
name: 'zhangsan',
age: 12,
say: () => {
console.log(this.age);
}
};
obj.say(); // undefined 因为this指向window
十六、剩余参数
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
const sum = (...args) => {
let total = 0;
args.forEach(item => total += item;)
return total;
};
sum(19, 20);
sum(10, 20, 30);
剩余参数和解构配合使用:
let arr = ['张三', '李四', '王宇'];
let [s1, ...s2] = arr;
console.log(s1); // 张三
console.log(s2); // ['李四', '王宇']
十七、Array的扩展方法
17.1 扩展运算符(展开语法):
扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
let arr = [1, 2, 3];
...arr; // 1,2,3
console.log(...arr); // 1 2 3
console.log(1,2,3); // 1 2 3
扩展运算符可以应用于合并数组
:
//方法一
let arr1 = [1,2,3];
let arr2 = [4,5,6];
let arr3 = [...arr1, ...arr2];
console.log(arr3); // [1,2,3,4,5,6]
//方法二
arr1.push(...arr2);
扩展运算符可以将伪数组转换为真正的数组:
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
//js部分
let divs = document.getElementByTagName('div')
cosnole.log(divs); // HtmlCollections[1,2,3,4,5]
let arr = [...divs];
cosnoel.log(arr); // array[1,2,3,4,5]
17.2 构造函数方法: Array.from()
将类数组或可遍历对象转换为真正的数组。
var arrayLike = {
'0': '张三',
'1': '李四',
'2': '王五',
'length': 3,
}
var ary = Array.from(arrayLike);
cosnoel.log(ary); // ['张三', '李四', '王五']
Array.from() 方法还可以接收第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
var arrayLike = {
'0': 1,
'1': 2,
}
var ary = Array.from(arrayLike, item => return item * 2);
cosnoel.log(ary); // [2, 4];
17.3 实例方法 :find()
用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let arr =[
{id:1, name: 'zhangsan'},
{id: 2, name:'lisi'}
];
let target = arr.find((item) => {
//return 的是满足条件
return item.id == 2
});
console.log(target); // {id: 2, name:'lisi'}
17.4 实例方法: findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回 -1
let arr = [1,2,3,10];
let index = arr.findIndex((value, index) => value > 9);
console.log(index); // 3
17.5 实例方法:includes()
表示某个数组是否包含给定的值,返回布尔值。
[1,2,3].includes(1); // true
[1,2,3].includes(4); // false
十八、模板字符串:
模板字符串可以解析变量;
let name = '张三';
let say = 'hello, ${name}';
console.log(say); // hello,张三
模板字符串中可以换行;
let result = {
name: '张三',
age: 20,
}
let html = `<div>
<span>${result.name}</span>
<span>${result.age}</span>
</div>`
console.log(html);
模板字符串中可以调用函数;
function fn = () => {
return '我是fn函数';
}
let html = `我是模板字符串 ${fn()}`;
console.log(html); // 我是模板字符串 我是fn函数
十九、String的扩展方法
19.1 实例对象: startsWith()和 endsWith()
- startsWith(): 表示参数字符串是否在原字符串的头部,返回布尔值
- endsWith(): 表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'hell0 2020';
let r1 = str.startsWith('hello');
let r2 = str.endsWith('2020');
cnosole.log(r1); // true
console.log(r2); // true
19.2 实例方法: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函数可以接受一个数组作为参数,用来初始化
cosnt set = new Set(['a', 'b', 'c']);
console.log(set.size); // 3
//利用Set可以做数组去重
cosnt s1 = new Set(['a', 'a', 'b', 'b']);
console.log(s1.size); // 2
const ary = [...s1];
console.log(ary); // ['a', 'b']
Set的实例方法:
const s = new Set();
//1. 向set结构中添加值 使用add方法
s.add('a').add('b');
console.log(s.size); // 2
//2. 从set结构中删除值 用到的方法delete 返回值是布尔值,成功true,失败false
const r1 = s.delete('b');
const r2 = s.delete('c');
console.log(r1); // true
console.log(r2); // false
//3.判断某个值是否是set结构中的成员 使用has 返回值是布尔值,是true,否false
const r3 = s.has('a'); //true
const r4 = s.has('d'); // false
//4. 清空set数据结构中的值 使用clear方法
s.clear();
console.log(s.size); // 0
//5.遍历 forEach方法
const s2 = new Set([1,2,3]);
s2.forEach((value) => {
console.log(value);
})