第四周复习

188 阅读11分钟

function

作用域

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

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自动释放,局部变量也就自动释放了

函数的执行原理图.png

两链一包

作用域链:以函数的EC的scope chain属性为起点,经过AO,逐级引用,形成的一条链式结构
 作用:查找变量,带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
闭包:我们发现全局和函数都有缺点,希望保护一个【可以反复使用的局部变量】的一种词法结构
何时使用:希望保护一个【可以反复使用的局部变量】的时候
	   如何使用:
		1、两个函数进行嵌套
		2、外层函数创建出受保护的变量
		3、外层函数要return出内层函数
		4、内层函数要去操作受保护的变量
	
			function f1(){
				创建的受保护的变量
				return function(){
					操作受保护的变量
				}
			}
	
	   强调:
		1、判断是不是闭包,有没有两个函数嵌套,返回内层函数,内层函数在操作受保护的变量
		2、外层函数调用几次,就会创建几个闭包,受保护的变量就有了几个副本
		3、同一次外层函数调用,返回的内层函数,都是在操作同一个受保护的变量

	   缺点:受保护的变量,永远不会被释放,使用过多,会导致内层泄漏 - 不可多用
	   问题:应该在哪里用呢? - 防抖节流
		1、三个事件需要防抖节流
			1、elem.onmousemove - 鼠标移动事件
			2、input.oninput - 每次输出/改变都会触发
			3window.onresize - 每次窗口的大小发生变化就会触发

			公式:
			
			function fdjl(){
				var timer=null;//2
				return function(){//3、
					if(timer){clearTimeout(timer);timer=null}//关闭之前的定时器
					timer=setTimeout(function(){//开启定时器,等1s才执行
						//操作由你来书写
					},1000)
				}
			}
			
			var result=fdjl();

Object (对象)

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

开发方式:面向对象 和 面向过程

1、面向过程:经过 - 开始->结束,我们一直以为的开发方式就是面向过程,先干什么再干什么最后干什么
2、面向对象:对象(属性和方法),js有一句话万物皆对象:假设一个人是一个对象的话:
	
为什么要面向对象:现实中所有的数据都必须包含在一个事物中才有意义
何时使用面向对象:以后做任何操作都要封装在一个对象中

封装:创建自定义对象:3种

1、*直接量方式:适合创建单个对象
			var obj={
				"属性名":属性值,
				...
				"方法名":function(){},
				...
			}
		   强调:1、其实属性名和方法名的双引号可以省略不写 - 暂时建议你加上为了以后学习的json数据格式
			 2、访问对象的属性和方法
				*obj.属性名		===		obj["属性名"]
				*obj.方法名();		===		obj["方法名"]();
				建议使用.访问属性和方法,更简单
				***js中一切都是对象,一切对象的底层都是hash数组
			 3、访问到不存在的属性,返回undefined
			 4、可以随时随地的添加新属性和新方法
			 5、希望获取出对象所有的东西:遍历:for in,obj[i]才能拿到
			 6、***如果你希望在对象的方法里使用对象自己的属性:写为this.属性名
				*****难点:this的指向:
					1、单个元素绑定事件 this->这个元素
					2、多个元素绑定事件 this->当前触发事件的元素
					3、函数中this->谁在调用此方法,this就是谁
					4、定时器中this->window
					5、箭头函数this->外部对象
					6、构造函数中this->当前正在创建的对象

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

		以上两个方法都有一个缺陷:一次只能创建一个对象

3、自定义构造函数方式:21、创建自定义构造函数
				function 类名(name,age,salary){
					this.name=name;
					this.age=age;
					this.salary=salary;
				}

			2、调用构造函数创建出对象
				var obj=new 类名(实参,...);

继承:父对象的成员(属性和方法),子对象可以直接使用

何时继承:只要多个子对象公用的属性和【方法】,都要集中定义在父对象中

如何找到父对象(原型对象)

1、对象名.__proto__;//必须先有一个对象
2、构造函数名.prototype;//构造函数名:Array/Object/String/Number/Boolean/RegExp/Date...几乎人人都有,除了Math

