JS第四周总结

137 阅读8分钟

Object:对象

    对象具有属性和方法
    
    面向对象:三大特点: 封装  继承  多态
    封装/创建/定义
            1.直接量方式:
                var obj={
                    "属性名":属性,
                    ... ,
                    "方法名":function(){
                    操作
                },...
             }
    强调:
        1.其实属性名和方法名的""可以不加
        2.访问对象的属性和方法
             对象名.属性名    === 对象名["属性名"]
             对象名.方法名()  === 对象名["方法名"]()
        js中一切皆为对象,除了undfined和null;一切对象的底层都是hash数组
        3.访问到不存在的属性,返回undifined
        4.可以随时随地添加新属性和新方法
        obj.属性名=属性值
        obj.方法名=function(){}
        5.必须使用for in循环,必须写为obj[i]才能拿到,不要使用.会得到undifined
        6.在对象的方法里,使用对象自己的属性,必须写为this.属性名
        this的指向
            1.单个元素绑定事件this -> 这个元素
            2.多个元素绑定事件this -> 当前触发这个事件的元素
            3.定时器中的this -> window
            4.箭头函数中的this -> 外部对象
            5.函数中的this -> 当前使用函数的这个对象
            6.自定义构造函数中的this -> 当前正在创建的对象
            
   2.预定义构造函数方式
       var obj=new object();//空对象 需要后续添加属性和方法
       obj.属性名=属性值
       obj.方法名=function(){}
       以上两个方法都有一个缺陷:一次只能创建一个对象
       
   3.自定义构造函数
       创建自定义构造函数
           function 类名(name,age,hobby,...){
               this.name=name;
               this.age=age;
               this.hobby=hobby
           }
     调用构造函数创建对象
         var obj=new (){实参,...}
     
     面对对象的优点:
         1.所有属性和方法都保存在一个对象中
         2.每个功能分开写,便于以后维护
         3.一个方法触发,多个方法联动
         
    继承 :父对象的成员(属性和方法),子对象可以直接使用
    
    为什么要继承:代码重用,提高代码的复用性,节约内存空间,提升网站性能
    
    何时继承:只要多个子对象共用的属性和方法,都要集中定义在父元素中
   
    如何找到原型对象(父对象):保存一类子对象的共有属性和共有方法
        对象名.__proto__;//必须先有一个对象
        构造函数名.prototype;//构造函数几乎人人都有,除了window和Math
    
    有了原型对象,可以设置共有属性和共有方法
        1.原型对象.属性名=属性值
        2.原型对象.方法名=function(){}

