JavaScript 进阶 - 第4天
了解函数中 this 在不同场景下的默认值,动态指定函数 this 的值,提升代码封装的灵活度。
- 能够区分不同场景下函数中
this
的默认值 - 知道箭头函数的普通函数的区别,掌握箭头函数的使用
- 能够动指定函数中
this
的值 - 了解基于类的面向对象的实现语法
一、this
了解函数中 this 在不同场景下的默认值,知道动态指定函数 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 定义值
以上归纳了普通函数和箭头函数中关于 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
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
let result = counter.call(null, 5, 10);
console.log(result);
</script>
总结:
call
方法能够在调用函数的同时指定this
的值- 使用
call
方法调用函数时,第1个参数为this
指定的值 call
方法的其余参数会依次自动传入函数做为函数的参数
apply
使用 call
方法调用函数,同时指定函数中 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
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
let result = counter.apply(null, [5, 10]);
console.log(result);
</script>
总结:
apply
方法能够在调用函数的同时指定this
的值- 使用
apply
方法调用函数时,第1个参数为this
指定的值 apply
方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数
bind
bind
方法并不会调用函数,而是创建一个指定了 this
值的新函数,使用方法如下代码所示:
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
// 调用 bind 指定 this 的值
let sayHello = sayHi.bind(user);
// 调用使用 bind 创建的新函数
sayHello();
</script>
注:bind
方法创建新的函数,与原函数的唯一的变化是改变了 this
的值。
改变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
二、class
了解 JavaScript 中基于 class 语法的面向对象编程,为后续课程中的应用做好铺垫。
传统面向对象的编程序语言都是【类】的概念,对象都是由类创建出来,然而早期 JavaScript 中是没有类的,面向对象大多都是基于构造函数和原型实现的,但是 ECMAScript 6 规范开始增加了【类】相关的语法,使得 JavaScript 中的面向对象实现方式更加标准。
2.1 封装
class(类)是 ECMAScript 6 中新增的关键字,专门用于创建类的,类可被用于实现逻辑的封装。
<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
方法接收实例化时传入的参数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
函数,否则会有语法错误,如下代码所示:
<script>
class Person {
// 构造函数
constructor (name, age) {
this.name = name;
this.age = age;
this.fn = function(){
}
}
// 父类的属性
legs = 2;
arms = 2;
walk () {
console.log('人类都会走路...');
}
}
// 子类 English 继承了父类 Person
class English extends Person {
// 子类的构造函数
constructor (name, age) {
super(name, age);
}
// 子类的属性
skin = 'white';
language = '英文';
}
// 实例化
let e1 = new English('jack', 18);
console.log(e1.name);
</script>
子类构造函数中的 super
函数的作用是可以将子类实例化时获得的参数传入父类的构造函数之中。
2.3 写在最后
ECMAScript 6 中基于类的面向对象相较于构造函数和原型对象的面向对象本质上是一样的,基于类的语法更为简洁,未来的 JavaScript 中也都会是基于类的语法实现,当前阶段先熟悉基于类的语法,后面课程中会加强基于类语法的实践。
拷贝:
拷贝不是直接赋值
浅拷贝:
含义:只拷贝最外面层的拷贝方式
let obj = {
uname : '张三丰',
age : 22,
sex : '男',
color : ['red', 'blue', 'yellow', 'pink'],
message : {
index : 1,
score : 99
}
}
let newObj = {};
Object.assign(newObj, obj);
console.log( obj, newObj );
深拷贝:
含义:所有层都拷贝的方式
let obj = {
uname : '张三丰',
age : 22,
sex : '男',
color : ['red', 'blue', 'yellow', 'pink'],
message : {
index : 1,
score : 99
}
}
let newObj = {};
function kaobei (newObj, obj) {
for ( let key in obj ) {
if ( obj[key] instanceof Array ) {// obj[key] 是数组
// obj[key]是数组,遍历
newObj[key] = [];
kaobei(newObj[key], obj[key]);
} else if ( obj[key] instanceof Object ) { // obj[key]是对象
// obj[key]是对象,遍历
newObj[key] = {};
kaobei(newObj[key], obj[key]);
} else {
newObj[key] = obj[key];
}
}
}
kaobei(newObj, obj);
obj.message.score = 123;
console.log( obj, newObj );