JavaScript 高级
面向对象
this:
了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法
问:this到底指向谁?
答:谁调用这个函数,this就指向谁
通常指向调用者
<script>
// 普通函数
function fn(){
console.log(this); // 指向Window 调用者
}
// 事件处理函数
document.addEventListener('click',function(){
console.log(this); // 指向事件源 调用者
})
// 构造函数
function Person(){
console.log(this); // 指向new的实例对象
}
new Person();
// 方法
let obj = {
uname:'张飞',
fei:function(){
console.log(this); // 指向调用者 obj
}
}
obj.fei();
// 定时器
window.setInterval(function(){
console.log(this); // 指向Window,因为Window是调用者
},1000)
// 自执行函数
(function(){
console.log(this); // 指向Window 调用者
})()
</script>
严格模式:
严格模式:‘use strict’
开启严格模式:“use strict”(作用域也不一样,取决于放哪里)
注意:必须放到作用域最开头的位置
<script>
// 1.变量必须声明
console.log(n); // n is not defined
// 2.普通函数的this,不再指向Window,而是undefined
function fn(){
console.log(this); // undefined
}
fn();
// 3.函数形参不可以重名
function fn(a,a){
console.log(a + a); // Duplicate parameter name not allowed in this context
}
fn(1,2);
------------------------------------------------------------------------------------------------------------
**上述例子,如果关闭严格模式,结果为:
function fn(a,a){
// a = 1;(被覆盖)
// a = 2;
console.log(a + a); // 4
}
fn(1,2); // 好比与 重新赋值
</script>
箭头函数:
- 箭头函数中的 this 与普通函数
完全不同
,也不受调用方式的影响
,事实上箭头函数中并不存在 this
- 箭头函数中访问的 this 不过是
箭头函数所在作用域的 this 变量
事件回调函数使用箭头函数时
,this 为全局的window
- 基于原型的面向对象也不推荐采用箭头函数
call方法:
call:函数.call(this的指向,参数1,参数2,参数n…);
<script>
// 定义一个函数
function fn(a,b){
console.log(this,a,b); // {uname: '阿飞', age: 22} 1 2 此时的this已经指向了obj
console.log(a + b); // 3
}
fn();
// 定义一个对象
let obj = {
uname:'张飞',
age:22
};
// 函数.call(this的指向,参数1,参数2)
fn.call(obj,1,2);
</script>
call练习:
<script>
// 定义一个对象
let obj = {
uname:'李',
eat:function(){
console.log(this); // {uname:'四'}
}
}
// 再次定义一个对象
let o = {
uname:'四'
}
// 使用call修改obj中this的指向
obj.call(o); // this指向了o这个对象,上面的console.log(this) 打印的就是o对象里面的内容
</script>
apply方法:
apply:函数.apply(this的指向,[参数1,参数2,参数n…]) 传的值必须放在数组里面进行传递
<script>
// 定义一个函数
function fn(a,b){
console.log(this,a,b); // {uname: '哇哈哈', age: 22} 12 22
}
// 定义一个对象
let o = {
uname:'哇哈哈',
age:22
}
// 函数.apply(this的指向,[参数1,参数2]);
fn.apply(o,[11,22]); // 传的是数组,但是传到函数里面的时候就会自动拆分出来
</script>
apply:绝大多数用于数组
<script>
// 数组求最大值
------------------------------------------------------------------------------------------------------------
// 第一种方法:正常forEach
let arr = [22,255,33,147];
let max = arr[0];
arr.forEach(function(item){
if(item > max) max = item;
})
console.log(max); // 255
------------------------------------------------------------------------------------------------------------
// 第二种方法:Math.max.apply
let arr = [22,255,33,147];
// Math.max 不加括号,即为一个不调用的函数
let re = Math.max.apply(null,arr); // null表示不修改this指向,后面参数表示把数组传进去,arr本身就是一个数组
console.log(re); // 255
------------------------------------------------------------------------------------------------------------
// 第三种方法:使用剩余符号(...)
// ... : 剩余符 或者 展开运算符
// 作用:① 接收参数的话就是接收剩余值 ② 对于复杂类型的话就是展开运算符
let arr = [22,255,33,147];
console.log(...arr); // 展开数组,将数组展开 (22 255 33 147)
let re = Math.max(...arr); // 将展开的数组,通过Math.max这个方法直接获取最大值
console.log(re); // 255
</script>
apply练习:
<script>
let obj = {
uname:'aaa',
fei:function(){
console.log(this);
}
}
let o = {
uname:'abc'
}
// 使用apply方法修改obj的this指向
// 函数.apply(this的指向,[参数1,参数2]);
obj.fei.apply(o); // obj里面的fei方法,所以要加上obj.
</script>
bind方法:
bind:函数.bind(this的指向,参数1,参数2,参数n…)
<script>
// 定义一个函数
function fn(a,b,c){
console.log(this,a,b,c);
}
// 定义一个对象
let o = {
uname:'aaa'
}
// 通过bind方法更改this的指向
// 注意:这里fn调用bind,但是bind不会回头调用fn,只是悄悄把this更改了
// bind一般用于改变this,但是不希望自动执行
let re = fn.bind(o,1,2,3); // 或者 fn.bind(o)()
re(); // 用re变量接收一下,再调用一下re;
</script>
bind练习:
<body>
<input type='button' value='点击获取验证码'>
</body>
<script>
// 获取元素
let btn = document.querySelector('input');
// 绑定单击事件
btn.addEventListener('click',function(){
// 立即禁用元素
this.disabled = true;
// 我希望5秒后解除
setTimeout(function(){
// 5秒后解除
// 此时的this指向仍然是window,this指向错误,所以解除禁用不会执行
// 用bind更改this指向 在函数后面直接点(.)
// bind里面直接写this(this是写在setTimeout外面,this不指向window,指向的是当前事件源 btn)
// 我们希望不立即解除,而是等待5秒执行,所以不能用call
}.bind(this),5000)
})
</script>
总结:
语法:
call:函数.call(this的指向,参数1,参数2,参数n…)
apply:函数.apply(this的指向,[参数1,参数2,参数n…])
bind:函数.bind(this的指向,参数1,参数2,参数n…)
相同点:
不同点:
类和对象
类:泛泛概念
对象:具体概念
类的基本使用:
<script>
// 创建类的语法
class 类名 {} (首字母大写)
----------------------------------
// 创建实例对象
new 类名()
</script>
<script>
// 例子
class Person {}
let p1 = new Person();
// instanceof用于判断是否属于构造函数
console.log(p1 instanceof Object); // true
-------------------------------------------------
class Dog {}
let d1 = new Dog();
console.log(d1 instanceif Object); // true
</script>
类的操作(增加成员)
成员分为两类:静态成员
和 实例成员
实例成员:
属性 = 值
- 关键字
class
封装了所有的实例属性和方法- 类中封装的并不是变量和函数,因此
不能使用关键字 let const var
静态成员:
static 属性 = 值
static
关键字用于声明静态属性和方法
静态属性
和方法
直接通过类名
进行访问
添加实例成员:
<script>
// 添加实例成员
class Person {
// 实例属性
// 一定不加let
uname = '张三丰';
age = 22;
head = 1;
// 实例方法
// 不用加function
eat() {
console.log('eat');
}
say() {
console.log('say');
}
}
// 实例化
let p1 = new Person();
console.log(p1); // Person {uname: '张三丰', age: 22, head: 1}
// 方法放进了原型对象prototype里面
p1.eat(); // eat
</script>
添加静态成员:
<script>
// 添加静态成员(供类使用)
class Person {
// 静态属性,在属性名前加static
static language = '汉语';
static skin = '黄皮肤';
// 静态方法
static sleep(){
console.log('sleep')
}
static walk(){
console.log('walk')
}
}
// 实例化对象
let p1 = new Person();
// 实例成员上没有成员 直接加到了原型对象prototype的constructor 的class里面了
console.log(p1); // Person {}
// Person仍可使用
console.log(Person.language); // 汉语
</script>
添加静态成员练习:
<script>
// 静态成员
class Person {
// 添加静态成员
static language = '汉语';
// 静态方法
static eat() {
console.log('eat')
}
static run() {
console.log('run')
}
}
// 实例化
let p1 = new Person();
// 方法存在了constructor class里面
console.log(p1);
>> 控制台输出:
`Person {}
[[Prototype]]: Object
constructor: class Person
[[Prototype]]: Object
`
Person.eat(); // eat
Person.run(); // run
</script>
构造函数
创建类时在类的内部有一个特定的方法 constructor
,该方法会在类被实例化时自动被调用
,常被用于处理一些初始化的操作
。
构造函数
- 在es6中,
constructor
被称为构造函数,构造方法,构造器
- 类里面有一个固定的方法名
constructor(构造器)
用来接收参数作初始化操作
- 构造函数在
new
的时候自动执行
,不需要调用
- 构造函数如果不写的话,那么内部会
默认的添加上这个方法
- 类里面
一定有构造器
,就看用不用- 构造器的名字
不能修改
,只能叫constructor
- 构造器
只能有一个
<script>
class Person {
// 此处仍然可以写
aaa = 'aaa';
// 类里面有一个固定方法名,constructor,用来接收参数作初始化操作
constructor(uname,age,gender){
// 构造方法
this.uname = uname;
this.age = age;
this.gender = gender;
// 这里同样也能添加共同属性
this.head = 1;
}
say(){
console.log('say')
}
eat(){
console.log('eat')
}
}
// 实例化操作
let p1 = new Person('张飞','22','男');
console.log(p1); // Person {aaa: 'aaa', uname: '张灰', age: 22, gender: '男', head: 1}
let p2 = new Person('关羽','23','男');
console.log(p2); // Person {aaa: 'aaa', uname: '关羽', age: 23, gender: '男', head: 1}
let p3 = new Person('貂蝉','21','女');
console.log(p3); // Person {aaa: 'aaa', uname: '貂蝉', age: 21, gender: '女', head: 1}
</script>
继承
extends:
super:
-
在继承的过程中
子类中 constructor 中必须调 super 函数
,否则会有语法错误 -
子类构造函数中的
super 函数
的作用是可以将子类实例化时获得的参数传入父类的构造函数之中
-
Super用于调用父类的方法
继承:
<script>
// 定义一个类
class Father {
constructor(uname,age){
this.uname = uname;
this.age = age;
}
qian(){
console.log('赚一个亿')
}
}
// 继承:
// extends:声明一个类为子类
class Son extends Father {
constructor(uname,age,score){
// 在继承时,如果子类有自己的构造器,那么必须使用super调用父类的方法(Father(的属性值))
// super:调用父类的方法
// 必须先使用super调用父类的构造器,然后再让自己添加成员
super(uname,age);
this.score = score;
}
}
// 实例化测试是否继承完成
let s1 = new Son('儿子',20,99)
console.log(s1); // Son {uname: '儿子', age: 20, score: 99}
// 调用
s1.qian(); // 赚一个亿
</script>
继承练习:
<script>
// 定义一个类
class People {
constructor(uname,age,sex){
this.uname = uname;
this.age = age;
this.sex = sex;
this.head = 1;
}
// 定义方法
say() {
console.log('say')
}
eat() {
console.log('eat')
}
}
// 再次定义一个继承People类
// 语法:class 新类名 extends 父类名
class Chinese extends People {
constructor(uname,age,sex,shuai) {
// 在设置之前调用super,调用父People的形参,有什么写什么
super(uname,age,sex)
this.shuai = shuai;
}
}
// 实例化对象
let c1 = new Chinese('中国人',1,'男','帅');
console.log(c1); // Chinese {uname: '中国人', age: 1, sex: '男', head: 1, shuai: '帅'}
</script>
类 的 本 质 就 是 函 数 = > f u n c t i o n 类的本质就是函数 => function 类的本质就是函数=>function
let obj = new Person();
console.log(typeof obj); // function
拷贝
- 拷贝不是直接赋值
- 直接赋值是传地址,共用一份数据,一方更改值,另一方也会跟着更改
浅拷贝:只拷贝最外面层
<script>
// 浅拷贝:只拷贝最外层
let obj = {
uname:'张三丰',
age:22,
gender:'男'
}
// 空对象
let newObj = {};
// 循环遍历
// 添加值,原本有值就是修改值,原本没有就是添加值
// for(let key in 需要被拷贝的对象){
// 新对象[key] = 被拷贝的对象[key]
// }
// 将有内容的对象拷贝到空对象里面
for(let k in obj){
newObj[k] = obj[k]
}
obj.uname = '哇哈哈';
// 两个对象之间互不影响
console.log(obj,newObj)
// {uname: '哇哈哈', age: 22, gender: '男'} {uname: '张三丰', age: 22, gender: '男'}
</script>
浅拷贝方法:Object.assign(newObj(空对象, 有内容的对象))
O b j e c t . a s s i g n ( n e w O b j ( 空 对 象 , 有 内 容 的 对 象 ) ) Object.assign(newObj(空对象,有内容的对象)) Object.assign(newObj(空对象,有内容的对象))
深拷贝:全部层拷贝
<script>
// 深拷贝:所有层都拷贝
let obj = {
uname:'张三丰',
age:22,
gender:'男',
color:['red','blue','yellow'],
message:{
index:6,
score:99
}
}
// 空对象
let newObj = {};
// 用来拷贝的函数
function kaobei(newObj,obj){
for(let key in obj){
//obj[key]可能是数组
//obj[key]可能是对象
//不能再直接赋值
//如果是数组,它的构造函数一定是Array
//如果为true,就能说明obj[k]是数组
if(obj[key] instanceof Array) { // 可能是数组
// obj[k] = ['red','blue','yellow'];
// 保证新对象里面也是一个数组
newObj[key] = [];
// 拷贝(递归,调用自己)
// 拷贝(参数)
kaobei(newObj[key],obj[key]);
// 如果是对象,它的构造函数一定是Object
// 如果为true,只能说明obj[key]是对象
} else if(obj[key] instanceof Object){ // 可能是对象
// newObj[key] = {index:6,score:99}
// 保证新对象里面也是一个对象
newObj[key] = {};
// 拷贝(递归,调用自己)
// 拷贝(参数)
kaobei(newObj[key],obj[key]);
}else{
newObj[key] = obj[key];
}
}
}
// kaobei(前面是一个空对象,后面是一个有内容的对象)
kaobei(newObj,obj);
newObj.message.index = 666;
console.log(obj,newObj);
>> 控制台输出: `index值被修改了,而且不影响另一个对象`
`
obj:
{uname: '张三丰', age: 22, gender: '男', color: Array(3), message: {…}}
age: 22
color: (3) ['red', 'blue', 'yellow']
gender: "男"
message: {index: 6, score: 99}
uname: "张三丰"
[[Prototype]]: Object
------------------------------------------------------------------------------
newObj:
{uname: '张三丰', age: 22, gender: '男', color: Array(3), message: {…}}
age: 22
color: (3) ['red', 'blue', 'yellow']
gender: "男"
message: {index: 666, score: 99}
uname: "张三丰"
[[Prototype]]: Object
`
</script>