JavaScript学习总结

147 阅读12分钟

JavaScript学习总结第四周

周一

1、事件的取消绑定:

1、如果你使用elem.on事件名=()=>{},那么写成:elem.on事件名=null; 可以取消事件绑定了

2、如果你使用elem.addEventListener("事件名",callback); 那么写成
	elem.removeEventListener("事件名",callback);  - 事件名和回调函数,【必须和添加时是一模一样的】

2、*****this的指向:非常多

1、单个元素绑定事件this->这个元素
2、多个元素绑定事件this->当前元素
3、箭头函数中this->外部对象
4、***函数中的this->当前正在调用函数的这个人!
5、定时器的this->window

3、*****ES5强制改变this的指向: - 笔试面试

call/apply:临时的替换了函数之中的this - 借用

语法:
   1、函数名.call(借用的对象,实参,...) - 单独传入每个实参
   2、函数名.apply(借用的对象,arr) - 只能传入一个事件要求必须是一个数组,apply其实会悄悄的将数组打散

	强调:call/apply:相当于立刻调用函数,立刻执行的

bind:永久替换了函数中的this - 买
	3件事:
	1、创建了一个和原函数功能完全相同的新函数
	2、将新函数的this永久绑定为了指定对象,别人都借不走
	3、将新函数的部分参数永久固定
	语法:var 新函数名=函数名.bind(永久对象,永久实参,...) - 不是立刻执行,需要自己调用
	强调:bind绑定的新函数没办法被call/apply再次借走!

以后哪怕不是自己的方法,也可以使用,个人推荐:借,白嫖!
	三个固定套路:
		1Math.max/min.apply(Math,arr)	===>	Math.max/min(...arr)
		2Object.prototype.toString.call/apply(arr)=="[object Array]"//toString不需要传入实
                   参,鄙视题:判断xx是不是一个数组,千万不要用typeof,他只能判断原始类型,不能判断引用类型
		3、类数组转为普通数组:
			1、老方式:接住=Array.prototype.slice.call/apply(类数组对象);
			2、新方法:接住=Array.from(类数组对象)

*****ES6:大版本 - 导致语法变化极大

1、学过了:*letconst关键字、*箭头函数

2、*模板字符串:可以直接识别变量,不再需要+运算去拼接了,而且实现了一个简单的js环境,甚至支持在里面做运算
   写API都可以
	`我的名字叫${name}`;

3、*****解构赋值:
	顾名思义:解析结构再进行赋值 - 赋值的新方式,并且得到了增强!
	如果赋值符号,左右两边的结构一样,就会悄悄得解开/脱掉结构再一一进行赋值
	语法:
		1、类似数组的解构赋值
			let [a,b,c]=[1,2,3];
			console.log(a);
			console.log(b);
			console.log(c);

		2、类似对象的解构赋值
			let {a,b=默认值,c}={c:3,a:1,b:2}
			console.log(a);
			console.log(b);
			console.log(c);
			//形参可以设置默认值,如果自己传入了,就用自己的

		3、调用函数时,传递实参的顺序其实无所谓了
			function zwjs({name="无名氏",age,hobby}){
				return `我的名字叫${name},今年${age}岁,喜欢${hobby}`;
			}
		
			console.log(
				zwjs({
					age:18,
					hobby:"学习"
				})
			)

		4、函数的返回的结果,可以有多个!
			function f1(){
				var a=1;
				var b=2;
				return [a,b];
			}
			var [a,b]=f1();
			console.log(a);
			console.log(b);

		//只要以后见到:方法名({里面放着很多很多的键值对,那么他的底层就是使用ES6的解构赋值,键
                  值对的写入的顺序其实是随意的})

4、新的循环:垃圾
	for(var val of 数组名){
		//val - 值
	}

	缺陷:
		1、没有提供过下标,意味着不能修改原数组
		2、只能遍历索引数组,不能遍历hash数组,意味着也不能遍历对象

5SetMap:两个新的数据类型:
	1、*Set:有一点点用:类似于数组的一种数据格式 - 【去重数组,然后再转回数组】
		[...new Set(arr)]
		... - 三个点扩展运算符,可以脱掉数组/对象的外套!
		不需要记忆他的任何的API,完全比不上数组的API

	2Map:垃圾:类似于对象的一种数据格式
		var m=new Map();
		添加:m.set("键","值");
		获取:m.get("键");
		删除:m.delete("键");
		清空:m.clear();
		遍历:m.forEach(callback);