面向对象:

    判断自有还是共有
    判断自有:obj.hasOwnProperty("属性名")  结果为true,则有自有属性;为false,则可能有,可能没有
    判断共有:if(obj.hasOwnProperty("属性名")==false&&"属性名"in obj){//(in 关键字,会自动查找整条原型链上的属性,找到结果了为true,没找到为false)
        共有
    }else{
        自有
    }
    完整公式
        if(obj.hasOwnProperty("属性名")){
            console.log("自有")
        }else{
            if("属性名" in obj){
                console.log("共有")
        }else{
            console.log("没有")
        }
    }
    
    删除和修改: 自有和共有
        自有:
            修改:obj.属性名=新值
            删除: delete obj.属性名
        共有:
            修改:原型.属性名=新值
            删除:delete 原型.属性名
    如何为老IE的数组添加indexOf方法
        if(Array.prototype.indexOf===undifined){//判断是不是老IE
            Array.prototype.indexOf=function(key,starti){
                starti===undifined&&(starti=0);
                fot(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));
  
  判断X是不是数组
      判断X是不是继承自Array.prototype
          Array.prototype.isPrototypeOf(X) 结果为true,则是;为false,则不是
          
      判断X是不是由Array这个构造函数创建的
          x instanceof Array 结果为true,则是;为false,则不是
           
      Array.isArray(x) //老IE不支持,只有数组有此方法 结果为true,则是;为false,则不是
      
      对输出[对象的字符串]形式
          在Object的原型上保存着最原始的toString()
          对最原始的toString()输出的形式:[object 构造函数名]

多态

    子对象觉得父对象的成员不好用,就在本地定义一个同名函数,覆盖父对象的成员。不严格的定义:同一个函数,不同的人使用,表现出来的效果不一样,有多种形态
    借用的固定套路:Object.prototype.toString.call/apply(x)===[object.Array]
    
    实现自定义继承
        两个对象之间设置继承
            子对象.__proto__=父对象
        批量设置继承
            构造函数名.prototype=父对象
   注意:先继承,再创建对象
   
   class 关键字:简化面向对象(封装,继承,多态)
        class 类名extends 老类{
            constructor(name,speed,rl){//写在constructor里的其实是自有属性
                super(name,speed);
                this。rl=rl;
            }//共有方法,就算是没有创建的共有方法,也会得到老类的共有方法
        }
        

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、调用时:
		正是因为有了前3步,才带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错

        5、调用完:
		函数的EC会出栈,没人引用着AOAO自动释放,局部变量也就释放了
                    
    闭包:希望保护一个可以反复使用的局部变量的一种词法结构,其实还是一个函数,只是写法比较特殊
    何时使用:希望保护一个可以反复使用的局部变量
    如何使用:
        1.两个函数进行嵌套
        2.外层函数创建出受保护的变量
        3.外层函数return出内层函数
        4.内层函数操作受保护的变量
    强调:
        1.判断是不是闭包,有没有两个函数进行嵌套,返回内层函数,内层函数再保护受保护的变量
        2.外层函数调用几次,就创建了几个闭包,受保护的变量就有几个副本
        3.同一次外层函数调用,返回的内层函数,都是在操作同一个受保护的变量
    缺点:受保护的变量,永远不会被释放,食用过多,会导致内存泄漏 - 闪退,不可多用
    使用场景:防抖节流
        5个事件需要防抖节流 - 共同:触发很快,但是不需要很快的修改DOM1.elem.onmousemove
            2.input.oninput
            3.elem.onclick
            4.window.onscroll
            5.windou.onresize
            固定公式:
                function fdjl(){
                    var timer=null;
                    return function(){
                        if(timer!=null){
                            clearTimeout(timer)
                        }
                        timer=setTimeout(()=>{
                            操作
                        },1000)
                    }
                }
                
      总结:
          两链一包:
                原型链:每个对象都有一个属性.__proto__,可以一层一层的找到每个人的父亲,形成的一条链式结构
		作用:查找共有属性和共有方法,哪怕自己没有也会悄悄向上找,如果顶层都没有就会报错
	作用域链:以函数EC的scope chain属性为起点,经过AO逐级引用,形成的一条链式结构
		作用:查找变量,带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
	闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
		作用:专门用于完成防抖节流
  

保护对象:保护对象的属性方法

    对象的每个属性都具有四大特性
    {
        value:1000; //实际保存值的地方
        wirtable:true/false;//开关,控制着这个属性是否能修改,默认为true
        enumerable:true/false;//开关,控制着这个属性是否能被for in遍历到,默认为true
        configurable:true/false;//开关,控制着着属性是否能被删除,默认为true
    }
    修改四大特性:
        Object.definedProperty(对象名."属性名",{
            writable: true/false,
	enumerable: true/false,
	configurable: true/false
        })
    调用一次方法只能保护一个属性的四大特性
    
    Object.defineProperties(对象名,{
			"属性名":{
				四大特性
			},
			...
		     })

		     至少方法只调用了一次,但是四大特性写着始终麻烦,甚至不能防止添加
   三个级别:
       防扩展:防添加
          Object.preventExtensions(x)
       密封:防添加和防删除
          Object.seal(x)
       冻结:防添加和防删除和防修改
          Object.freeze(x)
          
   四大特性其实该叫六大特性 - 可以做出动态数据
          Object.definedProperty(对象名,"属性名",{
              get:()=>{
                  console.log("获取数据会被拦截")
              }
              set:()=>{
                  console.log("设置数据会被拦截")
              }
          }) 
   
   对象的深拷贝和浅拷贝
       1.浅拷贝:利用按值传递的
           var obj1={"name":"obj1"};
           var obj2=obj1;
           obj2.name="obj2";
           console.log(obj1)
           console.log(obj2)
       2.深拷贝:两者互不影响
           var obj1={"name":"obj1"};
           var obj2={...obj1};
           obj2.name="obj2";
           console.log(ob1)
           console.log(ob2)
       后端不能直接传数据,必须穿上衣服才能出门,变成一个JSON组字符串 
           后端穿衣服:var jsonTxt=JSON.stringify(jsonObj)
           前端穿衣服:var jsonObj=JSON.parse(jsonTxt)或eval"("+json+js0nTxt+")")
           
           此方法能也就能实现深拷贝
           