面试题:两链一包

作用域链(查找变量)、原型链(查找属性和方法)、闭包(保护一个反复使用的局部变量)
每个对象都有一个属性:.__proto__,可以一层一层的找到每个人的父亲,形成的一条链式结构,我们就称之为叫做原型链
可以找到所有父对象的成员(属性和方法),作用:查找找共有属性和共有方法
最顶层是Object的原型,上面放着我们很眼熟的toString方法,怪不得人人都可以使用toString
JS万物皆对象

有了原型对象,设置共有的属性和方法

原型对象.属性名=属性值
原型对象.方法名=function(){};

继承笔试题

1、判断是自有还是共有:

	if(obj.hasOwnProperty("属性名")){
						自有
					}else{
						if("属性名" in obj){
							共有
						}else{
							没有
						}
					}
			

2、修改&删除:自有和共有

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

				共有:修改:原型对象.属性名=新值; - 千万不要直接在本地做操作,非常危险,添加上一个同名属性
				      删除:delete 原型对象.属性名; - 如果对本地删除,没有任何效果的

3、如何为老IE的数组添加indexOf方法

//判断是不是老IE
			if(Array.prototype.indexOf===undefined){
//				//我为数组类添加了一个indexOf方法
//				Array.prototype.indexOf=function(key,starti){
					//原理
					//用户如果没有传入开始位置,则设置为0
//					starti===undefined&&(starti=0);
					//从开始位置处,循环当前数组的每一个元素
//					for(var i=starti;i<this.length;i++){
						//每一个元素和用户传入的关键字进行比较,如果比较到了
//						if(this[i]==key){
							//返回下标
//							return i;
//						}
//					}
					//没找到返回-1
//					return -1;
//				}
//			}

4、判断x是不是一个数组 - 4种方式

1、判断x是不是继承自Array.prototype;
					Array.prototype.isPrototypeOf(x);//true说明是一个数组

2、判断x是不是由Array这个构造函数创建的
					x instanceof Array;//true说明是一个数组

3Array.isArray(x);//true说明是一个数组,只有数组可以这么判断,ES5提供的一个新方法,老IE都不支持

4、输出【对象的字符串】形式
					在Object的原型上保存着最古老最原始的toString方法
					原始的toString输出形式:[object 构造函数名]
					***多态:多种形态:子对象觉得父对象的成员不好用,在本地定义了同名成员覆盖了父对象的成员	
					固定套路:借用:Object.prototype.toString.apply(x)
					if(Object.prototype.toString.apply(obj)==="[object Array]"){
						console.log("数组")
					}else{
						console.log("不是数组")
					}

5、如何实现自定义继承

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

ES5

保护对象(面试笔试,开发中不用)

1、四大特性 - 每一个属性或方法都有四大特性
		如何设置四大特性:
			Object.defineProperties(obj,{
				"属性名":{
					value: 实际保存值的地方,
					writable: true/false,//开关:控制着这个属性是否可以被修改
					enumerable: true/false,//开关:控制着这个属性是否可以被for in循环遍历到
					configurable: true/false,//开关:控制着这个属性是否可以被删除
				}
			})

2、三个级别:
		1、防扩展:禁止给对象添加新属性
			Object.preventExtensions(obj);

		2、密封:禁止给对象添加新属性和删除属性
			Object.seal(obj);

		3、冻结:禁止给对象添加新属性和删除属性和修改属性
			Object.freeze(obj);

数组的新的API

1、判断:判断结果一定是一个布尔值

every:每一个 - 要求每一个元素都要满足,结果才为true,只要有一个不满足,结果则为false - 类似&&:碰到false就不会再执行后续操作了
			语法:arr.every(function(val,i,arr){
					//val - 当前值
					//i - 当前下标
					//arr - 数组本身
					return 判断条件
			      })

some:有一些 - 要求每一个元素都不满足,结果才为false,只要有一个满足,结果则为true - 类似于||:碰到true就不会再执行后续操作了
			语法:arr.some(function(val,i,arr){
					return 判断条件
			      })

2、遍历:将数组中每一个元素取出来执行 相同 或 相似的操作

forEach:遍历数组,直接修改原数组
			语法:arr.forEach(function(val,i,arr){
					直接做操作
			      })

map:遍历数组,不修改原数组,返回一个新数组
			语法:var newArr=arr.map(function(val,i,arr){
					return 操作;
			      })

3、汇总和过滤:

过滤:筛选出自己想要的,但是不会修改原数组
			语法:var newArr=arr.filter(function(val,i,arr){
					return 判断条件;
			      })

汇总:把数组中的每个元素都汇总到一起
			语法:var result=arr.reduce(function(prev,val,i,arr){
					return prev+val;
			      },基础值)

以上留个APi的底层都是for循环,目的也是为了简化for循环

面试:严格模式:很严格

开启:在你的任何作用域的顶部都可以加上这句话:"use strict"
功能:1、禁止给未声明的变量赋值 - 解决了全局污染
      2、静默失败升级为错误

call/apply/bind:不是自己的方法也可以使用,不管是笔试、面试、实际开发都很常用

call/apply:【临时替换函数中的this】 - 借用
语法:函数名.call(借用的对象,实参,...); - 单独传入每一个实参


函数名.apply(借用的对象,[实参,...]); - 只能传入一个实参,是一个数组,但是其实apply也会悄悄的将数组打散
强调:call/apply,相当于立刻调用函数,立刻执行

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


ES6

模板字符串

可以再字符串中放入变量,不需要在做字符串的拼接了,简化了输入法的切换,${}还实现了一个简单的js环境
		语法:`我的名字叫${name}`

块级作用域

尽量以后创建变量【优先】使用let关键字
		let 变量名=值;
		作用:
		  1、禁止了声明提前
		  2、添加了块级作用域:一个{}就是一个块
		  3、记录着当前触发事件的元素的下标

箭头函数:简化回调函数

公式:去掉function,在()和{}之间添加=>,如果形参只有一个,省略(),如果函数体只有一句话,省略{},如果函数体只有一句话并且是returnreturn和{}都省略
特殊:千万不要将事件也简化为箭头函数 - this会失效 - 暂时

for of循环

for(var v of arr){
			v;//当前元素
		}

		缺点:1、不能修改原数组,只能返回新数组
		      2、不支持hash数组,不支持对象

DOM(Document Object Model(文档对象模型))

将每一个标签/元素/属性/文本/注释,看做了一个DOM节点/元素/对象(提供了操作这些东西的属性和方法)

面试题:HTML/XHTML/DHTML/XML

HTML - 网页
XHTML - 更严格的网页
DHTML - Dynamic:动态的网页,其实并不是新技术、新概念,只是将现有技术的整合统称,使我们网页在离线版也具有动态效果
DHTML:HTML+CSS+JS(dom)
XML - 数据格式

DOM:原本是可以操作一切结构化文档的 HTML 和 XML,后来为了方便各类开发者分为了3个部分

1、核心DOM:【无敌】,既可以操作HTML 和 XML
		    缺点:API比较繁琐

2HTML DOM:只能操作HTML,API简单,缺点:比如属性部分,只能操作标准属性,不能操作自定义属性

3、XML DOM:只能操作XML,XML基本已经被淘汰了 - 现在最流行的数据格式json代替了

开发建议:优先使用HTML DOM,HTML DOM实现不了的时候在用核心DOM进行补充

每个DOM元素都有三大属性

1、xx.nodeType:描述节点的类型
		document节点:9
		元素节点:1
		属性节点:2
		文本节点:3

		以前有用:判断xx是不是一个页面元素 - 因为以前我们找元素的方法和大家现在不一样

2、xx.nodeValue:描述节点值,说白了就是获取属性值
		以前有用:因为我们获取一个属性值没有现在这么容易

3、***xx.nodeName:描述节点名称 - 判断xx是什么标签 - 后期搭配上事件委托(利用冒泡)
		注意:返回的是一个全大写的标签名

通过 关系 获取元素

        父:xx.parentNode;
	子:xx.children; - 集合
	第一个儿子:xx.firstElementChild;
	最后一个儿子:xx.lastElementChild;
	前一个兄弟:xx.previousElementSibling;
	后一个兄弟:xx.nextElementSibling;

递归:简单来说就是函数中,又一次调用了函数自己,迟早有一天会停下来

何时使用:遍历DOM,专门用于【遍历层级不明确】的情况 - 不光可以遍历层级不明确的DOM树,还可以遍历层级不明确的数据
	如何使用递归:2步
		function 函数名(root){
			1、第一层要做什么操作就直接做

			2、判断他有没有下一级,如果有下一级再次调用此方法,但是传入的实参是下一级		
		}

		函数名(实际的根节点)

		算法:深度优先!优先遍历当前节点的子节点,子节点遍历完毕才会调到兄弟节点
		缺点:同时开启大量的函数调用,消耗能存,只有一个情况采用:【遍历层级不明确】

		递归 vs 循环
		递归:优点:直观、易用
		      缺点:性能较低
		循环:优点:性能高,几乎不占内存
		      缺点:难得一匹

API直接查找元素

1、按照HTML的特点去查找元素
	1、id:var elem=document.getElementById("id值")
	2、class/标签名:var elems=document.getElementsByClassName/TagName/Name("class/标签名");
	建议:表单控件元素尽量可以不写class,因为必须要写name



2、按照CSS的选择器去查找元素
	1、单个元素:var elem=document.querySelector("任意css选择器");
		强调:万一选择器匹配到多个元素,只会返回第一个
		      没找到null

	2、多个元素:var elems=document.querySelectorAll("任意css选择器");
		强调:找到了集合,没找到空集合
		更适合做复杂查找
                
                

面试题:getXXX 和 querySelectorXXX 有什么区别?
		返回结果不同:
			1、getXXX:HTMLCollection - 是一个动态集合
			2、queryXXX:nodeList - 是一个静态集合

			动态集合:根据DOM树的改变,动态集合也会悄悄改变,每一次修改DOM树,getXXX都会悄悄再次查找元素
			    缺点:每次都会悄悄重新查找,效率较低,不能使用forEach

			静态集合:每次修改DOM树,静态集合都不会发生变化,只会认准你找的时候的第一次找到的元素
			    优点:每次不会悄悄重新查找,效率较高,支持forEach

创建元素

1、创建空标签:var elem=document.createElement("标签名");
		 比如:var a=document.createElement("a");

	2、添加必要的属性 和 事件
		elem.属性名="属性值"
		elem.on事件名=function(){操作}
	
	以上两步只是在js内存中创建出了元素,还需要渲染到DOM树上

渲染页面

 *1、父元素.appendChild(新);//新元素会追加到父元素中当最后一个儿子
	2、父元素.insertBefore(新,已有子元素);//新元素会追加到父元素中当儿子,会插到已有子元素的前面
3、父元素.replaceChild(新,已有子元素);//新元素会替换到父元素中当儿子,会替换已有子元素

删除元素

elem.remove();

HTML DOM就是对核心DOM进行的简化

Image对象:图片对象

   简化了创建方式:var img=new Image();
不是人人都能简化创建,只有个别可以

Form对象:表单对象

  简化了查找元素:var form=document.forms[i];//获取页面上的第iform元素
简化了查找表单控件元素:var inp=form.elements[i]//获取此form表单中的第i个表单空间元素

Select对象

属性:1select.options === select.children 获取到select下面的所有option
2、*select.selectedIndex;//获取到选中项的下标


方法:1、*select.add(option);//将option上树
2select.remove(i);//删除下标为i的option

*专属事件:onchange - 选中项发生改变后才会触发

Option对象

简化了创建方式:var opt=new Option("innerHTML","value");
建议你,如果以后希望创建出opt并且放到select中:一句话完成4个操作
		select.add(new Option("innerHTML","value"))