周二

1、*****正则表达式:定义字符串中字符出现规则的表达式

何时使用:切割、替换、【验证】! 如何使用:语法:/正则表达式/;

 1、最简单的正则就是关键字原文 "no" -> /no/后缀后缀:
 g:找全部		i:忽略大小写

2、备选字符集:/^[备选字符集]$/;
		强调:1、一个中括号,只管一位字符
		      2、问题:正则表达式默认只要满足了就不管后续了,而我们做验证的人,希望的是用
                        户从头到尾完全按照我们的要求来,希望完全匹配
			解决:前加^后加$,两者同时使用,代表要求从头到尾【完全匹配】/^[备选字符集]$/;
                        - 验证必加
		特殊:如果备选字符集中ascii码是连续的,那么可用-省略掉中间部分
			比如:
				一位数字:[0-9]
				一位字母:[A-Za-z]
				一位数字、字母、下划线:[0-9A-Za-z_]
				一位汉字:[\u4e00-\u9fa5]
				一位数字、字母、汉字、下划线:[0-9A-Za-z\u4e00-\u9fa5_]

				除了xxx之外的:[^0-9] - 很少使用,范围太广了

3、预定义字符集:前辈们提前定义了一些字符集,为了方便程序员开发 - 简化了备选字符集
		一位数字:\d
		一位数字、字母、下划线:\w
		一位空白字符:\s	什么叫空白字符:空格、制表符、换行

		一位除了换行外的任意字符:.	- 很少使用,范围太广了

		建议:优先使用预定义,预定义满足不了再用备选字符集补充

	问题:不管是备选字符集,还是预定义字符集,一个只管一位

	4、量词:规定了一个字符集出现的次数:
		1、有明确数量:
			字符集{n,m}:前边相邻的字符集,至少n个,最多m个
			字符集{n,}:前边相邻的字符集,至少n个,多了不限
			字符集{n}:前边相邻的字符集,必须n个

		2、无明确数量:
			字符集?:前边相邻的字符集,可有可无,最多1个
			字符集*:前边相邻的字符集,可有可无,多了不限
			字符集+:前边相邻的字符集,至少一个,多了不限

	5、选择和分组:
		选择:在多个规则中选一个
			规则1|规则2
		分组:将多个字符集临时组成一组子规则
			(规则1|规则2)

	6、指定匹配位置
		开头:^
		结尾:$
		*特殊:两者同时使用,前加^后加$,表示从头到尾要求完全匹配 - 只要你做【验证】
	
	7、密码强度:2-4位,可以输入数字、字母,但是必须出现一位大写和一位数字的组合?
		/^[0-9A-Za-z]{2,4}$/
		预判公式:
			(?![0-9]+$) -> 不能全由数字组成,可能有大写、小写、汉字、日文、韩文、特殊符号....
			(?![a-z]+$) ->不能全由小写字母组成,可能有大写、数字、汉字、日文、韩文、特殊符
                         号....
			(?![0-9a-z]+$) -> 不能全由数字组成,也不能全由小写字母组成,也不能全由数字和
                        小写的组合组成,至少要有点别的

		比如:
			/^(?![0-9a-z]+$)(?![A-Za-z]+$)[0-9A-Za-z]{2,4}$/; - 2-4位,可以输入数字、字母,
                    但是必须出现一位大写和一位数字的组合?
			/^(?![0-9a-z]+$)(?![A-Za-z]+$)(?![A-Z0-9]+$)[0-9A-Za-z]{2,4}$/; - 2-4位,可以
                    输入数字、字母,但是必须出现三者的组合?
			/^(?![0-9A-Za-z]+$)[0-9A-Za-z_]{2,4}$/; - 至少要有一个下划线

2、*****字符串中支持正则API:

1、*切割:var arr=str.split("固定切割符"/RegExp)

2、*****替换:很有可能出现在笔试中,而且真实开发也会遇到
	1、基础替换法:
		str=str.replace(RegExp,"新内容");
		//replace支持正则,并且搭配上后缀g就可以找到全部
		//缺陷:替换的新内容是一个固定的

	2、高级替换法:
		str=str.replace(RegExp,function(a,b,c){

 				console.log(a);//正则匹配到的关键字
				console.log(b);//正则匹配到的关键字的下标
				console.log(c);//原字符串
                        return 判断a关键字的长度,而返回不同的星星数量
                        });

	3、格式化:
		var id="500103198602215933";
		var reg=/\d{6}(\d{4})(\d{2})(\d{2})\d{4}/;
		id=id.replace(reg,function(a,b,c,d,e,f){
			//在replace的时候,正则出现了分组,我们会得到更多的形参
			//在形参a的后面就会出现n个形参,具体看你有多少分组
			//第1个分组获得的内容会保存到第2个形参之中
			//第2个分组获得的内容会保存到第3个形参之中
			...
			//倒数第二个是下标
			//倒数第一个是原文
			return `${b}${c}${d}日`
		})
		console.log(id);

