JS-高级

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();

第四节、变量的提升

概念:变量的提升是在程序进行编译的时候**,会将所有的变量的声明和函数的声明提升到作用域的最前面**。这个过程称为变量的提升。

注意:实际变量的提升,代码的位置是没有发生变化的。只是在编译阶段就将变量和函数放在内存中

image-20221116115118833

变量的提升:

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

1、执行let a = 1 ;会在栈区开辟一块空间,将1放在空间中。同时这一块空间的名字为a

2、执行let b =2; 同上

3、执行let c =a; 首先在栈区开辟一块空间取名c 同时将a的值1拷贝一份到c的内存空间中。

image-20221116153639956

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

第六节、立即执行函数

概念:本质是一种函数的调用方式,自己调用自己。【自调用函数】

特点:一旦程序解析到该函数,该函数会立即被执行。

语法:

方式一:

(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 该属性的值是一个地址,这个地址所有的对应的区域存储的是一个对象,这个对象就称为这个类的原型对象。

image-20221121144307618

为什么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

原型链[决定方法调用时候到底调用哪一个]:

每个对象都拥有自己的原型对象,而原型又是一个对象,也拥有自己的原型对象,依次类推,这多个原型对象之间形成一条链式结构,这条链称为原型链 所有的类的默认原型是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();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值