JS进阶4之(this指向 改变this方法 class类的封装 继承 浅拷贝 深拷贝
一、this
1.1 默认值
1.1.1 普通函数
普通函数的调用方式决定了this的值(谁调用this指向谁)
全局作用域下 this指向window
1.普通函数(直接拿过来调用的函数)
function fn() {
console.log('普通函数的this', this);//指向window
}
fn()
2.构造函数 通过new调用
function Person(uname) {
this.uname = uname
console.log('构造函数的this', this);//指向实例对象 (obj)
}
let obj = new Person('张飞');
3.方法
let objs = {
uname: '张三丰',
age: 22,
fei: function () {
console.log('方法中的this', this);//指向obj
}
}
objs.fei()
4.事件处理函数
document.addEventListener('click', function () {
console.log('事件处理函数的this', this);//指向document或者调用者
})
5.定时器setInterval
setInterval(function () {
console.log('定时器中的this', this);//指向window
}, 100000);
6.自调用函数 (前边要加;)
(function () {
console.log('自调用函数的this', this);//指向window或者调用者
})()
严格模式
开启严格模式 "use strict"
"use strict"
//正常模式 严格模式
//开启严格模式 : "use strict"
//放在作用域的开头(第一句话) 可以放全局作用域 局部作用域
//如果放在全局的开头 代表全局都要严格模式
//如果放在函数的开头 代表函数内部严格模式
// -----------------------------
//1.变量必须定义 再使用,不准删除变量(delete n)
// n = 3;
// console.log('n', n);// n is not defined
//2.普通函数中的this 指向undefined
// function fn() {
// console.log('this', this);//undefined
// }
// fn()
//3.函数的形参不能重名
function fn(a, a) {
return a + a //报错
}
fn(1, 2)
1.1.2箭头函数
箭头函数并不存在this
箭头函数中访问的 this 不过是箭头函数所在作用域的 this 变量。
// setInterval(() => {
// console.log('this', this);//指向window
// }, 1000)
// document.addEventListener('click', function () {
// setTimeout(() => {
// console.log('this', this);//指向document
// })
// })
//事件处理函数
document.addEventListener('click', () => {
console.log('this', this);//指向window
})
//构造函数
// function Person(uname){
// this.uname=uname
// }
// let obj=new Person('张飞');
let Person = (uname) => {
this.uname = uname
}
new Person() //报错
事件回调函数使用箭头函数时,this 为全局的 window
// DOM 节点
let btn = document.querySelector('.btn');
// 箭头函数 此时 this 指向了 window
btn.addEventListener('click', () => {
console.log(this);
})
// 普通函数 此时 this 指向了 DOM 对象
btn.addEventListener('click', function () {
console.log(this);
})
1.2 定义值
1.2.1 call
call 方法调用函数,同时指定函数中 this 的值
函数.call(this,参数1,参数2...)
函数调用call call会执行调用函数,在执行中 改变this指向
//call 改变this指向
function fun(a, b) {
console.log(this, a, b) //this指向obj
this.age = 33
}
let obj = {
uname: '张三',
age: 22,
fei: function () {
console.log('飞');
}
}
let o = {
uname: '李四'
}
//call
//函数.call(this,参数1,参数2,参数3.....) 函数不能加()
//要让函数中的this指向谁 ()就写谁
//函数调用call call会调用函数执行 再执行的过程中 改变this的指向
fun.call(obj, 1, 2) //call改变只会在调用的时候改变
console.log('对象', obj);//改变了
fun(3, 4) //指向window
obj.fei.call(o) //打印 飞
1.2.2 apply
使用 apply 方法调用函数,同时指定函数中 this 的值
函数.apply(this,[参数1,参数2...])
函数调用apply apply会执行调用函数,在执行中 改变this指向
// 第二个参数为要传的数组
// 函数.apply(this,[参数1,参数2,参数3...])
// apply会让函数调用执行
// 在执行过程中 改变this指向
// 会把数据取出来 解开
function fn(a, b) {
console.log('this', this, a, b);
}
let obj = {
uname: '张三'
}
fn.apply(obj, [1, 2])
let obj = {
uname: '张三',
age: 19,
taiji: function () {
console.log('this', this);
}
}
let o = {
uname: '张无忌',
age: 22
}
obj.taiji.apply(o)
利用apply求数组的最大值
//apply
// let arr = [11, 22, 33, 44, 66]
// //求数组的最大值
// // Math.max() 传的数字
// let max = Math.max.apply(null, arr) //利用apply传数组
// console.log('最大值', max);
let arr = [11, 22, 33, 44, 66]
//...剩余参数(函数和解构赋值) 展开扩展运算符(数组,字符串,对象)
let re = Math.max(...arr)
console.log(...arr);//11, 22, 33, 44, 66
1.2.3 bind
bind 方法并不会调用函数,而是创建一个指定了 this 值的新函数
函数.bind(this,参数1,参数2...)
既想改变this 又不想立即执行函数 就是用bind
function fn(a, b) {
console.log('this', this, a, b);
}
let obj = {
uname: '张三',
age: 22
}
fn.bind(obj, 123, 345)() //执行函数 ()里也可以传
console.log('函数本身', fn.bind(obj));//函数本身
let btn = document.querySelector('button')
btn.addEventListener("click", function () {
// {}指this向btn
//禁用按钮
this.disabled = true;
//开启定时器
window.setTimeout(function () {
//{}内this指向window
console.log('打印this', this);//指向btn
this.disabled = false;
}.bind(this), 5000)
})
二、class
类的本质就是构造函数
//ES6之前的写法
//function Person(){}
//let o=new Person()
//ES6
//类的本质:构造函数
class Person {
static head = 1;
}
//实例对象
let obj = new Person();
console.log('原型对象', Person.prototype);
2.1 封装
class(类)是 ECMAScript 6 中新增的关键字,专门用于创建类的,类可被用于实现逻辑的封装
//定义类 不加()
//class 类名{}
class Person {
}
//实例化类 要加()
let obj = new Person();
console.log('obj', obj);
console.log('检测是否是对象', obj instanceof Object);//true
2.1.1 静态成员
static 关键字用于声明静态属性和方法
静态属性和方法直接通过构造函数进行访问
添加静态方法不准有 function
class Person {
//添加静态成员
// static 声明静态成员
static language = '汉语'
static skin = '黄皮肤'
//添加方法 不准有function
static walk() {
console.log('方法');
}
}
//实例化对象
let obj = new Person()
console.log(obj.skin);//undefined 不能访问
console.log(Person.skin);//构造函数 可以访问
2.1.2 实例成员
类中封装的并不是变量和函数,因此不能使用关键字 let、const 或 var
class Person {
// static 声明静态成员
static language = '汉语'
//实例成员添加
uname = '张三';
age = 23;
sex = '男';
say() {
console.log('添加实例化方法');
}
}
//实例化
let obj = new Person();
console.log('obj', obj);
obj.say()
2.1.3 构造函数
创建类时在类的内部有一个特定的方法 constructor
constructor在类被实例化时自动被调用,常被用于处理一些初始化的操作
constructor 方法接收实例化时传入的参数
constructor 并非是类中必须要存在的方法
//类中的构造函数
//constructor 构造函数 构造方法 构造器
//是一个特殊的方法 如果不写,会自动的帮我们创建这个构造方法
//这个constructor在实例化对象的时候就会执行
class Person {
head = 1;
//实例化就会执行
constructor(a, uname) {
//constructor接收实参 做初始化的操作
this.uname = uname
console.log('a', a, uname);//执行了 333
}
say() {
console.log('说话');
}
}
let obj = new Person(333, '名字1');//传参
let objs = new Person(333, '名字2');//传参
属性放constructor中 方法放在外边
class Star {
//属性放constructor里边
constructor(uname, age) {
this.uname = uname
this.age = age
}
//方法放外边
sing() {
console.log('唱歌方法');
}
}
let o = new Star('张三', '22岁');
console.log('o', o);
2.2 继承
2.2.1 extends
extends 是 ECMAScript 6 中实现继承的简洁语法
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();
2.2.2 super
在继承的过程中子类中 constructor 中必须调 super 函数,否则会有语法错误
子类构造函数中的 super 函数的作用是可以将子类实例化时获得的参数传入父类的构造函数之中。
class Person {
constructor(uname, age) {
this.uname = uname;
this.age = age;
this.head = 1;
this.eyes = 2
}
say() {
console.log('父亲的唱歌方法');
}
}
//继承 子类有自己的成员
class Star extends Person {
//继承中 如果子类有自己的constructor
//必须通过super才可以调用父类的方法
constructor(uname, age, score) {
//先调用super
super(uname, age)
//再设定自己的成员
this.score = score;
console.log('子类形参', uname, age, score);
}
//如果子类 父类都有这个方法(同名方法) 就近原则
//如果依旧想用父类的方法 用super
say() {
//调用父亲的say方法
super.say()
// console.log('儿子的唱歌方法');
}
}
let obj = new Star('阿飞', 22, 99);
console.log('obj', obj);
obj.say(); //儿子的方法 就近方法
三、拷贝
拷贝不是直接赋值 分为深拷贝 浅拷贝
3.1 浅拷贝
只拷贝外面一层的(只拷贝简单数据类型的)
方法 Object.assgin(newObj,obj)
let obj = {
uname: '张三丰',
age: 22,
color: ['red', 'blue'],
message: {
index: 6
}
}
let newObj = {}
//浅拷贝
Object.assign(newObj, obj)
//遍历对象
// for (let key in obj) {
// console.log('key', key);
// newObj[key] = obj[key]
// }
obj.message.index = 666
// newObj.uname = obj.uname
// newObj.age = obj.age
// obj.name = '更改了'
console.log('newObj', newObj);
3.2 深拷贝
所有层都拷贝
let obj = {
uname: '张三丰',
age: 22,
color: ['red', 'blue'],
message: {
index: 6
}
}
let newObj = {}
//浅拷贝
Object.assign(newObj, obj)
//遍历对象
//如果遇到obj[key]是复杂数据类型 在遍历数组
// for (let key in obj) {
// console.log('key', key);
// newObj[key] = obj[key]
// }
//拷贝 递归函数
function kaobei(newObj, obj) {
for (let key in obj) {
// console.log('key', key);
//如果是数组
if (obj[key] instanceof Array) {
console.log('这个是数组', obj[key]);
//先保证newObj[key]是数组
newObj[key] = []
//再调用拷贝函数
kaobei(newObj[key], obj[key])
} else if (obj[key] instanceof Object) {
console.log('这个是对象', obj[key]);
newObj[key] = {}
//再调用拷贝函数
kaobei(newObj[key], obj[key])
//如果是对象
} else {
//如果是简单数据类型
newObj[key] = obj[key]
}
}
}
kaobei(newObj, obj)
obj.color[0] = '改变了'
console.log('newObj', newObj);
console.log('obj', obj);