Error对象

    目的:
        1.快速找到错误
        2.防止用户乱输入
        
       浏览器自带的4种错误类型
           语法错误:SyntaxError - 一定是符号/语法写错了
           引用错误:RefenerError - 没有创建就去使用
           类型错误:typeError - 不是你的方法,却使用了
           范围错误:RangeError - 只有num.toFxied,d的范围0~100之间
     
     错误处理
             语法:
                 try{
                     只放入可能出错的代码
                 }catch(err){
                     发生错误后才会执行的代码
                 }
     try...catch...的性能非常差,几乎是所有代码里最差的,可用if...else...代替
     
     抛出自定义错误:
         throw new Error("自定义错误消息")
     
     严格模式:
         开启:"use strict"
         作用:
             1.禁止全局污染
             2.将静默失败升级为报错
     
     柯里化函数
         function add(a){
             return function(b){
                  return function(c){
                      console.log(a+b+c)
                  }
             }
         }
         add(3)(5)(7)
         
    匿名函数:没有名字的函数
            1.自调:只能执行一次,好处:函数中的【没有用的变量】是会自动释放的,它可以用于代替你的全局代码写法,两者很相似,都只会执行一次,但是自调会释放掉没用的东西,绑定好的事件是不会被自动释放掉的
	(function(){
		操作;
	})()

            2.回调:匿名函数不是自调,就是回调 - 往往我们不需要关注回调函数前辈们是如何创建的,我们更关心如何使用
	elem.on事件名=function(){}
	arr.sort(function(){})
	var obj={
		"方法名":function(){}
	}
	var 方法名=function(){}
	一切的回调函数都可以简化为箭头函数

  设计模式:不仅仅局限于前端,它是一种编程思想,越来越复杂,有21种设计模式
          1.单例模式:也称之为单体模式,保证一个类仅有一个实例对象创建,并且提供一个访问它的全局访问点
          最简单的单例模式:利用ES6let不允许重复声明的特性,刚好就复合了单例的特点
			let obj={
				"name":"袍哥1",
				"getName":function(){return this.name+"在上课"}
			}
		不太推挤这种写法:
			1、污染命名空间(容易变量名冲突,会报错);
			2、维护时不易管控(搞不好就直接覆盖了);

	推荐写法:
		var h52303=(function(){
			let state=null;//利用闭包:保护了state变量  //state={吴昊}
			return function(name,age){
				this.name=name;
				this.age=age;
				if(state){
					return state;
				}
				return state=this;//return state={吴昊}
			}
		})()
		
		h52303.prototype.sayHello=function(){
			return this.name+":hello";
		}
		
		var wh=new h52303("吴昊",18);
		var dwj1=new h52303("短文将1",19);

		
		console.log(wh);
		console.log(wh.sayHello());
		
		console.log(dwj1);
		console.log(dwj1.sayHello());
            2.观察者模式:也有人称呼叫作 订阅 - 发布 模式
                现实生活中:QQ空间、朋友圈、微博...
		let obj={};//{obj["袍哥"]=[(msg)=>{console.log("小吴来了",msg)},(msg)=>{console.log("小段来了",msg)},(msg)=>{console.log("小肖来了",msg)}]}
		
		//创建订阅者
		function on(id,fn){
			if(!obj[id]){//!obj["袍哥"]
				obj[id]=[];
			}
			obj[id].push(fn);
		}
		
		on("袍哥",(msg)=>{console.log("小吴来了",msg)})
		on("袍哥",(msg)=>{console.log("小段来了",msg)})
		on("袍哥",(msg)=>{console.log("小肖来了",msg)})
		on("袍哥",(msg)=>{console.log("小王来了",msg)})
		on("袍哥",(msg)=>{console.log("小李来了",msg)})
		on("袍哥",(msg)=>{console.log("小张来了",msg)})
		
		//发布者的操作
		function emit(id,msg){//obj["袍哥"] => [fn,fn,fn]
			obj[id].forEach(fn=>fn(msg));
		}
		
		btn.onclick=()=>{
			emit("袍哥","一支穿云箭,千军万马来相见")
		}
    
    事件轮询:JS其实是单线程应用,代码必须是从上向下,一步一步执行的,如果某一块代码非常耗时,可能会导致整个页面都卡住了,尤其是如果你把JS放在head里面,用户可能只能看到一个白板
            宏任务:不会再卡住我们的单线程应用,可以让后续代码先走,我们慢慢跟着来,但是问题在于,多个宏任务同时存在,到底谁先执行,谁后执行,分不清楚
	1、定时器:setIntervalsetTimeout
	2AJAX - 他是前端和后端连接的关键点,他也是一个异步宏任务

            微任务:ES6提供了Promise对象 - 可以控制异步代码,依然是异步代码,但是可以控制执行的顺序了
	可以把Promise对象理解为是现实生活中的保安、保镖...
		function ajax1(resolve){
			setTimeout(()=>{
				console.log("<h1>页面的头部</h1>");
				resolve();//放行函数
			},Math.random()*5000);
		}
		
		function ajax2(){
			return new Promise(function(resolve){
				setTimeout(()=>{
					console.log("<h1>页面的身体</h1>");
					resolve();
				},Math.random()*5000);
			})
		}
		
		function ajax3(){
			return new Promise(function(resolve){
				setTimeout(()=>{
					console.log("<h1>页面的脚部</h1>");
					resolve();
				},Math.random()*5000);
			})
		}

		new Promise(ajax1).then(ajax2).then(ajax3);
		//想要连续的.then,除非前面的结果是一个Promise对象,所以我们才在操作里面return了Promise
		console.log("后续代码跑的依然更快")