JavaScript高级
第一节、关于数据类型的补充
基本数据类型
- number 所有的数字
- boolean 只有2个值
- string 双引号 单引号 模板字符串
- undefined 值:undefined
- null 值:null
- bigint 大整数类型:用于存储比较大的整数
- Symbol 随机的不重复的整数
怎么定义bigint类型的变量:
let 变量名 = 值n
let 变量名 = BigInt(“值”)
//基本数据类型
//bigint
let a = 123;//a就是number类型
console.log(a);
let b = 3214324532543654645;
console.log(b);//3214324532543654400 原因:number类型的数据 是有1个范围的 如果存储的数据非常大 数据就会发生丢失
//怎么解决上述问题:bigInt类型
// let c = 3214324532543654645n;//一旦添加n 则 c 就是bigInt类型
let c = BigInt("3214324532543654645");
console.log(c,typeof c);
//在前端存储比较大的数据时候
//1.bigint
//2.字符串
let d = "3214324532543654645";
console.log(d);
引用数据类型
- object
- Array[数组]
- Function [函数]
- object [对象]
第二节、查看某个变量的数据类型的方式
typeof:只能处理基本数据类型,不能处理引用数据类型【查看引用数据类型都是object】
变量.constructor.name
Object.prototype.toString.call(变量):可以处理任意的数据类型
//**typeof**:只能处理基本数据类型,不能处理引用数据类型【查看引用数据类型都是object】
//语法 : typeof 变量
let a = 10;//number 基本数据类型
console.log(typeof a);
let str = "abc";//string 基本数据类型
console.log(typeof str);
let fn = function(){}; //fn 是1个函数 Function
console.log(typeof fn);
let arr = []; //arr 数组 Array
console.log(typeof arr);//object
// 变量.constructor.name
// **Object.prototype.toString.call(变量)**:可以处理任意的数据类
console.log(Object.prototype.toString.call(arr));//Array
console.log(Object.prototype.toString.call(a));//Number
console.log(Object.prototype.toString.call(fn));//Function
练习:
1.分别定义4个变量 :分别赋值 1 [] 函数 {}
分别通过三种方式查看他们的数据类型
第三节、作用域及其作用域链
作用域
概念:JS中变量或者函数可以被访问范围【一个变量能不能被访问到,是由他的作用域决定的】
作用:控制变量和函数的可见性以及生命周期
作用域的分类
-
全局作用域:整个程序中都能被访问,声明周期跟浏览器打开及创建,关闭才销毁
-
web环境 window对象中
-
node环境 Global对象
-
什么情况下定义的变量或者函数是全局作用域
- 在最外层定义的变量
var a = 10; //a定义到最外层的,a是全局作用域 function c(){ console.log(a); var b = 20; return function b(){ console.log(a) } }
- 通过var关键字在除了方法的{}中定义的变量
if(true){ var a = 10; //定义在if的大括号中 var 全局作用域 } console.log(a); //10 function c(){ console.log(a);//10 var b = 10; //通过var关键字 定义在方法的{} } c(); console.log(a);//10 console.log(b);//b is not defined
- 在方法中直接使用的变量
function test(){ a = 10;//直接使用 没有经过定义 a全局作用域 } test(); console.log(a); //10
- 挂载在window对象上的
function test(){ var a = "window"; window.a = a; //将变量a 挂载在window } test(); console.log(a);//window
-
-
函数作用域:在函数内部形成1个独立的作用域**,作用范围只能在函数中**。函数执行完毕后对象所占用的空间会立即释放
- 就是在函数中定义的变量或者函数
function test(){ var a = "function"; //在函数中定义的 就是函数作用域 :特点:1、只能在函数中使用 2、。函数调用开始的创建,函数调用完毕后销毁 console.log(a); function test03(){ console.log(a); } } // console.log(a);// a is not defined test(); // console.log(a);// a is not defined a未定义
-
块级作用域
- ES6中引入2个关键字 let const ,如果通过这2个关键字定义的变量,在哪个大括号中定义则作用域就在哪个大括号中。
if(true){ let a = 10; //let 关键定义的变量 块级作用域 只能在定义的大括号中有效 if(true){ console.log(a); //a } } console.log(a); // a is not defind
补充:
const定义的是常量:一旦赋值后则不能再被修改,一般命名是大写,如果需要多个单词则通过下划线连接
// let PI = 3.14; const PI = 3.14; PI = 5.14; //报错 不能对常再次赋值 console.log(PI);
作用域链
当1个变量在当前作用域无法找到时,便会尝试在外层作用域寻找。如果还是找不到,则在外层的外层寻找(只会在外层找,不会在兄弟层级找)这种如果一条链的查找关系,这种查找关系称为作用域链
如果到了全局作用域都找不到,怎么办?
1、非严格模式下:隐式创建1个全局变量
2、在严格模式下:报错
补充:严格模式 在作用域开头 “use strict”
// var a ;
// function test(){
// a = 10; //非严格模式 , 首先在自己作用域找,如果没找到 则往外层找 外层中也没有找到 会隐式创建 全局作用域的变量a
// console.log(a);
// }
function test(){
"use strict"; //严格模式执行
a = 10;
console.log(a);//a is not defined 首先在自己作用域找,如果没找到 则往外层找 外层中也没有找到 此处是严格模式 报错
}
test();
第四节、变量的提升
概念:变量的提升是在程序进行编译的时候**,会将所有的变量的声明和函数的声明提升到作用域的最前面**。这个过程称为变量的提升。
注意:实际变量的提升,代码的位置是没有发生变化的。只是在编译阶段就将变量和函数放在内存中
变量的提升:
console.log(a);//undefined
var a = 10;
函数提升:
test();
//直接将函数进行提升
function test(){
console.log("hello"); //打印hello
}
======
test(); //test is not a function
var test = function(){ //test 是变量
console.log("hello");
}
======
function test(){
console.log(a); //f 输出函数 函数会将变量覆盖
var a = 10;
console.log(a);
function a(){
console.log("hello");
}
}
test();
为什么JS会有变量提升
1、提高效率:有一部分工作,在编译期间做了
2、提高容错
变量提升的问题
变量覆盖的问题
var name = "tom";
function test(){
console.log(name); //undefined 本质希望是使用全局作用域中变量 name 输出 tom 但是由于变量提升的机制 ,得到的结果时 undefined test方法中
//name 在编译期间 提升到最前面 所以使用的其实是方法作用域的name
if(true){
var name = "jerry";
}
}
test();
变量没有及时销毁
function test(){
for(var i = 0;i<5;i++){ //此处i 适用于控制循环的次数 循环结束则i释放空间 但是由于变量提升 需要等到整个方法调用结束才会释放。
}
console.log(i); //5
}
test();
变量提升阻止
ES6中引入let和const关键字,let和const不存在变量的提升
。
console.log(a);//Cannot access 'a' before initialization 不能访问a 在a被初始化之前
let a = 10;
拓展:定义变量的3个步骤:
1、定义
2、初始化
3、赋值
如果通过var关键字定义的变量。在变量提升的时候 做 1 2 这2件事件。
var a ; //1.定义1个变量 2.对变量赋初值undefined
log(a);
a = 10;//真正的赋值
如果通过let关键字,在变量提升阶段时候,做1步
TDZ死区:
如果通过let或者const定义的变量,在某个块中定义的。一旦定义变量后,在从这一块代码的第一句代码到定义这一个变量的这一段代码之间。对该变量是不能访问。这个区间称为暂时性死区。
function test(a,b){
console.log("hello");
console.log(a+b);
var c = 10; //在21行定义了变量 c 在进入方法 17-21都不能对c变量发起操作,这一块区域称为c变量的暂时性死区(TDZ死区)。
}
第五节、JS中内存空间分析
内存空间的背景
每个网页在运行时都会占据电脑的一部分资源(电脑内存)。根据电脑的系统位数(32或64)不同,每个页面 的最大占用内存不同(32位系统下一个页面最大可以占用0.7g(约700m),64位最大占用为32位二倍(1.4g))。但是浏览器本身自带内存优化,所以很少有占满的情况出现,如果有一般浏览器会进行页面崩溃来保证内存不会过多占用(转圈圈)
每个页面的内存空间组成
页面的内存大多数由渲染引擎占用(包括页面的解析,和js代码的执行),其中渲染引擎里占比最大的是就是JavaScript引擎。一般讨论或学习的JavaScript内存空间就是指的JavaScript引擎的内存占用。像谷歌就是Chrome v8 JavaScript引擎
内存占用组成(逻辑上):(以v8 为例:一个页面)
栈区:只要保存基本数据类型以及引用数据类型地址【保存变量】
堆区:保存引用数据类型数据 【对象 数组 …】
常量池:保存常量,也会放置一些频繁使用的变量。【一般会归纳到栈区】
![image-20221116152515408](https://woniumd.oss-cn-hangzhou.aliyuncs.com/java/liyoupeng/image-20221116152515408.png)
1、执行let a = 1 ;会在栈区开辟一块空间,将1放在空间中。同时这一块空间的名字为a
2、执行let b =2; 同上
3、执行let c =a; 首先在栈区开辟一块空间取名c 同时将a的值1拷贝一份到c的内存空间中。
![image-20221116153639956](https://woniumd.oss-cn-hangzhou.aliyuncs.com/java/liyoupeng/image-20221116153639956.png)
1、执行let obj = {}: 赋值从右往左,先执行{} — 创建对象,在堆区完成对象的创建。创建的对象有1个地址0x01,将0x01赋给给变量obj
2、执行obj.name = “tom” ;赋值从右往左,会在常量池中保存tom 然后在tom的地址保存0x01对应的区域的内存中
3、let o = obj; 就是1个变量的赋值,值的拷贝。将obj中存储的值拷贝了o ,只是obj存储的是地址。
4、o与obj 指向同一块空间
5、o.name = jerry 也是0x01这一块空间进行操作;
练习:
let students = [
{name:"tom",age:20},
{name:"jerry",age:21},
{name:"小美",age:27}
];
let arr = students.filter(element=>element.age>18);
let student = arr[0];
student.name ="小花";
console.log(arr[0].name);
![image-20221116162459232](https://woniumd.oss-cn-hangzhou.aliyuncs.com/java/liyoupeng/image-20221116162459232.png)
第六节、立即执行函数
概念:本质是一种函数的调用方式,自己调用自己。【自调用函数】
特点:一旦程序解析到该函数,该函数会立即被执行。
语法:
方式一:
(function(参数){})(实参)
方式二:
!function(形参){}(实参)
方式三:
~function(形参){}(实参)
// (function(a,b){
// console.log("hello");
// console.log(a,b);
// })(1,2);
// !function (){
// console.log("hello02");
// }();
~function (){
console.log("hello02");
}();
应用场景:
1、模拟块级作用域
2、模拟预加载的事件的效果
第七节、扩展运算符【…】
概念:ES6提出的一个新的操作符,主要的应用常见有2个:作为扩展运算符,REST参数
扩展运算符,主要可以对数组、对象、字符串可以将里面的数据进行展开
作用的数组:
1、展开数组的每一项,可以通过拓展运算符 快速的完成数组的展开、合并、复制并且配合数组的一些API进行操作
语法:…数组名
数组的展开:
let arr = [1,2,3];
//1.展开数组
console.log(...arr); // 1 2 3
数组的合并:
//2.完成数组的合并
let arr03 = [...arr,...arr02];
console.log(...arr03);
数组的拷贝[将值放在新数组中,本质是创建1个新数组] 浅拷贝
//3.完成数组的拷贝
//需求:将arr03中每一个元素拷贝到arr04
let arr04 = [...arr03];
console.log(...arr04);
console.log(arr03==arr04);//false arr03 arr04 本质存储的地址 【记住:如果直接引用数据类型 进行比较 则是比较地址并不是比较其中的值】
结合一些API [这些API可以接收多个参数,我们就将数组展开,每1个元素都是一个参数]
//4.配合一些API使用
//配合可以接收多个参数的API ,本质传递1个数组,然后将数组的每一项展开,展开后每一项就作为1个参数传递进去
//push 添加元素 添加数组
//arr04.push(7,8,9);
arr04.push(...[7,8,9]);
console.log(...arr04);
//splice() 指定位置添加元素 【arr04在下标为2的地方 添加“a”】
//arr04.splice(2,0,"a","b");
arr04.splice(2,0,...["a","b"],...arr);
console.log(arr04);
//查找数组中最大或者最小的数值
let arr05 = [1,3,2,5,9];
let max = Math.max(...arr05);
let min = Math.min(...arr05);
//假如现在有2个数组 【1,2,3】 【2,3,4】
//需求提取 数组中所有的奇数 形成新数组
let a1 = [1,2,3];
let a2 = [2,3,4];
let newArr = [...a1.filter(element=>element%2!=0),...a2.filter(element=>element%2!=0)];
console.log(...newArr);
补充:Math.random() 生成0-1的随机数
//生成1个0-100的随机数
let test = Math.random()*100;
//生成50-100的随机
Math.random()*50+50
作用在对象上
//作用到对象
let student = {
name:"tom",
age:20,
gf:{name:"jerry",age:80}
}
let student03={
gender:"男",
name:"jerry"
}
//对象的拷贝--- 浅拷贝[只会拷贝最外层]
let student02 = {...student};
console.log(student02);
console.log(student==student02);//false
console.log(student.gf==student02.gf);//true
//对象的合并 如果出现同名的属性 后面的会将前面的覆盖
let student04 = {...student,...student03}
console.log(student04);
作用在字符串上
作用:将字符串转为字符数组
let str = "abcd"; //字符串在JS中底层 是以字符数组存储的 ['a','b'....]
// console.log(str);
// console.log(str.length);
// console.log(str[0]);
//console.log(...str);
let strArr = [...str];
console.log(strArr);
将伪数组转为真数组
为什么需要转:伪数组本质是一个对象,不具备数组的API,假如我们希望使用数组的API—伪数组转为真数组
let allP = document.querySelectorAll("p");
// allP.filter(); //不行 --- allP 是一个伪数组
let allPArr = [...allP];//转为真数组
//可以调用真数组的API
let newArr = allPArr.filter(function(element){
return element.innerText%2!=0; //将文本为奇数的标签放在新数组中
})
console.log(newArr);
第八节、REST参数 【…】
REST【剩余】参数,放在方法的形参最后。
function方法名(形参1,形参2,…形参3){}
方法名(实参1,实参2,实参3,实参4)
实参3,实参4就是剩余的参数,通过…形参3来接收,最终这些实参以数组的形式保存。
function test(a,...b){
console.log(a,b);//1 [2,3]
}
test(1,2,3);
//完成对给定数据的累加
function add(...num){
let total = 0;
for(let i = 0;i<num.length;i++){
total += num[i];
}
return total
}
console.log(add(1,2,3,4,10));
//格式化日期 传递 是年月 年-月 年月日 年-月-日
function formateDate(...date){
return date.join("-");
}
console.log(formateDate(2022,11));
第九节、解构赋值
主要针对数组和对象,快速获取数组或者对象中数据。
针对对象
语法:
let {变量1,变量2,变量3…} = 对象
会将对象中属性名跟某个变量名一致,会将属性对应的值赋值给变量。如果某个变量在对象中没有名跟其对象,这时该变量的值就是undefined
好处:简化对象取值的方式
$.ajax({
url:"https://www.xxx.site/mock/Movie/getAllMovies",
dataType:"json",
success:function({movies}){
//渲染电影的代码
console.log(movies);
}
})
针对数组
作用:快速的提取数组中某项元素
语法
let [变量1,变量2,变量3] = 数组
将数组中下标为0的元素赋值给变量1,下标为1的元素赋值给变量2…
注意:中间可以通过,跳过数组的元素。
let arr = [1,2,3,4,5];
// let [a1,,a2,,a3] = arr;
//console.log(a2,a3); //从数组的第1个元素开始赋值,中间可以通过,进行跳过
let [a1,a3,...a2] = arr;
console.log(a1,a2,a3);//结合扩展运算符
针对字符串
let str = "hello";
let {length} = str;
console.log(length);
let [a,b,...c] = str;
console.log(a,b,c);
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo :1
bar:2
baz:3
let [ , , third] = ["foo", "bar", "baz"];
third baz
let [x, , y] = [1, 2, 3];
x 1
y 3
let [head, ...tail] = [1, 2, 3, 4];
head 1
tail [2 3 4]
let [x, y, ...z] = ['a'];
x 'a'
y undefined
z []
let [foo = true] = [];
foo true
let [x, y = 'b'] = ['a']; 'a' 'b'
let [x, y = 'b'] = ['a', undefined]; 'a'
第十节、闭包
概念:内层函数作为外层函数的返回值返回了,这种现象形成了闭包
作用:模拟块级作用域。
实现:将操作变量的函数及其变量包在另一个函数中
案例:点赞器
function outer(){
let i = 0;//记录点赞的总数 被包在outer 作用域 outer里面 其他地方不能操作 --- 保证数据的安全
//
return function dianzan(){
i++;
document.querySelector("span").innerText = i;
}
}
闭包的弊端:闭包中的变量会一直存在于内存中,占用内存空间。
第十一节、防抖
在页面上有些事件,会被频繁的触发,由于每触发一次事件都会调用一次事件的监听函数。如果频繁触发事件,频繁调用事件的函数。这样会影响页面的效率,针对这种情况需要做防抖的优化。
常见的优化场景:
1、input事件:input框中内容发生改变
2、鼠标移动事件
3、窗口大小改变事件:resize事件
4、滚动条滚动事件
防抖:每次触发事件后重新计时,直到之间再也没有触发该事件后,并且计时已经到了则会触发执行核心业务函数
1、以前我们直接将事件方法绑定给元素,一旦元素触发事件则会立即执行方法,处理核心业务【案例:获取输入内容开头的学生姓名 — 渲染】
2、防抖策略,一旦触发事件后不会立即调用处理核心业务的方法,而是调用另一个方法设置1个延时任务。延时任务中方法是处理核心业务的【过指定时间后才去处理核心业务】
function fangdou(){
let id = null;
return function test(){
if(id==null){ //如果为null
id = setTimeout(lianxiang,500);
}else{
clearTimeout(id);//清除掉以前 重新设置1个 相当于达到重新计时的效果
id = setTimeout(lianxiang,500);
}
}
}
// //核心业务
function lianxiang(){
document.querySelector("#u1").innerHTML = "";
//console.log("hello");
let inputVal = document.querySelector("#keyword").value;
// console.log(inputVal);
arr.forEach(function(element){
if(element.startsWith(inputVal)&&inputVal!=""){ //如果数组中名字是以输入的名字开头的 进入if
let str = `<li>${element}</li>`;
document.querySelector("#u1").innerHTML += str;
}
})
}
第十二节、节流
核心思想:只要在不断触发事件,每隔N秒会执行一次。
实现的思路:设置一个延时任务,在事件触发的时候判断有没有对应的延时任务,如果没有则设置,如果有则不进行任何的操作。
最开始设置1个ID为null ,第一次触发的事件的时候设置一个延时任务。【ID不会为空】,下一个瞬间判断ID是否为null,如果不为null,不做任何操作。等待指定时间后会执行对应延时任务。一旦执行后立即将ID设置为null。
function myJL(){
let id = null;
let count = 0;
return function(){
if(id==null){
id = setTimeout(function(){
console.log(++count);
id = null;
},500)
}
}
}
//记录鼠标移动次数 改为节流的优化
第十三节、Set集合
作用:存储数据
特点:类似于数组,用于存储多个数据的。Set存储的数据不会重复【如果是基本数据类型,直接比较。如果是引用数据类型,比较的是地址】
总结:什么情况下是创建新的对象或者数组
1、只要看到{} 或者 [] 就是创建新对象或者数组
2、只要看到new关键字 就是创建新对象 new Set() new Array()
基本使用:
创建set集合:
let 变量名 = new Set(); //创建一个空set集合
let 变量名 = new Set([元素]);//创建一个带有数据的set集合
//创建set集合
let mySet = new Set();//创建一个空的set
let mySet02 = new Set([1,2,3,1]);//创建一个带数据的set
console.log(mySet,mySet02); //{1, 2, 3}
//需要注意的是 去除重复的规则
//在JS中只要看到{} 就是在创建对象
//数组中创建了2个对象 因为你有2个大括号 创建了2个对象 这2个对象地址不一样
let mySet03 = new Set([{name:"tom"},{name:"tom"}]);
console.log(mySet03);//{}{}
应用场景:
1、页面需要保存不重复的数据–set 自动帮你去重
如:假如有1个数组[1,2,3,1] ,希望得到一个没有重复数据的数组
console.log('========================');
// 如:假如有1个数组[1,2,3,1] ,希望得到一个没有重复数据的数组
let myArr = [1,2,3,1];
let mySet = new Set(myArr); // 1 2 3---set结构
let newArr = [...mySet];
console.log(newArr);
2、Set的API
add
作用:向set集合中添加元素
set集合.add(“元素”).add().add()
注意:add方法支持链式调用
delete
作用:删除指定的元素
set集合.delete(元素)
注意:返回值 true 表示删除成功 false 删除失败
clear
作用:清空set集合
has
作用:判断指定元素是否在set集合中
set集合.has(元素)
返回值:true 在 false 不在
let set = new Set([1,2,3]);
//添加元素
set.add("a").add("b").add(1); //不会添加重复的元素
console.log(...set);
set.delete("a");
console.log(...set);
console.log(set.has("A"));
//遍历set
set.forEach((element)=>{
console.log(element);
})
第十四节、Map集合
作用:存储K-V键值对的数据,类似于对象
优点:K可以是任意的数据类型,需要注意的是K也是不能重复
创建Map集合
let 变量名 = new Map(); 创建空的Map集合
let 变量名 = new Map(二维数组) 创建带数据的Map集合
Map相关的Api
set(K,V)
作用:向指定的map集合中存储K-V键值对
get(K)
作用:根据K取出对应的值
delete(K)
作用:根据K删除指定的V
has(K)
作用:判断是否有指定的K
forEeach(function(value,key,map){}) 遍历
//创建Map对象
let map = new Map();
//向map中添加元素
map.set("name","tom");
map.set(3,"hello");
let obj = {};
map.set(obj,obj);
map.set(obj,{name:"jerry"}); //如果key 重复 则后面添加的会将前面添加的覆盖
console.log(map.get(obj)); //根据K获取指定的值
//console.log( map.containsKey(3));
console.log(map.has(4)); //判断是否有指定的K
map.forEach((element,key)=>{
console.log(element,key);
})
假如:有1个字符串 str = “aaaabbbccabc” ===>输出的结果 a5b4c3
let str = "aaadsadsa"; //创建字符串
let strArr = [...str];//转为数组
let map = new Map();//创建map
strArr.forEach((element)=>{
if(map.has(element)){ //如果存在 意味着 之前设置了对应k的k-v键值对
map.set(element,map.get(element)+1); //如果不是首次出现则将之前的值取出来加1 再放回去 由于key不允许重复 放回去的时候会将之前的值给覆盖
}else{ //当前循环到字符是首次出现
map.set(element,1);
}
})
console.log(map);
第十五节、编程思想
面向对象的三大特征:
1、封装:封装类过程
2、继承
面向过程【以过程为中心】
将一个业务所需要步骤,每一步都封装一个方法(函数),通过这些函数之间的相互的调用,完成某个业务功能。
家里装修:
1、找人设计
2、水电布局
3、贴砖
4、刷漆…
优点:接近于人类思考的方式
缺点:需要对其中每一个细节都很清晰。对应到编程我们就应该都每一个细节代码都非常清楚。不适用于大型的软件。
代表语言:C
面向对象
将需要完成某个业务及其对应的方法封装一起,形成一个对象,对象仅仅对外暴露功能就可以了。至于细节忽略
家里装修:
找装修公司
将很多的一些功能进行组合,这些对象上小的功能组合成大的完整的功能。
## 类与对象
类:是对象模板,要产生对象必须现有类 对象才具备功能和数据 类只是一个模糊概念
类是对一类事物的统称,在编程中通过代码描述一类事物—描述一类的共性
对象:数据与方法的集合,其中提供了很多功能,但是通过类产生的。
比如:学生
姓名 年龄 性别 吃饭 做作业…
静态的一些特征:姓名 年龄 性别
属性:通过变量进行描述
动态的一些特征【动作】:吃饭 做作业
行为:通过方法
定义类的语法:
class 类名{
//描述这一类事物的共性
constructor(){
//放置属性
}
//有什么行为就定义什么方法
}
//定义学生类
//属性:姓名 年龄 性别
//行为 : 做作业
class Student{ //类名首字母大写
//所有的属性放置constructor中 这个函数 称为构造函数
constructor(){
this.name;
this.age;
this.gender;
}
//描述行为
doHomWord(){
console.log("正在做作业");
}
}
通过类产生对象
let 对象名 = new 类名();
new 关键 就是创建某个类的对象的,只要new了就会产生一个新对象
let stu = new Student();//stu就是一个具体的对象 stu就具备名字 年龄 性别 能做作业
stu.doHomWord();
stu.name = "tom";
console.log(stu.name);
let stu2 = new Student(); //产生新对象
console.log(stu2.name);
关于构造函数
如果通过new关键字创建对象的时候,本质是调用类的constroctor。可以在创建对象的时候跟一些参数进去
//所有的属性放置constructor中 这个函数 称为构造函数
constructor(name,age,gender){
//在构造方法中this 表示是当前正在创建的那个对象的地址
this.name = name;
this.age = age;
this.gender = gender;
}
继承
什么叫做继承:
现实生活中继承:继承财产,将长辈的财产归为后辈
代码中继承:最大的好处实现代码的复用
中华田园犬 阿拉斯加 秋田 蓝猫 白猫 黑猫…
狗:名字 年龄 吃饭 看门
猫:名字 年龄 命的条数 吃饭
实现继承的语法:
class A extends B{
//编写A独有的一些特征
}
A称为子类 B称为父类
class Animal{
constructor(name,age){
this.name = name;
this.age = age;
}
eat(){
console.log("吃东西");
}
}
class Dog extends Animal{
constructor(name,age){
super(name,age); //调用父类的constructor 必须放在构造方法的第一句 如果没有写 系统会自动加1个无参数的
console.log("dog constructor");
}
seeDoor(){
console.log("看门");
}
}
let tugou = new Dog("小黄",20);//调用Dog constructor name 和age 来自于 父类 super继续调用父类的构造方法
第十六节、在ES5中定义类
ES6之前定义类其实就是定义函数,一旦通过函数创建了对象,这种函数就称为构造函数。【这种函数跟类的含义就是一致】
function Anima(name,age){
this.name = name;
this.age = age;
eat = function(){
console.log("eat.....");
}
}
//一旦通过该函数创建了对象 此时函数就被称为构造函数 --- 类
let animal = new Anima("tom",20);
console.log(animal);
注意:ES6中的class关键子其实是一个语法糖,底层实现其实跟ES5是完全一致的。
第十七节、原型
概念:ES6之前定义类都是通过方法来的【 包括ES6中class关键底层本质也是通过方法实现】。方法本身也是Function这一个类的对象,方法对象中有1个属性prototype 该属性的值是一个地址,这个地址所有的对应的区域存储的是一个对象,这个对象就称为这个类的原型对象。
为什么JS中会引入原型对象:
原型对象上放的是这个类的方法代码,所有的通过这个类产生的对象可以公用这个原型对象,达到了公用同一份方法代码的效果。避免了每一个对象需要单独放置方法代码而造成内存空间浪费。
怎么查看原型对象:
每一个类都有一个属性prototype — 查看该类的原型对象 【显示原型】
每一个通过类产生的对象都有一个属性 __ proto __ – 查看该类的原型对象**【隐式原型】**
class Animal{
constructor(name,age){
this.name = name;
this.age = age;
}
eat(){
console.log("eat.....");
}
sleep(){}
}
let animal1 = new Animal("tom",20);
let animal2 = new Animal("jerry",20);
console.log(Animal.prototype); //显示原型
console.log(animal1.__proto__);//隐式原型
console.log(Animal.prototype===animal1.__proto__); //true 这2个对象的地址一致 他们指向同一块空间
console.log(Animal.prototype===animal2.__proto__); //true 这2个对象的地址一致 他们指向同一块空间
![image-20221121152738186](https://woniumd.oss-cn-hangzhou.aliyuncs.com/java/liyoupeng/image-20221121152738186.png)
原型链[决定方法调用时候到底调用哪一个]:
每个对象都拥有自己的原型对象,而原型又是一个对象,也拥有自己的原型对象,依次类推,这多个原型对象之间形成一条链式结构,这条链称为原型链 所有的类的默认原型是Object,如果发生了继承则原型就被更改了。
注意:Object是原型链的最顶层,它没有原型了。
class Animal{
constructor(name,age){
this.name = name;
this.age = age;
}
eat(){
console.log("eat.....");
}
}
class Dog extends Animal{
kanmen(){
console.log("kanmen......");
}
}
let dog = new Dog();
dog.eat();//通过对象调用方法的时候,首先在自己的原型上找对应的方法,
//如果没有找打则继续往原型的原型上找,一层一层往上推,直到都没找到则报错,如果在某个上找到了则会调用
第十八节、new 关键字
new关键字的作用
1、在堆中分配对象的内存空间
2、设置当前对象的__ proto __属性值为当前类的prototype属性的值
3、改变当前构造函数中this指向为当前对象(默认指向window)
4、调用构造方法(属性的赋值)
第十九节、this关键字
基本概念
this是一个关键字,他的作用存储一个引用地址。通常写在方法中,在方法执行时会创建this,this的值会随着使用场景的变化而变化。
this指向的四种规则
默认绑定
指向:this指向window
- 在全局作用域中直接使用this
console.log(this); //全局作用中直接使用 指向window
- 在独立的调用函数
function test(){
console.log(this);
}
test();//没有借助任何对象,这种调用方式称为独立调用
- 立即执行函数中
//在立即执行函数中使用window
(function(){
console.log(this);
})();
- 在闭包函数中
//在闭包
function test(){
return function(){
console.log(this);
}
}
test()();
隐式绑定
对象.属性名 调用方法
规则:谁调用就指向谁
let obj = {
name:"tom",
test(){
console.log(this.name,this);
}
}
let obj2 = {
name:"jerry",
test(){
console.log(this.name,this);
}
}
obj.test(); //tom
obj2.test();//jerry
方法作为一个事件的监听函数
规则:谁触发事件指向谁【事件源】
let btn = document.querySelector("#btn");
btn.onclick = test03; //DOM0
btn.addEventListener("click",test03); //DOM2
function test03(){
console.log(this); //事件源
}
new绑定
new创建对象的时候[构造方法中]
规则:创建的是哪个对象就指向哪个对象
class Animal{
constructor(name){
this.name = name;
}
eat(){
console.log(this.name+"eat.....");
}
}
let animal = new Animal("tom");//new绑定 创建谁指向谁 这里创建animal
animal.eat(); //隐式绑定 谁调用指向谁 这里animal调用
显示绑定
实现:通过方法 call apply bind
语法:
对象1.方法名.call(对象2,实参1,实参2) //对象1 拥有方法的对象 对象2是需要将方法中this改变到指向 实参调用方法需要的参数
对象1.方法名.apply(对象2,[实参1,实参2]) //对象1 拥有方法的对象 对象2是需要将方法中this改变到指向 实参调用方法需要的参数。需要注意 参数要以数组组织
返回值= 对象1.方法名.bind(对象2) //将方法中this的绑定拥有的返回,通过返回值这个函数对象实现调用。
作用:改变this的指向
<script>
let obj = {
name:"tom",
fn:function(age,num){
console.log(this.name+"====="+age+"--->"+num);
}
}
//显示绑定 通过方法 call apply bind
let obj2 = {name:"jerry"}
// obj.fn(20); tom 20
obj.fn.call(obj2,30,20); //call(this指向的对象,调用方法时候参数列表)
obj.fn(1,1);
obj.fn.apply(obj2,[31,21]); //call(this指向的对象,[参数需要用数组组织])
//bind 返回一个函数对象 这个函数对象中对this的指向的改变永久有效
let result = obj.fn.bind(obj2); //result中 fn永久绑定给obj2
result(19,20);
result(11,12);
obj.fn(1,1);
obj.fn.call(obj2) 我们发现fn中this由原来的obj对象变成了obj2对象,this指向发生改变。其实我们可以理解为将fn函数借给了obj2。
伪数组借用真数组的方法:
let obj = { //典型的伪数组
0:"aa",
1:"bb",
length:2
}
Array.prototype.push.call(obj,"cc");
借用Math的方法
//console.log(Math.max(...arr));
let max = Math.max.apply(Math,arr); //巧用了apply方法
借用Object toString方法查看数据类型
//查看数据类型
console.log(Object.prototype.toString.call(arr));
箭头函数中的this
箭头函数是没有自己的this,但是并不意味着不能访问this。
let obj = {
name:"tom",
// fn:function(){
//进入到函数中 就会创建this 隐式绑定 绑定给调用的对象
// console.log(this.name+"fn......");
// }
fn:()=>{
//进入函数时 不会创建this
console.log(this);//window 结论:没有自己的this 但是可以访问this 为什么打印window 作用域链的问题
//访问变量 作用域链生效 访问方法 原型链生效
}
}
由于这个特点、以下场景不适用箭头函数:
- DOM事件处理函数
- 不适用构造方法
由于这个特点、以下场景适用箭头函数:
//适用于箭头函数
let obj = {
name:"tom",
test(){
setTimeout(()=>{
console.log(this.name); //自己没有this 会顺着作用域链往上test找到this
},1000);
}
}
obj.test();