3、*正则对象:

创建: 
1、直接量:var reg=/正则表达式/后缀;
2、构造函数:var reg=new RegExp("正则表达式","后缀");

API:
	验证:var bool=reg.test(用户输入的);

周三

1、animate.css文件是一个动画库,放着很多很多的动画

为了我们程序员方便,怕我们自己画的丑 如何使用:

1、打开百度:搜索animate.css得到网址
 <https://animate.style/>
2、下载 - 你不需要再做了
3、引入此文件
4、挑选你喜欢的动画,把class放到你需要的元素上
5、并且要设置上animation-duration: 1s;执行时长
6、还需要根据不同的动画,设置不同的初始效果,才会好看

2、无缝轮播:

详见案例

3、swiper插件:专门的轮播插件 - 里面放着各种各样的轮播:提供了HTML/CSS/JS,只需要复制

1、打开百度,搜索swiper
2、可以首先点击开始使用swiper按钮可以做出一个做基本的轮播
3、如果你想要更多,就点击导航条上的在线演示
4、挑选你喜欢的,右键查看源代码
5、而且多个轮播之间可以混搭

4、封装一个运动(动画)函数:简化不用每次都写elem.style.这一句话

5、百度/高德地图:

1、打开百度:搜索:百度/高德地图开放平台,进入
2、注册/登录百度账号
3、拉到最下面 - 注册成为开发者,跳转到控制台
4、创建应用 - 应用名称你随便,应用类型浏览器端,白名单写一个\*,实名认证一下
5、恭喜你得到了密钥 - 成功了一大半了
6、鼠标放到导航条上开放文档 -> javascript API -> 示例DEMO -> 挑选你喜欢的地图,复制到你的项目之中
7、修改JS代码、在线查询经纬度
8、注意:再百度地图某次升级过后,出现了GL地图和普通版地图,两者不可以混搭使用,只要是GL都可以混搭使用

周四

1、*****Object:对象 - Array、String、RegExp、Date... 对象具有属性和方法,都是预定义好的,现在我们可以学习自定义对象 - js是基于原型的面向对象语言

面向对象 - 三大特点:封装、继承、多态

1、***开发方式:
面向过程:过程 - 开始 -> 结束,其实我们一直得开发方式都是面向过程:先干什么在干什么最后干什么
面向对象:对象(属性和方法),js里面有一句话万物皆对象,假设一个人是一个对象得话:
属性:身高、体重、姓名、性别、爱好、智商、情商...
方法:吃饭、睡觉、跑步、学习、打字、上课、讲课...

	何时使用面向对象:以后任何操作都要封装在一个对象之中 - 但是新手并不是太友好,难度较大
	为什么要面向对象:现实生活中所有得数据都必须包含在一个事物之中才有意义 - 更符合现实生活

