了解函数中 this 在不同场景下的默认值,动态指定函数 this 的值,提升代码封装的灵活度。
- 能够区分不同场景下函数中
this
的默认值 - 知道箭头函数的普通函数的区别,掌握箭头函数的使用
- 能够动指定函数中
this
的值 - 了解基于类的面向对象的实现语法
一、this
了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法。
- 事件处理函数中,this表示事件源(事件给谁添加的,this就表示谁)
- 构造函数中或者类中this、对象的方法中,this表示实例对象
- 普通函数中、全局中、回调函数中,this表示window对象
- 箭头函数中没有 this(如果出现this,则按照作用域链去查找)
课堂代码
// 1. 事件处理函数中,this表示事件源(事件给谁添加的,this就表示谁)
// let btn = document.querySelector('button')
// btn.addEventListener('click', function () {
// // console.log(this) // btn
// console.log(this === btn) // true,说明this就是btn按钮
// })
// 2. 构造函数中或者类中this、对象的方法中,this表示实例对象
// let obj = {
// age: 50,
// say: function () {
// // console.log(this) // obj对象,方法中的this表示当前的对象
// console.log(this.age)
// }
// }
// // console.log(obj.age)
// obj.say()
// function Person() {
// // this 表示根据这个构造函数实例化出来的对象
// this.age = 100
// this.say = function () {
// console.log(this.age) // 实例对象的方法中的this,还是表示实例对象
// }
// }
// let obj = new Person()
// class Person {
// constructor() {
// this.age = 200
// this.say()
// }
// say() {
// console.log(this.age)
// }
// }
// let obj = new Person()
// 3. 普通函数中、全局中、回调函数中,this表示window对象
console.log(this)
function abc() {
console.log(this)
}
abc()
setTimeout(function () {
console.log(this) // window
})
// 4. 箭头函数中没有this
1.1 默认值
this
是 JavaScript 最具“魅惑”的知识点,不同的应用场合 this
的取值可能会有意想不到的结果,在此我们对以往学习过的关于【 this
默认的取值】情况进行归纳和总结。
普通函数
普通函数的调用方式决定了 this
的值,即【谁调用 this
的值指向谁】,如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this);
}
// 函数表达式
let sayHello = function () {
console.log(this);
}
// 函数的调用方式决定了 this 的值
sayHi(); // window
window.sayHi();
// 普通对象
let user = {
name: '小明',
walk: function () {
console.log(this);
}
};
// 动态为 user 添加方法
user.sayHi = sayHi;
uesr.sayHello = sayHello;
// 函数调用方式,决定了 this 的值
user.sayHi();
user.sayHello();
</script>
注: 普通函数没有明确调用者时 this
值为 window
,严格模式下没有调用者时 this
的值为 undefined
。
箭头函数
箭头函数中的 this
与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this
!箭头函数中访问的 this
不过是箭头函数所在作用域的 this
变量。
<script>
console.log(this); // 此处为 window
// 箭头函数
let sayHi = function() {
console.log(this); // 该箭头函数中的 this 为函数声明环境中 this 一致
}
// 普通对象
let user = {
name: '小明',
// 该箭头函数中的 this 为函数声明环境中 this 一致
walk: () => {
console.log(this);
},
sleep: function () {
let str = 'hello';
console.log(this);
let fn = () => {
console.log(str);
console.log(this); // 该箭头函数中的 this 与 sleep 中的 this 一致
}
// 调用箭头函数
fn();
}
}
// 动态添加方法
user.sayHi = sayHi;
// 函数调用
user.sayHi();
user.sleep();
user.walk();
</script>
在开发中【使用箭头函数前需要考虑函数中 this
的值】,事件回调函数使用箭头函数时,this
为全局的 window
,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:
<script>
// DOM 节点
let btn = document.querySelector('.btn');
// 箭头函数 此时 this 指向了 window
btn.addEventListener('click', () => {
console.log(this);
})
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener('click', function () {
console.log(this);
})
</script>
同样由于箭头函数 this
的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
<script>
function Person() {
}
// 原型对像上添加了箭头函数
Person.prototype.walk = () => {
console.log('人都要走路...');
console.log(this); // widow
}
let p1 = new Person();
p1.walk();
</script>
1.2 定义值
call、apply、bind只能由函数调用。
以上归纳了普通函数和箭头函数中关于 this
默认值的情形,不仅如此 JavaScript 中还允许指定函数中 this
的指向,有 3 个方法可以动态指定普通函数中 this
的指向:
call
使用 call
方法调用函数,同时指定函数中 this
的值,使用方法如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16,
}
// 调用函数并指定 this 的值
sayHi.call(user); // this 值为 user
sayHi.call(student); // this 值为 student
</script>
总结:
call
方法能够在调用函数的同时指定this
的值- 使用
call
方法调用函数时,第1个参数为this
指定的值 call
方法的其余参数会依次自动传入函数做为函数的参数
基于call的使用:
<input type="checkbox">
<input type="checkbox">
<input type="checkbox">
<button>判断</button>
<script>
// 扩展一个小知识(记一下规律)
let btn = document.querySelector('button')
let ck = document.querySelectorAll('input')
btn.addEventListener('click', function () {
// console.log(ck) // 伪数组
// 复选框.checked
// console.log(ck[0].checked) // 如果复选框选中,则返回true;否则返回false
// console.log(ck[1].checked)
// console.log(ck[2].checked)
// 判断三个复选框是否都选中(checked属性值都是true)了?
// let result = [6, 9, 7, 8].every(item => item > 5)
// console.log(result)
let result = [].every.call(ck, item => item.checked === true)
// 规律是:如果一个伪数组,希望使用数组方法的话,可以使用下面的语法
// [].方法.call(伪数组, 方法的参数)
// let result = ck.every(item => {
// return item.checked === true
// })
console.log(result)
})
</script>
apply
使用 apply方法调用函数,同时指定函数中 this
的值,使用方法如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16
}
// 调用函数并指定 this 的值
sayHi.apply(user); // this 值为 user
sayHi.apply(student); // this 值为 student
</script>
课堂代码:
// function sayHi(x, y) {
// console.log(x + y)
// console.log(this)
// }
// let user = { name: 'user', age: 20 }
// let student = { name: 'stu', age: 30 }
// //
// // 函数.apply(对象) // 调用函数,并且把函数中this改为传入的那个对象
// // sayHi.apply(user)
// // sayHi.apply(student)
// // sayHi.call(user, 3, 5)
// sayHi.apply(user, [3, 5])
// apply的应用
// Math.max(3, 2, 9, 0, 7)
console.log(Math.max.apply(null, [3, 2, 9, 0, 7]))
console.log(Math.max(...[5, 10, 20, 15]))
总结:
apply
方法能够在调用函数的同时指定this
的值- 使用
apply
方法调用函数时,第1个参数为this
指定的值 apply
方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数bind
bind
方法并不会调用函数,而是创建一个指定了this
值的新函数,使用方法如下代码所示: ```javascript function sayHi(x, y) { console.log(x + y) console.log(this) }
let user = { name: ‘张三’, age: 20 }
// 语法: // call ,表示调用函数,并修改函数中this的指向 // bind,得到一个新函数,并修改函数中this的指向 // 函数.bind(对象, 3, 5) // let fn = sayHi.bind(user, 3, 5) // fn()
// sayHi.bind(user, 3, 5)()
let fn = sayHi.bind(user) fn(6, 5)
注:`bind` 方法创建新的函数,与原函数的唯一的变化是改变了 `this` 的值。<br />**改变this三个方法总结:**
call: fun.call(this,arg1, arg2,……)
apply: fun.apply(this, [arg1, arg2,……])
bind: fun.bind(this, arg1, arg2,……)
相同点: 都可以用来改变this指向,第一个参数都是this指向的对象 区别: call和apply:都会使函数执行,但是参数不同 bind:不会使函数执行,参数同call
<a name="Lm3gC"></a>
# 二、class
> 了解 JavaScript 中基于 class 语法的面向对象编程,为后续课程中的应用做好铺垫。
传统面向对象的编程序语言都是【类】的概念,对象都是由类创建出来,然而早期 JavaScript 中是没有类的,面向对象大多都是基于构造函数和原型实现的,但是 ECMAScript 6 规范开始增加了【类】相关的语法,使得 JavaScript 中的面向对象实现方式更加标准。
<a name="GKBPj"></a>
## 2.1 封装
class(类)是 ECMAScript 6 中新增的关键字,专门用于创建类的,类可被用于实现逻辑的封装。
```html
<script>
// 创建类
class Person {
// 此处编写封装逻辑
}
// 实例化
let p1 = new Person();
console.log(p1);
</script>
实例成员
<script>
// 创建类
class Person {
// 实例属性
name = '小明';
// 实例方法
sleep () {
console.log('sleeping...')
}
}
// 实例化
let p1 = new Person();
p1.sayHi();
</script>
总结:
- 关键字
class
封装了所有的实例属性和方法 类中封装的并不是变量和函数,因此不能使用关键字
let
、const
或var
静态成员
<script> // 创建类 class Person { // 静态属性 static version = '1.0.0'; // 静态方法 static getVersion = function () { console.log(this.version); } } // 静态方法直接访问 console.log(Person.version); Person.getVersion(); </script>
总结:
static
关键字用于声明静态属性和方法-
构造函数
创建类时在类的内部有一个特定的方法
constructor
,该方法会在类被实例化时自动被调用,常被用于处理一些初始化的操作。<script> class Person { // 实例化时 立即执行 // 构造函数、构造方法、构造器 constructor (name, age) { this.name = name; this.age = age; } // 实例方法 walk () { console.log(this.name + '正在走路...'); } } // 实例化 let p1 = new Person('小明', 18); p1.walk(); </script>
总结:
constructor
是类中固定的方法名constructor
方法在实例化时立即执行constructor
方法接收实例化时传入的参数-
2.2 继承
extends
extends
是 ECMAScript 6 中实现继承的简洁语法,代码如下所示:<script> class Person { // 父类的属性 legs = 2; arms = 2; eyes = 2; // 父类的方法 walk () { console.log('人类都会走路...'); } // 父类的方法 sleep () { console.log('人都得要睡觉...'); } } // Chinese 继承了 Person 的所有特征 class Chinese extends Person {} // 实例化 let c1 = new Chinese(); c1.walk(); </script>
如上代码所示
extends
是专门用于实现继承的语法关键字,Person
称为父类、Chinese
称为子类。super
在继承的过程中子类中
constructor
中必须调super
函数,否则会有语法错误,如下代码所示: ```html总结: 1. 子类如果不存在 constructor 则可以不调用 super() 1. 子类存在 constructor 则必须调用 super() 1. 在子类方法中,还能通过 `super.xxx()` 的方式调用父类方法 <a name="CkeiI"></a> ## 2.3 写在最后 ECMAScript 6 中基于类的面向对象相较于构造函数和原型对象的面向对象本质上是一样的,基于类的语法更为简洁,未来的 JavaScript 中也都会是基于类的语法实现,当前阶段先熟悉基于类的语法,后面课程中会加强基于类语法的实践。 <a name="CV0TE"></a> # 三、对象的拷贝 拷贝不是直接赋值 <a name="lkf7M"></a> ## 3.0 instanceof - instanceof 用于判断一个对象的构造函数是什么? - 语法:`对象 instanceof 构造函数` - 对象 instanceof 原型链上的任何一个构造函数,都会得到true ```javascript // instanceof是一个关键字,用于检测对象的构造函数是什么 function Person() { } let obj = new Person() // console.log(对象 instanceof 构造函数) console.log(obj instanceof Person) // true console.log(obj instanceof Array) // false console.log(obj instanceof Object) //true。因为 对象 instanceof 原型链上的任何一个构造函数,都会得到true // 如何判断数组和对象 // let arr = [3, 4, 5] // new Array(3, 4, 5) // let obj = { name: 'zs' } // if (obj instanceof Object) { // // 说明是对象 // console.log('obj是对象') // } // if (arr instanceof Array) { // console.log('arr是数组') // } // console.log(Array.isArray(arr)) // true // console.log(Array.isArray(obj)) // false
3.0 递归函数
// 递归入门 // let i = 1 // function fn() { // // 加一个条件,在适当的时候,退出函数,否则就形成死循环了 // if (i > 10) return // console.log(i) // i++ // fn() // } // fn() // -------------- 计算阶乘 ------------------------------ // 10的阶乘 = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 // 5的阶乘 = 5 * 4 * 3 * 2 * 1 // 规律: 10的阶乘 = 10 * 9的阶乘 // 写递归函数,一定要相信自己。我写的函数一定会完成这个功能 // 接下来,表演一下,我写一个函数,功能是: 计算一个数的阶乘 // 当我需要计算一个数的阶乘的时候,就调用这个函数 // function fn(n) { // // return n的阶乘 // if (n === 1) { // 如果n是1,则直接返回1 // return 1 // } // // 如果n不是1,自己计算 // return n * fn(n - 1) // } // console.log(fn(10)) // -------------------------------------------------- // 计算斐波那契数列 // 位置: 1 2 3 4 5 6 7 8 ........................... 40 // 数字: 1 1 2 3 5 8 13 21 ........................... ? // 规律:位置n的数字 = n-1位置的数字 + n-2位置的数字 // 写一个函数,函数的功能:计算一个位置的数字 function fn(n) { if (n == 1 || n == 2) { return 1 } // return n-1位置的数字 + n-2位置的数字 return fn(n - 1) + fn(n - 2) } console.log(fn(40))
3.1 浅拷贝
let obj1 = { name: 'zs', age: 20, height: 180 } let obj2 = {} // 循环遍历 obj1,循环一次,将一个属性拷贝到 obj2 里面 for (let key in obj1) { // key 表示对象的属性 // console.log(key) // name age height // console.log(obj1[key]) // zs 20 180 obj2[key] = obj1[key] } obj2.height = 200 console.log(obj1) console.log(obj2)
3.2 深拷贝
// 深拷贝:不止拷贝对象的一层,拷贝的时候,如果发现对象的某个属性是引用类型的,则继续拷贝;直至得到两个完全独立的对象 let obj1 = { name: 'zs', age: 20, info: [180, 80], dog: { color: '#000', age: 3 } } let obj2 = {} // 递归 // 写一个函数,功能是实现浅拷贝(拷贝的时候,判断一下,如果对象的值是数组或者对象,则继续浅拷贝) function kaobei(obj1, obj2) { for (let key in obj1) { // 'obj1[key] 是数组吗?' if (Array.isArray(obj1[key])) { obj2[key] = [] kaobei(obj1[key], obj2[key]) } else if (obj1[key] instanceof Object) { // 'obj1[key] 是对象吗?' obj2[key] = {} kaobei(obj1[key], obj2[key]) } else { obj2[key] = obj1[key] } } } kaobei(obj1, obj2) // 改其中一个对象 obj1.info[0] = 170 obj1.dog.color = 'red' console.log(obj1) console.log(obj2)