ES6类的本质
ES6之前 → 通过 构造函数 + 原型
实现面向对象编程
- 构造函数有原型对象 prototype
- prototype里有 constructor 指向构造函数本身
- 可以通过原型对象添加方法
- 创建实例对象有
__proto__
原型指向构造函数的原型对象
类的本质
- class本质还是一个 function
- 类的所有方法都定义在类的prototype属性上
- 类创建的实例,里面也有__ proto __指向类的prototype原型对象
- class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语言,所以ES6的类其实就是
语法糖
。
语法糖就是一种便捷写法,简单理解,有两种方法可以实现同样的功能,但是一种写法更加清晰、方便,那么这个方法就是语法糖。
ES6面向对象
1. 类的创建
创建类
class name{
// class body
}
// 创建实例 类必须使用new 实例化对象
var xx = new name();
2. constructor构造函数
- 类的构造函数(默认方法),用于传递参数,返回实例对象.
- 通过new生成对象实例时自动调用该方法.
- 如果没有显示定义,类内部会自动创建.
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
say(){
console.log(this.name + 'Hello!');
}
}
// 创建实例
var ldh = new Person('刘德华',18);
console.log(ldh.name) // 刘德华
- 方法之间不能添加逗号,方法也不需要function关键字.
3. 类的继承
子类可以继承父类的一些属性和方法。
class Father{ // 父类
}
class Son extends Father{ // 子类继承父类
}
实例:
class Father{
constructor(surname){
this.name = surname;
}
say(){
console.log('你的姓是' + this.name);
}
}
class Son extends Father{ // 子类继承了父类
}
var ss = new Son('廖')
ss.say(); // 你的姓是廖
- 继承中的属性或者方法查找原则:就近原则
4. super 关键字
调用父类的构造函数,也可以调用父类的普通函数。
class Father{
constructor(surname){
this.surname = surname;
}
saySurname(){
console.log('我的姓是' + this.surname);
}
}
class Son extends Father{
constructor(surname,firstname){ // 子承父类
super(surname); // 调用父类constructor(surname)
this.firstname = firstname; // 定义子类独有属性
}
sayFirstname(){
console.log('我的名字是:' + this.firstname);
}
}
var ss = new Son('吴','彦祖')
ss.saySurname();
ss.sayFirstname();
调用父类的普通函数
class Father{
say(){
return '我是父类 -->'
}
}
class Son extends Father{
say(){
// super.say() super 调用父类方法
return super.say() + '的子类';
}
}
var dome = new Son();
console.log(dome.say()); // 我是父类!的子类
super 必须在子类this之前调用。
子类在构造函数中使用super, 必须放到 this 前面 (必须先调用父类的构造方法,在使用子类构造方法)
注意点
- ES6中没有变量提升(变量的提前声明),所以必须先定义类,才能实例化对象。
- 类里面的共有属性和方法一定要加 this 使用。
类里面的 this 指向问题:
constructor
里面的 this 指向实例对象,方法里的 this
指向这个方法的调用者。
一些方法:
用法 | 说明 |
---|---|
insertAdjacentHTML(‘追加的位置’,‘追加的字符’) | 用字符串创建的元素追加到父元素里面 |
beforeend | 插入元素内部的最后一个子节点之后 |
ondblclick | 双击事件 |
input.select() | 文本框内容处于选中状态 |
双击禁止选中文字:
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
insertAdjacentHTML(position,text);
// 创建li元素和section元素
var li = '<li class="liactive"><span>测试1</span><span class="iconfont icon-guanbi"></span></li>';
// 把这两个元素追加到对应的父元素里面 beforeend(父元素内容的后面)
that.ul.insertAdjacentHTML('beforeend', li)
ES6新增语法
let 声明变量关键字
-
let 声明的变量时只在所处于的块级有效
if (true) { let b = 20; console.log(b) if (true) { let c = 30; } console.log(c); // c is not defined } console.log(b) // b is not defined
-
不存在变量提升
console.log(a); // a is not defined let a = 20;
-
存在暂时性死区问题
var num = 10 if (true) { console.log(num); // Cannot access 'num' before initialization let num = 20; }
-
防止循环变量变成全局变量
```js for (let i = 0; i < 5; i++) { console.log(i); } // 依次输出 0 1 2 3 4 5 ```
const 声明常量
作用:声明常量,常量就是值(内存地址) 不能变化的量
- 使用const关键字声明的常量具有块级作用域
if (true) {
const a = 10;
if (true) {
const a = 20;
console.log(a); // 20
}
console.log(a); // 10
}
console.log(a); // a is not defined
```
2. 声明常量时必须赋值
```js
const PI; // Missing initializer in const declaration
- 常量赋值后,值不能修改, 但是可以替换数组内的值。
const = PI;
PI = 100; // assignment to constant variable.
const ary = [100, 200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // ['a', 'b'];
ary = ['a','b']; // assignment to constant variable.
解构赋值
ES6 中允许从数值中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
数值解构
// 数组解构允许我们按照一一对应的关系从数组中提取值 然后将值赋值给变量
let ary = [1,2,3];
let [a, b, c, d, e] = ary;
console.log(a,b,c) // 1 2 3
console.log(e) // undefined
// 如果解构不成功,变量的值为undefined
let [foo] = [];
let [bar, foo] = [1];
对象解构
let person = { name: 'zhangsan', age: 20 };
// 从 person对象中解构(匹配) name 和 age 属性
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
箭头函数
写法:
() => {}
const fn = () => {}
- 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号
function sum(num1, num2) {
return num1 + num2;
}
const sum = (num1, num2) => num1 + num2;
- 如果形参只有一个,可以省略小括号
function fn(v){
retuen v;
}
const fn = v => v;
- 箭头函数不绑定this,没有自己的this关键字,在箭头函数中 this关键字将指向箭头函数定义位置中的this。
const obj = { name: '张三'}
function fn () {
console.log(this); // obj
return () => {
console.log(this) // obj
}
}
const resFn = fn.call(obj);
resFn();
箭头函数面试题:
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age) // 100
}
}
obj.say();
剩余参数
在最后的形参名前加
...
- 剩余参数语法允许我们将一个不定数量的参数表示( 存放至)为一个数组.
function sum (first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sun(10, 20, 30);
- 剩余参数和解构配合使用
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // wangwu
console.log(s2); // ['zhangsan', 'lisi']
ES6 新增方法
数组方法
扩展运算符 (展开语法)
- 将数组或者对象转为用逗号分隔的参数序列
let aty = [1, 2, 3];
// ...ary // 1, 2, 3
console.log(...ary); // 1 2 3
console.log(1, 2, 3); // 1 2 3
- 使用扩展运算符可以应用于合并数组
//方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
- 将类(伪)数组或可遍历对象转换为正真的数组
let oDivs = document.getElemenetsByTagName('div'); // HTMLCollection(10)
oDivs = [...oDivs]; // Array(7)
构造函数方法
- 将类(伪)数组或可遍历对象转换为正真的数组
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
- 可以传入第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
let arrayLike = {
"0": 1,
"1": 2,
"length": 2
}
let newAry = Array.from(aryLike, item => item * 2)
实例方法
find()
返回第一个符合条件元素,如果没有找到返回undefined
let ary = [{
id: 1,
name: '张三'
},{
id: 2,
name: '李四'
}];
let target = ary.find((item, index) => item.id == 2);
findIndex()
返回第一个符合条件元素索引位置,如果没有找到返回undefined
let ary =[1, 5, 10, 15];
let index = ary.findIndex((value,index) => value > 9);
console.log(index); // 2
includes()
查找某个数组是否包含给定的值,返回布尔值。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
let ary = ["a", "b", "c"];
console.log(ary.includes('a'))
模板字符串
ES6 新增的创建字符串的方式,使用反引号定义。
1、解析变量
let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello,my name is zhangsan
2、模板字符串换行
let resilt = {
name: 'zhangsan',
age: 20,
sex:'男'
}
let html = ` <div>
<span>${result.name}</span>
<span>${result.name}</span>
<span>${result.name}</span>
</div> `;
3、在模板字符串可以调用函数
colst aryHello = function () {
return '你好 世界!';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet); // 你好 世界!
字符串方法
startsWith()
- 表示参数字符串是否在原字符串的头部,返回布尔值
endsWith()
- 表示参数字符串是否在原字符串的尾巴,返回布尔值
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
repeat()
- 将原字符串重复n次,返回一个新字符串。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
Set 数据结构
- ES6 提供了新的数据结构 Set 它类似于数组 但是成员的值都是唯一的 没有重复的值。
- Set本身是一个构造函数,用来生成 Set 数据结构。
- Set函数可以接受一个数组作为参数,用来初始化。
// 创建一个set() 数据结构
const s = new Set;
const set = new Set([1, 2 ,3 ,4 , 5]);
set() 实例方法
方法 | 说明 |
---|---|
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 结构中的所有值
set() 遍历
- Set 结构的实例与数组一样,也拥有forEach方法。
// 遍历set数据结构 从中取值
const s = new Set(['a', 'b', 'c']);
s.forEach(value => {
console.log(value)
})
正则表达式
正则表通常被用来
提取和替换
那些符合某个模式(规则)的文本。列如验证表单、过滤敏感词、从字符串中获取我们想要的特定部分。
创建正则表达式
// 1. 利用 RegExp对象来创建 正则表达式
var regexp = new RegExp(/123/);
console.log(regexp);
// 2. 利用字面量创建 正则表达式
var rg = /123/;
test() 检测正则表达式
正则对象方法,用于检测字符串是否符合该规则,返回 true 或 false
var rg = /123/;
console.log(rg.test(123));
边界符
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
- ^ 和 $ 在一起使用时,表示必须是精确匹配
字符类
- 表示有一系列字符可供选择,只要匹配其中一个就可以了
- 所有可供选择的字符都放在方括号
[]
内。
var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
console.log(rg.test('andy'));
// 2. 字符组合 匹配26个英文字母和数字 1-9
/^[a-z0-9]$/.test('a') // true
// 3. [^] 方括号内部 取反符^
/^[^abc]$/.test('a') // false
量词符
量词符用来设定某个模式出现的次数。
量词 | 说明 |
---|---|
* | 匹配零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
括号使用
括号 | 说明 |
---|---|
{} 量词符 | 括号内表示重复次数 |
[] 字符集合 | 匹配方括号中的任意字符 |
() 分组 | 表示优先级 |
预定义类
预定义类指的是某些常见模式的简写方式
预定义 | 说明 | 等价于 |
---|---|---|
\d | 匹配0-9之间的任一数字 | [0-9] |
\D | 匹配所有0-9以外的字符 | [ ^0-9] |
\w | 匹配任意的字母、数字和下划线 | [A-Za-z0-9_] |
\W | 除所有字母、数字和下划线以外的字符 | [ ^A-Za-z0-9_] |
\s | 匹配空格 (包括换行符、制表符、空格符等) | [\t\r\n\v\f] |
\S | 匹配非空格的字符 | [ ^\t\r\n\v\f] |
全局匹配和忽略大小写
匹配模式 | 说明 |
---|---|
g | 全局匹配 (默认只匹配一个) |
i | 忽略大小写 |
gi | 全局匹配+忽略大小写 |
正则表达式中的替换
使用replace() 方法替换,返回值是替换完毕的新字符串。
stringObject.replace(regexp/substr,replacement)
- 第一个参数: 被替换的字符串 或者 正则表达式
- 第二个参数: 替换的字符串
<textarea name="" id="message"></textarea> <button>提交</button>
<div></div>
// 替换 replace
var text = document.querySelector('textarea');
var btn = document.querySelector('button');
var div = document.querySelector('div');
btn.onclick = function() {
div.innerHTML = text.value.replace(/激情|gay/g, '**');
}
ES5 的构造函数和原型
在 ES6之前 ,对象不是基于类创建的,而是用构建函数的特殊函数来定义对象和它们的特征。
构造函数
是一种特殊的函数,使用 new 来初始化对象,把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
构造函数存在浪费内存问题
解决办法:我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
构造函数原型 prototype
构造函数通过原型分配的函数是所有对象所共享的。
原型的作用就是 共享方法。
对象原型 __ proto __
对象都会有一个属性
__ proto __
指向构造函数的prototype
原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __ proto __ 原型的存在。
__proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
实例:
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('薛之谦', 18);
var zxy = new Star('灵境', 19);
console.log(ldh.sing === zxy.sing); // true
ldh.sing();
zxy.sing();
// 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
console.log(ldh);
console.log(ldh.__proto__ === Star.prototype); // true
-
__ proto __ 对象原型和原型对象 prototype 是等价的
-
方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的 sing
-
如果么有sing 这个方法,因为有
__proto__
的存在,就去构造函数原型对象 prototype 身上去查找sing这个方法。
constructor 构造函数
对象都会有一个属性 __ proto __ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __ proto __ 原型的存在。
constructor 主要用于记录该对象引用那个构造函数
,它可以让原型对象重新指向原来的构造函数。
注意:
很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
// Star.prototype.sing = function() {
// console.log('我会唱歌');
// };
// Star.prototype.movie = function() {
// console.log('我会演电影');
// }
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star,
sing: function() {
console.log('我会唱歌');
},
movie: function() {
console.log('我会演电影');
}
}
var ldh = new Star('薛之谦', 28);
var zxy = new Star('灵境', 26);
console.log(Star.prototype); // Object
console.log(ldh.__proto__); // Object
console.log(Star.prototype.constructor);
console.log(Star.prototype.constructor === ldh.__proto__.constructor); // true
构造函数、实例、原型对象之间的关系
原型链
成员查找机制
-
当访问一个对象的属性时首先查找这个对象自身有没有该属性。
-
如果没有就找原型(
__proto__指向的 prototype
)。 -
如果还没有就查找原型对象的原型(Object的原型对象)。
-
一直找到 Object 为止(null)。
原型对象里面方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象.
扩展内置对象方法
通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给对象增加自定义求偶数和的功能。
Array.prototype.sum = 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());
使用对象的形式创建方法会被覆盖
(如: Array.prototype = {} ),所以数组和字符串内置对象只能是 Array.prototype.xxx = function(){} 的方式。
call() 方法
call (设置调用时指向的对象, 传递其他参数…)
- 调用函数,并修改函数运行时this指向。
function fn(x, y) {
console.log('我想喝手磨咖啡');
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
// fn.call(); 1. 使用call() 可以调用函数
// 2. 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2);
ES5里的继承
ES6之前并没有给我们提供
extends
继承。我们可以通过构造函数+原型对象
模拟实现继承
,被称为组合继承。
借用父构造函数继承属性和方法
// 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) {
// 借用父构造函数继承属性 使用call()设置调用时this为 子构造函数的this
Father.call(this, uname, age);
// this 指向子构造函数的对象实例
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
var son = new Son('张全蛋', 28, 100);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);
ES5方法回顾
数组方法
- 迭代(遍历)方法:
forEach()、map()、filter()、 some()、every()
forEach() 遍历数组
- forEach适合于你并
不打算改变数据的时候
(打印或存入数据库)
array.forEach(function(currentValue, index, arr))
-
currentValue : 每一个数组元素
-
index :数组当前项的索引
-
arr : 数组对象本身
map() 数组遍历
- 适用于你要改变数据值的时候。速度更快且返回一个新的数组。
- 还可以使用 复合(composition) (map(), filter(), reduce() 等组合使用。
let arr = [1, 2, 3, 4, 5];
// 使用map将每一个元素乘以它们自身,然后筛选出那些大于10的元素,最终结果赋值给arr2。
let arr2 = arr.map(value => value * value).filter(value => value > 10);
// arr2 = [16, 25]
filter() 筛选数组
筛选时直接返回一个新数组。
array.filter(function(currentValue, index, arr))
实例:
var arr = [12,66,88,99,56,4,3]
var newarr = arr.filter(function(value,index){
// 筛选数组中大于20的值
return value > = 20;
//return value % 2 === 0; // 返回偶数
});
console.log(newarr);
some () 是否满足条件
返回布尔值, 找到第一个满足条件的元素则终止循环,不再继续查找。
var arr1 = ['red', 'pink', 'blue'];
var flag1 = arr1.some(function(value) {
return value == 'pink';
});
console.log(flag1); // true
filter返回一个新数组,含多个元素,find返回第一个符合条件元素。
indexof() 方法可返回某个指定字符串值在字符串中首次出现的位置。
字符串方法
trim() 删除两端空白字符
- 不影响原字符串,返回一个新字符串。
var str = ' an dy ';
var str1 = str.trim();
对象方法
Object.keys()
获取对象自身所有的属性
var obj = {
id: 1,
pname: '小米',
price: 1999,
num: 2000
};
// 获取对象自身所有的属性
var arr = Object.keys(obj);
// 使用 Object.keys() 获取所有属性后遍历对象
arr.forEach(function(value) {
console.log(value);
})
Object.defineProperty()
定义新属性或修改原有的属性
Object.defineproperty(obj, prop, descriptor)
- obj: 必需,目标对象
- prop:必需,需定义或修改的属性的名字
- descriptor: 必需,目标属性所拥有的特性
第三个参数 descriptor 说明:
- 必须以对象形式{} 书写
- value: 设置属性值 默认为undefined
- writable: 设置属性值是否可以被修改。true | false 默认为false不可修改。
- enumerable: 目标属性是否可以被遍历。
- configurable: 目标属性是否可以被删除或是否可以再次修改特特性。默认为false不可删除和修改。
var obj = {
id: 1,
pname: '小米',
price: 1999
};
Object.defineProperty(obj, 'address', {
value: '小米科技有限公司',
// 如果值为false 不允许修改这个属性值 默认值也是false
writable: true,
// enumerable 如果值为false 则不允许遍历, 默认的值是 false
enumerable: true,
// configurable 如果为false 则不允许删除这个属性 默认为false
configurable: true
});
console.log(obj.address);
函数进阶
定义方法
调用方式
- 普通函数
function fn() {
console.log('Hello !');
}
// fn(); fn.call()
- 对象的方法
var o = {
sayHi: function() {
console.log('人生的巅峰');
}
}
o.sayHi();
- 构造函数
function Star() {
// do somesing...
};
new Star();
- 绑定事件函数
btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
- 定时器函数
setInterval(function() {}, 1000); // 这个函数是定时器自动1秒钟调用一次
- 立即执行函数
// 立即执行函数是自调用
(function() {
console.log('hello 2021');
})();
this 指向问题
调用方式的不同决定了this 的指向不同。
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | (创建的) 实例对象 原型对象里面的方法指向实例对象 |
对象方法调用 | 改方法所属对象 |
事件绑定方法 | 绑定事件的那个对象 |
定时器函数 | window |
立即执行函数 | window |
详解:
<button>点击</button>
<script>
// 函数的不同调用方式决定了this 的指向不同
// 1. 普通函数 this 指向window
function fn() {
console.log('普通函数的this' + this);
}
window.fn();
// 2. 对象的方法 this指向的是对象 o
var o = {
sayHi: function() {
console.log('对象方法的this:' + this);
}
}
o.sayHi();
// 3. 构造函数 this 指向 ldh 这个实例对象 原型对象里面的this 指向的也是 ldh这个实例对象
function Star() {};
Star.prototype.sing = function() {
}
var ldh = new Star();
// 4. 绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象
var btn = document.querySelector('button');
btn.onclick = function() {
console.log('绑定时间函数的this:' + this);
};
// 5. 定时器函数 this 指向的也是window
window.setTimeout(function() {
console.log('定时器的this:' + this);
}, 1000);
// 6. 立即执行函数 this还是指向window
(function() {
console.log('立即执行函数的this' + this);
})();
</script>
改变函数内部 this 指向:
使用 bind()、call()、apply() 三种方法可以优雅的解决函数内部的 this 指向问题。
call() 和 apply() 调用函数同时改变this指向
var o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a + b);
};
// 1. call() 调用函数同时改变this指向
fn.call(o, 1, 2);
// apply() 调用函数同时改变this指向,参数必须是数组(伪数组)
fn.apply(o, ['pink']);
bind() 不调用函数改变this指向
示例: 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮:
<button>点击</button>
<button>点击</button>
<button>点击</button>
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);
}
}
总结:
相同点:
- 都可以改变函数内部 this 指向。
不同点:
- call 和 apply 会调用函数,并且改变函数内部this指向;
- bind 不会调用函数,可以改变函数内部this指向
应用场景:
- call() 经常做继承;
- apply() 经常跟数组有关系,如借助数学对象求 max | min;
- bind() 在不想调用函数又想改变this指向时使用,如改变定时器内部的this指向。
高阶函数
回调函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
function fn(a, b, callback) {
console.log(a + b);
callback && callback();
}
fn(7, 10, function() {
console.log('我是最后调用的');
});
函数也是一种数据类型,同样可以作为参数,传递给另一个参数使用,最典型的就是作为回调函数
。
递归函数
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
- 递归函数的作用和循环效果一样
- 由于递归很容易发生 “栈溢出”错误,所以必须要加
退出条件 return
实例: 利用递归函数求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));
console.log(fn(4));
// 详细思路 假如用户输入的是 3
//return 3 * fn(2)
//return 3 * (2 * fn(1))
//return 3 * (2 * 1)
//return 3 * (2)
//return 6
实例2: 利用递归函数求斐波那契数列 (兔子序列)
// 1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));
console.log(fb(6));
闭包
变量作用域
分为全局变量局部变量
- 函数内部可以使用全局变量
- 函数外部不可以使用局部变量
- 当函数执行完毕,本作用域的局部变量将会被销毁。
什么是闭包 ?
一个作用域可以访问另外一个函数内部的局部变量。
闭包的作用:
延伸了变量的作用范围
我们函数外面的作用域可以访问函数内部的局部变量。
function fn1(){ // fn1 就是闭包函数
var num = 10;
function fn2(){
console.log(num); // 10
}
fn2()
}
思考题1:
var name = "The Window";
var obj1 = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(obj1.getNameFunc()()) // The Window
思考题2 :
var name = "The Window";
var obj2 = {
name: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
}
};
console.log(obj2.getNameFunc()()) // My Object
浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的值拷贝引用
- 深拷贝拷贝多层,每一级别的数据都会拷贝。
ES6新增方法可以浅拷贝
Object.assign(target, …sources)
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
Object.assign(o, obj);
严格模式
- 为整个 js 脚本开启严格模式
<script>
"use strict";
console.log("这是严格模式。");
</script>
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在立即执行的匿名函数之中,这样独立创建一个作用域而不影响其他 script脚本文件。
(function(){
"use strict";
var num = 10;
function fn(){}
})();
为函数开启严格模式
// 此时只是给fn函数开启严格模式
function fn() {
'use strict';
// 下面的代码按照严格模式执行
}
严格模式变化
变量常规:
- 没有变量声明提前,变量名必须先声明再使用;
- 严禁删除已声明的变量;
严格模式下 this 指向问题:
- 严格模式下全局作用域中函数中的this 是 undefined,非Windows;
- 构造函数不加new调用,this 会报错;
- new 实例化的构造函数指向创建的对象实例;
- 定时器 this 还是指向 window;
- 事件、对象还是指向调用者;
- 严格模式下函数里面的参数不允许有重名。