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);
}
sayHi();
window.sayHi();
let user = {
name: '小明',
walk: function () {
console.log(this);
}
};
user.sayHi = sayHi;
uesr.sayHello = sayHello;
user.sayHi();
user.sayHello();
</script>
注: 普通函数没有明确调用者时 this 值为 window ,严格模式下没有调用者时 this 的值为 undefined 。
箭头函数
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。
<script>
console.log(this);
let sayHi = function() {
console.log(this);
}
let user = {
name: '小明',
walk: () => {
console.log(this);
},
sleep: function () {
let str = 'hello';
console.log(this);
let fn = () => {
console.log(str);
console.log(this);
}
fn();
}
}
user.sayHi = sayHi;
user.sayHi();
user.sleep();
user.walk();
</script>
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window ,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:
<script>
let btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
console.log(this);
})
btn.addEventListener('click', function () {
console.log(this);
})
</script>
同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数,如下代码所示:
<script>
function Person() {
}
Person.prototype.walk = () => {
console.log('人都要走路...');
console.log(this);
}
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,
}
sayHi.call(user);
sayHi.call(student);
function counter(x, y) {
return x + y;
}
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
}
sayHi.apply(user);
sayHi.apply(student);
function counter(x, y) {
return x + y;
}
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
}
let sayHello = sayHi.bind(user);
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>
总结:
静态成员
<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('人都得要睡觉...');
}
}
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;
}
legs = 2;
arms = 2;
walk () {
console.log('人类都会走路...');
}
}
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 );
|