1.面向过程与面向对象
1.1面向过程
蛋炒饭
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
1.2面向对象
盖浇饭
- 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
- 思维特点
- 抽取对象公用的属性和行为封装成一个类(模版)
- 对类进行实例化,获取类的对象
1.2.1 特性
-
封装
- 每一个对象都是独立存在的,有其独有的属性和方法,必须通过调用的方式来使用。保证了对象内部属性和方法的安全性
-
继承
- 子类可以通过extends关键字继承父类的属性和方法。减少了代码的冗余,省略了很多重复代码。
-
多态
- 子类可以在自身内部定义自己的方法,也可以重载父类的方法。拓展了类的适用性和灵活性。
1.3面向过程与面向对象对比
| 面向过程 | 面向对象 | |
|---|---|---|
| 优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 |
| 缺点 | 不易维护、不易复用、不易扩展 | 性能比面向过程低 |
-
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特征,可以设计出低耦合的系统,使系统更加灵活、更易于维护。
- 缺点:性能比面向过程底。
-
面向对象是把构成问题事务分解成各个对象,然后由对象之间分工与合作。建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。比如 单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能时最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展。
2.对象与类
2.1对象
对象是由属性和方法组成的:是一个无序键值对的集合,指的是一个具体的事物
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
2.1.1创建对象
//以下代码是对对象的复习
//字面量创建对象
var ldh = {
name: '刘德华',
age: 18
}
console.log(ldh);// {name:'ldh', age:18}
//构造函数创建对象
function Star(name, age) {
this.name = name;
this.age = age;
}
//
var ldh = new Star('刘德华', 18)//实例化对象
console.log(ldh);// Star {name:'刘德华', age: 18}
2.2类
在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指
某一大类(class)对象特指某一个,通过类实例化一个具体的对象
类是图纸,对象实例 是按照图纸建造的房子
- 变量是数据的容器
- 数组 是一组数据的容器
- 方法(函数) 是代码的容器
- 对象 是超级容器(数据、数组、方法、对象都可以装)
- 类 给对象做分类,分类过程中提取出 一些对象的共同特点,然后用 类 来描述
2.2.1创建类
// 1. 创建类 class 创建一个 明星类
class Star {
// 类的共有属性放到 constructor 里面
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh);// Star {name:'刘德华', age:18}
通过结果我们可以看出,运行结果和使用构造函数方式一样
2.2.2类创建添加属性和方法
// 1. 创建类 class 创建一个类
class Star {
// 类的共有属性放到 constructor 里面 constructor是 构造器或者构造函数
constructor(uname, age) {
this.uname = uname;
this.age = age;
}//------------------------------------------->注意,方法与方法之间不需要添加逗号
sing(song) {
console.log(this.uname + '唱' + song);
}
}
// 2. 利用类创建对象 new
var ldh = new Star('刘德华', 18);
console.log(ldh); // Star {uname: "刘德华", age: 18}
ldh.sing('冰雨'); // 刘德华唱冰雨
注意:
- 通过class 关键字创建类, 类名我们还是习惯性定义
首字母大写 - 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
- constructor 函数 只要 new 生成实例时,就会
自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数 - 多个函数方法之间
不需要添加逗号分隔 - 生成实例
new 不能省略 - 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
- 类没有提升,所以需要
先定义,再实例化
使用new创建对象的步骤
- 在内存中申请一块空闲的空间,存储创建的新的对象
- 把this指向为当前的对象
- 执行函数,设置对象的属性和方法的值
- 返回this指向这个对象的地址(所以构造函数里不需要写return)
2.2.3 构造函数和class的区别与相似性
区别
-
每创建一个实例,就会重新创建一个sayName函数,这样运行起来会消耗一定的性能
-
类的函数是创建在prototype上的,每个实例的syaName方法都指向同一个地址,也就是说是同一个方法,相对于构造函数来说,性能会有所提高。
function Person(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
}
const p1 = new Person('高秀')
const p2 = new Person('高秀')
p1.sayName() // 高秀
p2.sayName() // 高秀
console.log(p1.sayName === p2.sayName) //false
console.log(Person.prototype)
/*
* {constructor: ƒ}
* constructor: ƒ Person(name)
* __proto__: Object
*
* */
class Student {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
const p3 = new Student('麦乐')
const p4 = new Student('麦乐')
p3.sayName() // 麦乐
p4.sayName() // 麦乐
console.log(p3.sayName === p4.sayName) // true
console.log(Student.prototype)
/*
*{constructor: ƒ, sayName: ƒ}
constructor: ƒ Student(name),
sayName: ƒ sayName(),
__proto__: Object
*/
相似性
- this都指向的是实例
function Person(name) {
this.name = name
this.sayName = function() {
console.log(this)
}
this.sayHello = function() {
const check = this.sayName
check()
}
}
const p1 = new Person('高秀')
const p2 = new Person('麦乐')
p1.sayName() // Person {name: "高秀", sayName: ƒ, sayHello: ƒ}
p2.sayName() // Person {name: "麦乐", sayName: ƒ, sayHello: ƒ}
p1.sayHello() // undefiend
class Student {
constructor(name) {
this.name = name
}
sayName() {
console.log(this)
}
sayHello() {
const checkThis = this.sayName
checkThis()
}
sayBind() {
const checkThisBind = this.sayName.bind(this)
checkThisBind()
}
}
const p5 = new Student('章三')
const p6 = new Student('历史')
p5.sayName() // Student {name: "章三"}
p6.sayName() // Student {name: "历史"}
p5.sayHello() // undefiend 这里可以解释为什么react中的事件调用函数需要绑定this
p5.sayBind() // Student {name: "章三"}
2.2.4类的继承
- 子类使用super关键字访问父类的方法
// super 关键字调用父类普通函数
class Father {
constructor(x, y) {
this.x = x;
this.y = y;
}
sum() {
console.log(this.x + this.y);
}
say() {
return '我是爸爸';
}
}
class Son extends Father {
constructor(x, y) {
super(x, y); //使用super调用了父类中的构造函数
}
say() {
// console.log('我是儿子');
console.log(super.say() + '的儿子');
// super.say() 就是调用父类中的普通函数 say()
}
}
var son = new Son(1, 2);
son.sum(); // 结果为3
son.say(); // 我是爸爸的儿子
-
注意:
-
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
-
继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
-
如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,
super 必须在子类this之前调用
-
- 子类继承父亲方法,同时扩展自己方法
// 父类有加法方法
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, z) {
// 利用super 调用父类的构造函数
// super 必须在子类this之前调用
super(x, y);
this.z = z;
}
subtract() {
console.log(this.x - this.y - this.z);
}
}
var son = new Son(5, 3, 9);
son.subtract(); // -7
son.sum(); // 8
-
时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用
- constructor中的this指向的是new出来的实例对象
- 自定义的方法,一般也指向的new出来的实例对象
- 绑定事件之后this指向的就是触发事件的事件源
var that;
var _that;
class Star {
constructor(uname, age) {
// constructor 里面的this 指向的是 创建的实例对象
that = this;
console.log(this);
this.uname = uname;
this.age = age;
this.btn = document.querySelector('button');
// this.sing的this指向的是 btn
this.btn.onclick = this.sing;
}
sing() {
// 这个sing方法里面的this 指向的是 btn 这个按钮,因为这个按钮调用了这个函数
console.log(this);
console.log(that.uname); // that里面存储的是constructor里面的this
}
dance() {
// 这个dance里面的this 指向的是实例对象 ldh 因为ldh 调用了这个函数
_that = this;
console.log(this);
}
}
var ldh = new Star('刘德华');
console.log(that === ldh); // true
ldh.dance();
console.log(_that === ldh); // true
- 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象!
3.面向对象版tab 栏切换
3.1功能需求
- 点击 tab栏,可以切换效果.
- 点击 + 号, 可以添加 tab 项和内容项.
- 点击 x 号, 可以删除当前的tab项和内容项.
- 双击tab项文字或者内容项文字可以修改里面的文字内容
3.2案例准备
- 获取到标题元素
- 获取到内容元素
- 获取到删除的小按钮 x号
- 新建js文件,定义类,添加需要的属性方法(切换,删除,增加,修改)
- 时刻注意this的指向问题
3.3切换
-
为获取到的标题绑定点击事件,展示对应的内容区域,存储对应的索引
this.lis[i].index = i; this.lis[i].onclick = this.toggleTab; -
使用排他,实现只有一个元素的显示
toggleTab() { //将所有的标题与内容类样式全部移除 for (var i = 0; i < this.lis.length; i++) { this.lis[i].className = ''; this.sections[i].className = ''; } //为当前的标题添加激活样式 this.className = 'liactive'; //为当前的内容添加激活样式 that.sections[this.index].className = 'conactive'; }
3.4添加
-
为添加按钮+ 绑定点击事件
this.add.onclick = this.addTab; -
实现标题与内容的添加,做好排他处理
addTab() { that.clearClass(); // (1) 创建li元素和section元素 var random = Math.random(); var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"> </span></li>'; var section = '<section class="conactive">测试 ' + random + '</section>'; // (2) 把这两个元素追加到对应的父元素里面 that.ul.insertAdjacentHTML('beforeend', li); that.fsection.insertAdjacentHTML('beforeend', section); that.init(); }
3.5删除
-
为元素的删除按钮x绑定点击事件
this.remove[i].onclick = this.removeTab; -
获取到点击的删除按钮的所在的父元素的所有,删除对应的标题与内容
removeTab(e) { e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件 var index = this.parentNode.index; console.log(index); // 根据索引号删除对应的li 和section remove()方法可以直接删除指定的元素 that.lis[index].remove(); that.sections[index].remove(); that.init(); // 当我们删除的不是选中状态的li 的时候,原https://github.com/sl1673495/leetcode-javascript/issues/11来的选中状态li保持不变 if (document.querySelector('.liactive')) return; // 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态 index--; // 手动调用我们的点击事件 不需要鼠标触发 that.lis[index] && that.lis[index].click(); }
3.6编辑
- 双击禁止选定文字
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
- 使文本框自动选择
// 使文本框自动选择
input.select();
-
为元素(标题与内容)绑定双击事件
this.spans[i].ondblclick = this.editTab; this.sections[i].ondblclick = this.editTab; -
在双击事件处理文本选中状态,修改内部DOM节点,实现新旧value值的传递
editTab() { var str = this.innerHTML; // 双击禁止选定文字 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); // alert(11); this.innerHTML = '<input type="text" />'; var input = this.children[0]; input.value = str; input.select(); // 文本框里面的文字处于选定状态 // 当我们离开文本框就把文本框里面的值给span input.onblur = function() { this.parentNode.innerHTML = this.value; }; // 按下回车也可以把文本框里面的值给span input.onkeyup = function(e) { if (e.keyCode === 13) { // 手动调用表单失去焦点事件 不需要鼠标离开操作 this.blur(); } } }
4. 构造函数和原型
4.1 对象的三种创建方式--复习
-
字面量方式
var obj = {}; -
new关键字
var obj = new Object(); -
构造函数方式
function Person(name,age){ this.name = name; this.age = age; } var obj = new Person('zs',12);
4.2 静态成员和实例成员
-
实例成员就是构造函数内部通过this添加的成员 如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
-
静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
Star.sex = '男';
var ldh = new Star('刘德华', 18);
console.log(ldh.uname);//实例成员只能通过实例化的对象来访问
console.log(Star.sex);//静态成员只能通过构造函数来访问
构造函数方法很好用,但是存在浪费内存的问题。
4.3 原型对象prototype和对象原型__proto__
4.3.1 原型对象
每一个
构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
把那些不变的方法,直接定义在 prototype 对象上,这样所有
对象的实例就可以共享这些方法.ldh.sing === zxy.sing;// true。就可以节省内存空间的开辟
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing);// true
4.3.2 对象原型
对象都会有一个属性 __proto__指向构造函数的 prototype 原型对象之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有__proto__原型的存在。
__proto__对象原型和原型对象 prototype 是等价的
ldh._proto_===Star.prototype;// true__proto__对象原型的意义就在于
为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function () {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
ldh.sing();
4.4 constructor构造函数
对象原型__proto__和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象prototype中设置。 如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star, // 手动设置指回原来的构造函数
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var zxy = new Star('张学友', 19);
console.log(Star.prototype);
zxy.constructor = zxy.__proto__.constructor = Star.prototype = Star;
设置constructor属性如图:
如果未设置constructor属性,如图:
4.4.1 constructor的作用
就是为了将实例的构造器的原型对象暴露出来, 比如你写了一个插件,别人得到的都是你实例化后的对象, 如果别人想扩展下对象,就可以用 instance.constructor.prototype 去修改或扩展原型对象。
var a,b;
(function(){
function A (arg1,arg2) {
this.a = 1;
this.b=2;
}
A.prototype.log = function () {
console.log(this.a);
}
a = new A();
b = new A();
})()
a.log();// 1
b.log();// 1
// 因为A在闭包里,所以现在我们是不能直接访问A的
// 所以通过constructor为A.prototype添加方法
a.constructor.prototype.log2 = function () {
console.log(this.b)
}
a.log2();// 2
b.log2();// 2
4.5 原型链
每一个实例对象又有一个__proto__属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找就形成了原型链。
4.5.1 成员的查找机制
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
- 如果还没有就查找原型对象的原型(Object的原型对象)。 ... 依此类推一直找到 Object 为止(null)。
// Object是内置对象,只能添加方法,是不能覆盖对象的
Object.prototype.sing1 = function() {
console.log('我会拍戏');
}
Object.prototype.uname1 = '你好啊';
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
console.log(ldh.uname1);
console.log(ldh.__proto__);
// 1. 只要是对象就有__proto__ 原型, 指向原型对象
console.log(Star.prototype);
console.log(Star.prototype.__proto__);
// 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Object.prototype.__proto__);
// 3. 我们Object.prototype原型对象里面的__proto__原型 指向为 null
4.6 构造函数实例和原型对象三角关系
4.7 原型对象中this指向
构造函数中的this和原型对象的this,都指向我们new出来的实例对象
4.8 通过原型为数组扩展内置方法
注意:
内置对象不能以对象的形式覆盖其原型对象prototype,只能通过.来追加方法。
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
//此时数组对象中已经存在sum()方法了 可以使用数组.sum()进行数据的求和
5.继承call(即ES6的extends)
- call()可以调用函数
- call()可以修改this的指向,使用call()的时候 参数一是修改后的this指向,参数2,参数3..使用逗号隔开连接
5.1 继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
3.使用call方式实现子继承父的属性
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
5.2 借用原型对象继承方法
关键点:
利用new Father()创建的实例对象,也是可以调用Father的原型对象上的方法
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
// new Father()即为创建的实例对象,也是可以调用Father的原型对象上的方法
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);
6. ES5新增方法
6.1 数组方法forEach遍历数组
#在forEach里面的return不会终止迭代
arr.forEach(function(value, index, array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
})
//相当于数组遍历的 for循环 没有返回值
forEach()没有提供 退出整个循环的语法
解决方案:抛出异常 和异常捕获
let arr=['a','b','c','d']
try{
arr.forEach((ele,i)=>{
if(i==1){
// 抛出异常
throw new Error('')
}
console.log(ele,i);
})
// 异常捕获
}catch(e){
}
6.2 数组方法filter过滤数组
var arr = [12, 66, 4, 88, 3, 7];
var newArr = arr.filter(function(value, index,array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value >= 20;
});
console.log(newArr);//[66,88] //返回值是一个新数组
6.3 数组方法some
# 在some里面遇到了 return true则终止遍历,迭代效率更高
# 所以在数组中查询唯一的元素,推荐用some
some 查找数组中是否有满足条件的元素
var arr = [10, 30, 4];
var flag = arr.some(function(value,index,array) {
//参数一是:数组元素
//参数二是:数组元素的索引
//参数三是:当前的数组
return value < 3;
});
console.log(flag);//false返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环
6.4some和forEach区别
- 如果查询数组中唯一的元素, 用some方法更合适,在some 里面 遇到 return true 就是终止遍历 迭代效率更高
- 在forEach 里面 return 不会终止迭代
6.5获取对象的属性名
Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]
6.6Object.defineProperty
Object.defineProperty设置或修改对象中的属性
Object.defineProperty(对象,修改或新增的属性名,{
value:修改或新增的属性的值,
writable:true/false,//如果值为false 不允许修改这个属性值,默认值为true
enumerable: false,//enumerable 如果值为false 则不允许遍历
configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
})
7. 函数的定义和调用
7.1 函数的定义方式
1. 方式1 函数声明方式 function 关键字 (命名函数)
function fn(){}
2. 方式2 函数表达式(匿名函数)
var fn = function(){}
3. 方式3 new Function() --------不推荐用
var f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);
var fn = new Function('参数1','参数2'..., '函数体')
注意
/*Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)
函数也属于对象
*/
7.2 函数的调用
/* 1. 普通函数 */
function fn() {
console.log('人生的巅峰');
}
fn();
/* 2. 对象的方法 */
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
/* 3. 构造函数*/
function Star() {};
new Star();
/* 4. 绑定事件函数*/
btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
/* 5. 定时器函数*/
setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次
/* 6. 立即执行函数(自调用函数)*/
(function() {
console.log('人生的巅峰');
})();
8. this
8.1 函数内部的this指向
一般指向我们的调用者.
8.2 改变函数内部 this 指向
8.2.1 call方法
call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
第一个参数为this指向的对象,传递参数使用逗号隔开
应用场景: 经常做继承.
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a+b)
};
fn(1,2)// 此时的this指向的是window 运行结果为3
fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3
8.2.2 apply方法
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
第一个参数为this指向的对象,用数组传递参数
应用场景: 经常跟数组有关系
// apply 的主要应用 比如说我们可以利用 apply 借助于数学内置对象求数组最大值
// Math.max
var arr = [1, 66, 3, 99, 4];
// var max = Math.max.apply(null, arr);
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
Math.max(...arr)// 解构 ES6才有
8.2.3 bind方法
bind() 方法不会调用函数,但是能改变函数内部this 指向,返回的是原函数改变this之后产生的新函数
如果只是想改变 this 指向,并且不想调用这个函数的时候,可以使用bind
应用场景:不调用函数,但是还想改变this指向
// 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
function fn1() {
this.disabled = false;
}
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
this.disabled = true;
// 因为定时器里面的函数指向的是window对象
setTimeout(fn1.bind(this), 2000);
}
}
8.2.4 call、apply、bind三者的异同
-
共同点 : 都可以改变this指向
-
不同点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
- bind 不会调用函数, 可以改变函数内部this指向.
-
应用场景
- call 经常做继承.
- apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
注意:
如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数。
function foo () {
console.log(this.a)
}
var a = 2
foo.call()
foo.call(null)
foo.call(undefined)
输出的是:
2
2
2
9. 严格模式
9.1 开启严格模式
- 为脚本开启严格模式
(function (){
//在当前的这个自调用函数中有开启严格模式,当前函数之外还是普通模式
"use strict";
var num = 10;
function fn() {}
})();
//或者
<script>(不推荐)
"use strict"; //当前script标签开启了严格模式
</script>
<script>
//当前script标签未开启严格模式
</script>
- 为函数开启严格模式(推荐,方便代码合并)
function fn(){
// 声明放在函数体所有语句之前
"use strict";
return "123";
}
//当前fn函数开启了严格模式
9.2 严格模式中的变化
- 严格模式下全局作用域中函数中的 this 是 undefined
- 严格模式下,定时器 this 还是指向 window
10. 高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
-
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
-
同理函数也可以作为返回值传递回来
11. 闭包
11.1 闭包的概念
闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
闭包的本质父函数的作用域对象(未被销毁)
变量所在的函数,就是闭包函数。
-
作用域:就是一个 用来保存数据的
对象 -
函数被调用时,会产生一个
临时的作用域对象,用来保存 局部变量和形参,一旦本地调用结束,这个临时的作用域对象就要销毁。- 相同函数被调用2次,就会产生两个作用域对象
- 因为子函数 使用了 父函数 的局部变量,所以父函数执行后,产生的作用域对象 不能被销毁(自函数调用完,使用到的父函数的 局部变量 也不会被销毁)
// 闭包: 我们fun 这个函数作用域 访问了另外一个函数 fn 里面的局部变量 num
// fn就是闭包函数
function fn() {
var num = 10;
function fun() {
console.log(num); // 10
}
return fun;
}
let f= fn();
f();
11.2 闭包的作用
延伸变量的作用范围。
在外面的全局作用域通过f() 访问了局部变量num
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
// return function () {
// num++;
// console.log(num);
// }
}
var f = fn();
// 类似于
// var f = function() {
// console.log(num);
// }
f();
11.3 闭包应用
- 点击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(i);
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)
}
// 3. 利用let的块级作用域
var lis = document.querySelector('.nav').querySelectorAll('li');
for (let i = 0; i < lis.length; i++) {
lis[i].onclick = function() {
console.log(i);
}
}
- 3秒钟之后,打印所有li元素的内容(也是三种方式)
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(i);
}
// 补充:每隔3秒打印一个
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000*i)
})(i);
}
- 计算打车价格
/*需求分析
打车起步价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(flag) {
return flag ? total + 10 : total;
}
}
})();
console.log(car.price(5)); // 23
console.log(car.yd(true)); // 33
- this指向
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
// 匿名函数的this指向的是window
return function() {
return this.name;
};
}
};
console.log(object.getNameFunc()());// The Window
// 类似于
var f = object.getNameFunc();
var f = function() {
return this.name;
}
f();
// 此时没有闭包的产生,因为没有局部变量
-----------------------------------------------------------------------------------
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
// 是objec调用了这个函数,this指向的就是obj
var that = this;
return function() {
return that.name;
};
}
};
console.log(object.getNameFunc()());// My Object
// 此时有闭包的产生,因为that是局部变量
12. 递归
12.1什么是递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数
注意:递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。
// 递归函数 : 函数内部自己调用自己, 这个函数就是递归函数
// 每次调用,都会开辟一个新的函数
var num = 1;
var i = 0;
function fn() {
console.log('我要打印6句话');
if (num == 6) {
return; // 递归里面必须加退出条件
}
num++;
fn();
console.log(i++);
}
fn();
12.2利用递归求1~n的阶乘
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n) {
if (n == 1) { //结束条件
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3));
12.3利用递归求斐波那契数列
// 利用递归函数求斐波那契数列(兔子序列) 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));
12.4利用递归遍历数据
// 我们想要做输入id号,就可以返回的数据对象
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔'
}, {
id: 112,
gname: '美的'
}]
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰',
goods: [{
id: 13,
gname: '冰箱1',
goods: [{
id: 131,
gname: '海尔1'
}, {
id: 132,
gname: '美的1'
}, ]
}, {
id: 14,
gname: '洗衣机1'
}]
}];
// 我们想要做输入id号,就可以返回的数据对象
// 1. 利用 forEach 去遍历里面的每一个对象
function getID(json, id) {
var res = {};
json.forEach(function(item) {
// item--2个数组元素
// 当res不为空时,说明已经找到了,就不再去遍历查找执行了
// Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组
if (Object.keys(res).length == 0) {
if (item.id == id) {
res = item;
// 2. 我们想要得里层的数据 11 12 可以利用递归函数
// 里面应该有goods这个数组并且数组的长度不为 0
} else if (item.goods && item.goods.length > 0) {
res = getID(item.goods, id);
}
}
});
return res;
}
console.log(getID(data, 1));// {id: 1, name: "家电", goods: Array(2)}
13. 浅拷贝和深拷贝
13.1 浅拷贝
浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
// 方法一:
for (var k in obj) {
// k 是属性名 obj[k] 属性值
o[k] = obj[k];
}
// 方法二:
// 把obj拷贝给o
Object.assign(o, obj);
o.id = 2;
o.msg.age = 20;
console.log(obj.id); // 1
console.log(obj.msg.age); // 20
// 方法一和方法二,都只会拷贝复杂数据类型的引用(msg),o的msg改变,obj对应内容会发生改变
// 而简单数据类型(id,name),就直接复制值,o改变,obj不会发生改变
// 方法三:
o = obj;
o.id = 2;
console.log(obj.id); // 2
// 直接拷贝引用地址,所以都会发生改变
13.2 深拷贝
深拷贝拷贝多层, 每一级别的数据都会拷贝.
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['pink', 'red']
};
var o = {};
// 方法一:封装函数
function deepCopy(newobj, oldobj) {
for (var k in oldobj) {
// 判断我们的属性值属于那种数据类型
// 1. 获取属性值 oldobj[k]
var item = oldobj[k];
// 2. 判断这个值是否是数组
// 注意数组判断要写在对象之前,因为数组也是对象
if (item instanceof Array) {
newobj[k] = [];
deepCopy(newobj[k], item)
} else if (item instanceof Object) {
// 3. 判断这个值是否是对象
newobj[k] = {};
deepCopy(newobj[k], item)
} else {
// 4. 属于简单数据类型
newobj[k] = item;
}
}
}
deepCopy(o, obj);
o.msg.age = 20;
console.log(obj.msg.age); // 18
// 方法二:先转为字符串,再转回去
let newobj = JSON.stringify(obj);
newobj = JSON.parse(newobj);
14. 正则表达式
正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。
14.1 正则表达式的使用
- /abc/
- 满足条件:包含有abc
- 正则表达式里面不需要加引号 不管是数字型还是字符串型
// 1. 利用 RegExp对象来创建 正则表达式
var regexp = new RegExp(/123/);
console.log(regexp);
// 2. 利用字面量创建 正则表达式
var rg = /123/;
// 3.test 方法用来检测字符串是否符合正则表达式要求的规范
console.log(rg.test(123));// true
console.log(rg.test('abc'));// false
14.2 特殊字符
14.2.1 边界符
-
^ 表示匹配行首的文本(以谁开始)
-
$ 表示匹配行尾的文本(以谁结束)
-
如果 ^和 $ 在一起,表示必须是精确匹配。
14.2.2 [] 方括号
只要匹配其中一个就可以了,即多选一
// 字符类: [] 表示有一系列字符可供选择,只要匹配其中一个就可以了
var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
console.log(rg.test('baby')); // 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('----------------');
var reg = /^[a-z]$/; // 26个英文字母任何一个字母返回 true - 表示的是a 到z 的范围
console.log(reg.test('a')); // true
console.log(reg.test('A')); // false
console.log('----------------');
// 字符组合
var reg1 = /^[a-zA-Z0-9_-]$/; // 26个英文字母(大写和小写都可以)、数字0-9、-、_ 任何一个返回 true
console.log(reg1.test('-')); // true
console.log(reg1.test('_')); // true
console.log(reg1.test('!')); // false
console.log('----------------');
// 如果中括号里面有^ 表示取反的意思 千万和 我们边界符 ^ 别混淆
var reg2 = /^[^a-zA-Z0-9_-]$/;
console.log(reg2.test('_')); // false
console.log(reg2.test('!')); // true
14.2.3 量词符
量词符用来设定某个模式出现的次数。
| 量词 | 说明 |
|---|---|
| * | 重复0次或更多次 |
| + | 重复1次或更多次 |
| ? | 重复0次或1次 |
| {n} | 重复n次 |
| {n,} | 重复n次或更多次 |
| {n,m} | 重复n到m次 |
// var reg = /^a{3+}$/;
console.log(reg.test('aaaba'));// false
// {3, } 大于等于3
var reg = /^a{3,}$/;
console.log(reg.test('aa'));// false
console.log(reg.test('aaa'));//true
// 只是让c重复3次 abccc
var reg = /^abc{3}$/;
// 让abc重复3次
var reg = /^(abc){3}$/;
// {3, 16} 大于等于3 并且 小于等于16
var reg = /^a{3,16}$/;
console.log(reg.test('aaaaaa'));// true
// 只能为英文字母,数字,下划线或者短横线组成, 并且用户名长度为6~16位.
var reg = /^[a-zA-Z0-9_-]{6,16}$/;
14.3 预定义类
指的是某些常见模式的简写方式
// 验证座机号码
var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/;
var reg = /^\d{3,4}-\d{7,8}$/;
//手机号验证:/^1[3|4|5|7|8][0-9]{9}$/;
//QQ号验证: /^[1-9]\d{4,}$/;
//昵称验证:/^[\u4e00-\u9fa5]{2,8}$/
14.4 正则替换replace
实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
// 默认替换第一个
var str = 'abcabc'
var newStr = str.replace(/a/,'哈哈')//哈哈bcabc
// 后面写g,替换全部
var newStr1 = str.replace(/a/g,'哈哈')//哈哈bc哈哈bc
// 忽略大小写i
var str = 'aAbcAba';
var newStr = str.replace(/a/gi,'哈哈')//"哈哈哈哈bc哈哈b哈哈"
// 将所有的激情和gay都替换为**
div.innerHTML = text.value.replace(/激情|gay/g, '**');
14.5 正则提取match
match()方法可以实现从一个字符串内提取指定内容的子字符串,返回一个由匹配项组成的新内容
var str = 'abc123def';
var patt = /[0-9]+/;
var newArr = str.match(patt); // ["123", index: 3, input: "abc123def", groups: undefined]
15. ES6(ECMAScript)语法
15.1 let
声明变量
- let声明的变量只在所处于的块级有效
- 使用let关键字声明的变量没有变量提升
- 使用let关键字声明的变量具有暂时性死区特性
经典面试题
// 此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
// 此题的关键点在于每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值.
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
15.2 const
声明常量,常量就是值(内存地址)不能变化的量
- 具有块级作用域
- 声明常量时必须赋值
- 常量赋值后,值不能修改
15.3 let、const、var 的区别
15.4 解构赋值
-
解构赋值就是把数据结构分解,然后给变量进行赋值
-
如果结构不成功,变量跟数值个数不匹配的时候,变量的值为undefined
-
利用解构赋值能够让我们方便的去取对象中的属性跟方法
15.4.1 数组解构
let [a, b, c] = [1, 2, 3];
console.log(a)//1
console.log(b)//2
console.log(c)//3
//如果解构不成功,变量的值为undefined
15.4.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
15.5 箭头函数
箭头函数的优点在于解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题(他们的this默认一般都是指向window)
-
this是由
外层作用域来决定的,且指向函数定义时的this而非执行时。- 箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。
-
字面量创建的对象,作用域是window,如果里面有箭头函数属性的话,this指向的是window
-
构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的this是指向新建的对象的,因此this指向这个对象。
-
箭头函数的this
无法通过bind、call、apply来直接修改,但是可以通过改变作用域中this的指向来间接修改。
15.5.1 案例
- 全局普通函数:函数嵌套
const obj = { name: '张三'}
function fn () {
console.log(this);//this 指向 是obj对象
return () => {
console.log(this);//this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
}
}
const resFn = fn.call(obj);
resFn();
- 字面量对象中普通函数与箭头函数的区别:函数嵌套
var name = 'window'
var obj1 = {
name: 'obj1',
foo: function () {
console.log(this.name)
return function () {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2',
foo: function () {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var obj3 = {
name: 'obj3',
foo: () => {
console.log(this.name)
return function () {
console.log(this.name)
}
}
}
var obj4 = {
name: 'obj4',
foo: () => {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
obj1.foo()() // 'obj1' 'window'
obj2.foo()() // 'obj2' 'obj2'
obj3.foo()() // 'window' 'window'
obj4.foo()() // 'window' 'window'
- 构造函数对象中普通函数和箭头函数的区别:函数嵌套的题目
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
return function () {
console.log(this.name)
}
}
this.foo2 = function () {
console.log(this.name)
return () => {
console.log(this.name)
}
}
this.foo3 = () => {
console.log(this.name)
return function () {
console.log(this.name)
}
}
this.foo4 = () => {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
person1.foo1()() // 'person1' 'window'
person1.foo2()() // 'person1' 'person1'
person1.foo3()() // 'person1' 'window'
person1.foo4()() // 'person1' 'person1'
- 箭头函数结合.call的题目
在这道题中,
obj1.foo1.call(obj2)()就相当于是通过改变作用域间接改变箭头函数内this的指向。
var name = 'window'
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name)
return () => {
console.log(this.name)
}
},
foo2: () => {
console.log(this.name)
return function () {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2'
}
obj1.foo1.call(obj2)() // 'obj2' 'obj2'
obj1.foo1().call(obj2) // 'obj1' 'obj1'
obj1.foo2.call(obj2)() // 'window' 'window'
obj1.foo2().call(obj2) // 'window' 'obj2'
- 面试题
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age)// 100
}
}
obj.say();//箭头函数this指向的是被声明的作用域里面,而对象没有作用域的,所以箭头函数虽然在对象中被定义,但是this指向的是全局作用域
15.7 剩余参数
将一个不定数量的参数表示为一个数组,不定参数定义方式,这种方式很方便的去声明不知道参数情况下的一个函数
function sum (first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sum(10, 20, 30)
#剩余参数和解构配合使用
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
16. ES6 的内置对象扩展
16.1 Array
16.1.1 扩展运算符(展开语法)
- 将数组或者对象转为用逗号分隔的参数序列
let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3,相当于下面的代码
console.log(1,2,3);
- 合并数组
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
- 将类数组或可遍历对象转换为真正的数组
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
16.1.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(arrayLike, item => item *2)//[2,4]
// 注意:如果是对象,那么属性需要写对应的索引
16.1.3 实例方法:find()
用于找出第一个符合条件的数组成员,如果没有找到返回undefined
let ary = [{
id: 1,
name: '张三'
}, {
id: 2,
name: '李四'
}];
let target = ary.find((item, index) => item.id == 2);//找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个
16.1.4 实例方法:findIndex()
用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
16.1.5 实例方法:includes()
判断某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
16.2 String 的扩展方法
16.2.1 模版字符串
// 模板字符串中可以换行
let html = ` <div>
<span>${result.name}</span>
<span>${result.age}</span>
<span>${result.sex}</span>
</div> `;
// 在模板字符串中可以调用函数
let greet = `${sayHello()} 哈哈哈哈`;
16.2.2 实例方法:startsWith() 和 endsWith()
- startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
- endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
16.2.3 实例方法:repeat()
repeat方法表示将原字符串重复n次,返回一个新字符串
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
16.3 Set 数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set本身是一个构造函数,用来生成 Set 数据结构
const s = new Set();
Set函数可以接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);//{1, 2, 3, 4}
16.3.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值
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值
s.clear() // 清除 set 结构中的所有值
//注意:删除的是元素的值,不是代表的索引
16.3.2 遍历
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
s.forEach(value => console.log(value))