2、*****封装/创建/定义:封装自定义对象:31、*直接量方式:
		var obj={
			"属性名":属性值,
			...,
			"方法名":function(){

			},
			...
		}

		强调:
			1、其实属性名和方法名得""可以不加 - 暂时建议你加上,以后我们要学习一个数据格式
                         JSON,他就必须在键上加上""
			2、访问对象的属性和方法
				obj.属性名	===	obj["属性名"]
				obj.方法名()	===	obj["方法名"]()
				建议使用.去访问对象的属性和方法,更简单
				***js中一切都是对象,除了undefined和null,一切对象的底层都是hash数组
			3、访问到不存在的属性,返回undefined
			4、可以随时随地添加新属性和新方法
				obj.属性名=新值;
				obj.方法名=function(){}
			5、如果我希望遍历出对象所有的东西,必须使用for in循环,必须写为obj[i]才能拿到,
                        不要使用.会出问题
			6、***如果你希望在对象的方法里,使用对象自己的属性,写为this.属性名!
					*****难点:this的指向
						1、单个元素绑定事件this->这个元素
						2、多个元素绑定事件this->当前元素
						3、定时器中的this->window
						4、箭头函数中的this->外部对象
						5、函数中的this->谁在调用此方法,this就是谁
						6、构造函数之中this->当前正在创建的对象

	2、预定义构造函数:
		var obj=new Object();//空对象
		//需要自己后续慢慢添加属性和方法
		obj.属性名=新值;
		obj.方法名=function(){}

	以上两个方法都有一个缺陷:一次只能创建一个对象,适合创建单个对象的时候使用(第一种方法),第二种
        永远是垃圾,如果你要批量创建多个对象,那么我推荐使用第三种方式

	3、自定义构造函数方式:21、创建自定义构造函数
			function 类名(name,age,hobby){
				this.name=name;
				this.age=age;
				this.hobby=hobby;
			}
			//千万不要再里面创建方法,每个对象都会创建出一个相同的方法,浪费内存 - 学习继承
                         后就可以解决

		2、调用构造函数创建对象
			var obj=new 类名(实参,...)
	
	面向对象:
		优点:
			1、逼格高,所有的属性和方法都保存在一个对象之中 - 更符合现实生活更有意义
			2、每个功能特地分开写 - 便于以后维护
			3、铁锁链舟 - 一个方法触发多个方法联动

		缺点:对新手不友好,尤其是this的指向的问题

3、*****继承:父对象的成员(属性和方法),子对象可以直接使用
	为什么要继承:代码重用!提高代码的复用性,节约了内存空间,网站的性能自然也就提升了
	何时继承:只要多个子对象公用的属性和【方法】,都要集中定义再父对象之中

	1、***如何找到原型对象(父对象):保存一类子对象共有属性和共有方法
		1、对象名.__proto__; //必须先有一个对象
		2、构造函数名.prototype;//构造函数几乎人人都有,除了Math和window,new 构造函数名();
                //Array、String、Date、RegExp...

	2、*面试题:两链一包:作用域链和【原型链】和闭包
		每个对象都有一个属性:.__proto__,可以一层一层的找到每个人的父亲,形成的一条链式结构,
            我们就称之为叫做原型链
		可以找到父对象的成员(属性和方法),作用:找共有属性和共有方法,自己没有,会悄悄向上找
		最顶层的是Object的原型,上面放着一个我们眼熟的方法
                toString(),怪不得人人都可以使用toString
		JS万物皆对象

	3、有了原型对象,可以设置共有属性和共有方法
		1、原型对象.属性名=属性值;
		2、原型对象.方法名=function(){}

周五

面向对象:

1、*****继承具有很多很多的面试和笔试题:

1、判断是自有还是共有:
     1、判断自有:obj.hasOwnProperty("属性名");
     结果结果为true,说明是自由属性,如果结果为false,有两种可能,有可能是共有,也有可能是没有

	2、判断共有:
	if(obj.hasOwnProperty("属性名")==false&&"属性名" in obj){//in关键字,会自动查找整条原型链上的属性,找到了结果为true,找不到结果为false
				共有
			}else{
				没有
			}

			完整公式:
			if(obj.hasOwnProperty("属性名")){
				自有
			}else{
				if("属性名" in obj){
					共有
				}else{
					没有
				}
			}

	2、修改和删除:自有和共有
		自有:
			修改:obj.属性名=新属性值;
			删除:delete obj.属性名;

		共有:
			修改:原型.属性名=新属性值;//千万不要觉得,自己能拿到,就能直接修改,这样很危险,
                    并没有修改原型东西,而是再本地添加了一个同名属性
			删除:delete 原型.属性名;//千万不要觉得,自己能拿到,就能直接删除,操作会无效

	3、*如何为老IE的数组添加indexOf方法 - 这道题不是固定的,可能问如何为一类人创建某个方法
		if(Array.prototype.indexOf===undefined){//老IE
			Array.prototype.indexOf=function(key,starti){
				starti===undefined&&(starti=0);
				for(var i=starti;i<this.length;i++){
					if(this[i]==key){
						return i;
					}
				}
				return -1;
			}
		}
		var arr1=[1,2,3,4,5];
		var arr2=[2,4,6,8,10];
		console.log(arr1.indexOf(2));
		console.log(arr2.indexOf(2));
		更多案例见0405.html

	4、*如何判断x是不是一个数组:4种方式:千万别用typeof,只能检查原始类型, 不能检查引用类型,如果
    检查引用类型的得到的结果都是一个object
		1、判断x是不是继承自Array.prototype
			Array.prototype.isPrototypeOf(x);
			结果为true,说明是数组,结果为false,说明不是数组

		2、判断x是不是由Array这个构造函数创建
			x instanceof Array;
			结果为true,说明是数组,结果为false,说明不是数组

		3Array.isArray(x) - ES5新增的方法,只有数组可以这么使用
			结果为true,说明是数组,结果为false,说明不是数组

		4、*输出【对象的字符串】形式
			在Object的原型上保存着最原始的toString()方法
			原始的toString输出的形式:[object 构造函数名]
			***多态:子对象觉得父对象的成员不好用,就在本地定义了一个同名函数,覆盖了父对象
                        的成员,不严格定义:同一个函数,不同的人使用,表现出来得效果不一样,由多种形态
			固定套路:
				Object.prototype.toString.call/apply(x) === "[object Array]";

	5、实现自定义继承:
		1、两个对象之间设置继承:
			子对象.__proto__=父对象

		2、多个对象之间设置继承:
			构造函数名.prototype=父对象;
			时机:应该在开始创建对象之前就设置好继承关系

