赞
踩
基本数据类型
怎么定义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);
引用数据类型
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)
}
}
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
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未定义
块级作用域
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死区)。
}
内存空间的背景
每个网页在运行时都会占据电脑的一部分资源(电脑内存)。根据电脑的系统位数(32或64)不同,每个页面 的最大占用内存不同(32位系统下一个页面最大可以占用0.7g(约700m),64位最大占用为32位二倍(1.4g))。但是浏览器本身自带内存优化,所以很少有占满的情况出现,如果有一般浏览器会进行页面崩溃来保证内存不会过多占用(转圈圈)
每个页面的内存空间组成
页面的内存大多数由渲染引擎占用(包括页面的解析,和js代码的执行),其中渲染引擎里占比最大的是就是JavaScript引擎。一般讨论或学习的JavaScript内存空间就是指的JavaScript引擎的内存占用。像谷歌就是Chrome v8 JavaScript引擎
内存占用组成(逻辑上):(以v8 为例:一个页面)
栈区:只要保存基本数据类型以及引用数据类型地址【保存变量】
堆区:保存引用数据类型数据 【对象 数组 …】
常量池:保存常量,也会放置一些频繁使用的变量。【一般会归纳到栈区】
1、执行let a = 1 ;会在栈区开辟一块空间,将1放在空间中。同时这一块空间的名字为a
2、执行let b =2; 同上
3、执行let c =a; 首先在栈区开辟一块空间取名c 同时将a的值1拷贝一份到c的内存空间中。
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);
概念:本质是一种函数的调用方式,自己调用自己。【自调用函数】
特点:一旦程序解析到该函数,该函数会立即被执行。
语法:
方式一:
(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【剩余】参数,放在方法的形参最后。
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存储的数据不会重复【如果是基本数据类型,直接比较。如果是引用数据类型,比较的是地址】
总结:什么情况下是创建新的对象或者数组
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); })
作用:存储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继续调用父类的构造方法
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个对象的地址一致 他们指向同一块空间
原型链[决定方法调用时候到底调用哪一个]:
每个对象都拥有自己的原型对象,而原型又是一个对象,也拥有自己的原型对象,依次类推,这多个原型对象之间形成一条链式结构,这条链称为原型链 所有的类的默认原型是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关键字的作用
1、在堆中分配对象的内存空间
2、设置当前对象的__ proto __属性值为当前类的prototype属性的值
3、改变当前构造函数中this指向为当前对象(默认指向window)
4、调用构造方法(属性的赋值)
基本概念
this是一个关键字,他的作用存储一个引用地址。通常写在方法中,在方法执行时会创建this,this的值会随着使用场景的变化而变化。
this指向的四种规则
默认绑定
指向:this指向window
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 作用域链的问题
//访问变量 作用域链生效 访问方法 原型链生效
}
}
由于这个特点、以下场景不适用箭头函数:
由于这个特点、以下场景适用箭头函数:
//适用于箭头函数
let obj = {
name:"tom",
test(){
setTimeout(()=>{
console.log(this.name); //自己没有this 会顺着作用域链往上test找到this
},1000);
}
}
obj.test();
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。