2、ES6 - class关键字:简化面向对象(封装、继承、多态)

class 类名 extends 老类名{
    constructor(name,speed,rl){//放在constructor里面得都是自有属性
    super(name,speed);//帮你调用你继承的老类得constructor函数
    this.rl=rl;
  }//放在constructor外面得都是共有方法
  //还会继承到老类所有的API,也可以在此处添加属于自己的新的API
}

var obj=new 类名(实参,...)

3、*****Function - 闭包:

作用域:21、全局:随处可用,可以反复使用,缺点:容易被污染

2、函数:只能在函数调用时内部可用,不会被污染,缺点:一次性的,使用完就会释放的。

***函数的执行原理:
    1、程序加载时:
	创建执行环境栈(ECS):保存函数调用顺序的数组
	首先压入全局执行环境(全局EC)
	全局EC引用着全局对象window	
	window种保存着我们的全局变量

   2、定义函数时
	创建函数对象:封装代码段
	在函数对象中有一个scope(作用域)属性:记录着函数来自的作用域是哪里
	全局函数的scope都是window

   3、调用前
	在执行环境栈(ECS)压入新的EC(函数的EC)
	创建出活动对象(AO):保存着本次函数调用时用到的局部变量
	在函数的EC中有一个scope chain(作用域链)属性引用着AO
	AO还有parent属性是函数的scope引用着的对象

   4、调用时:
	正是因为有了前面三步,才会带来变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错

   5、调用完:
	函数的EC会出栈,没人引用着AO,AO自动释放,局部变量也就释放了

*****闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊

    何时使用:希望保护一个可以【反复使用的局部变量】- 私有化变量
    如何使用:
	1、两个函数进行嵌套
	2、外层函数创建出受保护的变量
	3、外层函数return出内层函数
	4、内层函数在操作受保护的变量

强调:
	1、判断是不是闭包:有没有两个函数嵌套,返回内层函数,内层函数再操作受保护的变量
	2、外层函数调用了几次,就创建了几个闭包,受保护的变量就有了几个副本
	3、同义词外层函数调用,返回的内层函数,都是在操作同一个受保护的变量

缺点:受保护的变量,永远都不会被释放,使用过多,会导致内存泄漏 - 闪退,不可多用!
问题:应该在哪里去使用呢?
	1、四个事件需要防抖节流 - 共同:触发的飞快,但是我们不需要飞快的修改DOM树!
		1、elem.onmousemove
		2、input.oninput 
		3window.onresize
		4window.onscroll

	防抖节流公式:
		function fdjl(){
			var timer=null;
			return function(){//停开停开......停开
				if(timer){clearTimeout(timer);timer=null;}
				timer=setTimeout(()=>{
					操作;
				},间隔毫秒数)
			}
		}
	
		var inner=fdjl()

总结:

两链一包:
1、作用域链:以函数的EC的scope chain属性为起点,经过AO逐级引用,形成的一条链式结构
	作用:查找变量,来带了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
2、原型链:每个对象都有一个属性.__proto__,可以一层一层的找到每个人父亲,形成了一条链式结构
	作用:找共有属性和共有方法的,哪怕自己没有会悄悄的向上查找,如果最顶层也没有才会报错
	最顶层是Object的原型,甚至上面放着我们眼熟的API - toString,怪不得人人都可以使用
3、闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
	作用:专门用于防抖节流