学习JS第四周总结

269 阅读9分钟

Object

JS是基于原型的一个面向对象语言。
面向对象 – 三大特点:封装、继承、多态
封装/创建/定义:封装的自定义对象:31.*直接量方式:
         var obj = {
             “属性名”:属性值,
                 …
                 “方法名”:function(){操作},
                 …
        }
    强调:
     1.其实属性名和方法名的“”可以不加 – 暂时建议加上,但Json数据格式的键必须加上。
     2.访问对象的属性和方法
        a)对象名.属性名;   ===  对象名[“属性名”];
        b)对象名.方法名();        ===   对象名[“方法名”]();
        c)JS中一切都是对象,除了undefined和null,一切对象的底层都是hash数组。
        d)建议使用 **.** 去访问对象的属性和方法,更简单。
     3.访问到不存在的属性,返回的是一个undefined
     4.可以随时随地的添加新属性和新方法
          Obj.属性名=属性名;
          Obj.方法名=function(){};
     5.如果希望遍历出所有东西,必须使用for in循环,必须写为obj[i]才能拿到,不要使用 **.** 会得到undefined。
     6.***如果希望在对象的方法里,使用自己的属性,必须写为this.属性名!
    **this的指向**
        ① 单个元素绑定事件this  -> 这个元素
        ② 多个元素绑定事件this  -> 当前元素
        ③ 定时器中this -> window
        ④ 箭头函数 this -> 外部对象
        ⑤ 函数中的this -> 谁在调用此方法,this就是谁
        ⑥ 自定义构造函数的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 类名(实参,…);
面向对象:
    优点:
        所有的属性和方法都保存在一个对象之中 – 更符合现实生活,更有意义。
        每个功能特地的分开写 – 便于维护,哪怕不写注释也看得懂
        一个方法触发,多个方法联动
继承:
    父对象的成员(属性和方法),子对象可以直接使用
    为什么要继承:代码重用!提高代码的复用性,节约了内存空间,网站的性能自然也就提升了。
何时继承:只要多个子对象共用的属性和【方法】,都要集中定义在父对象之中

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


**两链一包:作用链和【原型链】和闭包**  (初次简单理解)
    原型链:每个对象都有一个属性:__proto__,可以一层一层的找到每个人的父亲,形成一条链式结构,我们就称之为原型链。
    可以找到父对象的成员(属性和方法),作用:找共有属性和共有方法的,自己没有的,会悄悄的向上找,如果顶层没有就会报错。
    最顶层的是object的原型,上面放着一个我们很眼熟的方法toString()
    作用域链:
        以函数EC的scope chain属性为起点,经过AO逐级引用,形成的一条链式结构
        作用:查找变量,带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
    闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
    作用:专门用于完成防抖节流    
    
    
有了原型对象,可以设置共有属性和共有方法:
    原型对象.属性名=属性值;
    原型对象.方法名=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===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;
             }
          }
   多态:
       子对象觉得父对象的成员不好用,就在本地定义了一个同名函数,覆盖了父对象的成员,
       不严格的定义:同一个函数,不同的人来使用,表现出来的效果是不一样的,有多种形态  
  实现自定义继承:
      1.两个对象之间设置继承
          **子对象.__proto__=父对象**
      2.批量设置继承:
          **构造函数名.prototype=父对象**
          注意时机:应该在创建对象之前设置好继承关系
          
如何判断xxx是不是一个数组:4种方法
Array.prototype.isPrototypeOf(x);
x instanceof Array;
Array.isArray(x) ES5提供的
以上三种,结果为true,说明是数组,结果为false,说明不是数组
*输出【对象的字符串】形式
    在Object的原型上保存着最原始的toString();
    最原始的toString()输出的形式:[object 构造函数名]
    借用的固定套路:Object.prototype.toString.call/apply(x)==="[object Array]";
    
ES6 – class关键字
为了简化面向对象:封装、对象、多态
    class 类名 extends 老类{
            constructor(name,speed,rl){//写在constructor里面的其实就是自有属性,
            就跟以前的构造函数的写法一样
            super(name,speed);
            this.rl=rl;
            }
   //共有方法,就算你没有创建共有方法,也会得到你继承的老类(爷爷)的共有方法
   //自己也可以写,但是不会覆盖到爷爷,只是在爸爸身上添加了,形成了多态
   }
Function:作用域链&闭包
 作用域:21、全局:随处可用,可以反复使用,缺点:容易被污染
 2、函数:只能在函数调用时内部可用,不会被污染,缺点:一次性的,使用完就会自动释放
 闭包:
     作用:用于防抖节流
     希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
     如何使用:思路
         1、两个函数进行嵌套
         2、外层函数创建出受保护的变量
         3、外层函数return出内层函数
         4、内层函数操作受保护的变量
 强调:
     1、判断是不是闭包,有没有两个函数进行嵌套,返回内层函数,内层函数再操作受保护的变量
     2、外层函数调用几次,就创建了几个闭包,受保护的变量就有了几个副本
     3、同一次外层函数调用,返回的内层函数,都是在操作同一个受保护的变量
     缺点:受保护的变量,永远不会被释放,使用过多,会导致内存泄漏 - 闪退的,不可多用!
     
防抖节流
5个事件需要防抖节流 - 共同:触发的飞快,但是我们不需要飞快的修改DOM树!
    1、elem.onmousemove;
    2、input.oninput;
    3、elem.onclick;
    4window.onscroll;
    5window.onresize;
    固定公式:
        function fdjl(){
            var timer=null;
            return function(){
                if(timer!=null){
                clearTimeout(timer)
                }
                timer=setTimeout(()=>{
                      操作!
                },1000)
            }
         }
函数的执行原理
1.程序加载时:创建执行环境栈(ESC):保存函数调用顺序的数组
    首先压入全局执行环境(全局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自动释放,局部变量也就释放了
ES5 - 保护对象的属性和方法
**对象的每个属性都有四大特性+get()+set():** 
    value: 1001, //实际保存值的地方
    writable: true, //开关,控制着这个属性是否可以被修改 - 默认值为true
    enumerable: true, //开关,控制着这个属性是否可以被for in循环遍历到 - 默认值为true
    configurable: true//开关,控制着这个属性是否可以被删除 - 默认值为true
  修改四大特性:
      Object.defineProperties(对象名,{
              "属性名":{
              四大特性
              },
              ...
      })//此方法只调用了一次,但是四大特性写着始终麻烦,甚至不能防止添加
  get()和set():
      Object.defineProperty(对象名,"属性名",{
              get:()=>{
                  console.log("获取数据会进行拦截")
              },
              set:(v)=>{
              //v就是你设置的东西
              console.log("设置数据会进行拦截")
              }
     })
**三个级别:**
    1.Object.preventExtensions(x);//放扩展 防添加
    2.Object.seal(x);//密封  防添加和防删除
    3.Object.freeze(x);//冻结  防添加和防删除和防修改
    
深拷贝还有一种方式:
    后端穿衣服:var jsonTxt=JSON.stringify(jsonObj) - Node.js就是这句话
    前端脱衣服:var jsonObj=JSON.parse(jsonTxt); 或者 eval("("+jsonTxt+")")
    
Error对象
浏览器自带4种错误类型:快速找到错误
    ① 语法错误:SyntaxError - 一定是你的符号/语法写错了
    ② 引用错误:ReferenceError - 没有创建就去使用了
    ③ 类型错误:TypeError - 不是你的方法,你却去使用了,最有可能的就是你拿到了undefinednull
    ④ 范围错误:RangeError - 只有API会遇到:num.toFixed(d);//d取值范围:0~100之间
错误处理:就算发生错误,我们也不希望报错,而是给出一个错误提示,让后续代码依然可以继续执行!
语法:
    try{
        只放入可能出错的代码
       }catch(err){
            发生错误后才会执行的代码
            err - err形参会报错到错误消息,但是不是报错,只是一个黑色的错误消息,
            但是是英文,中国人不一定看得懂,建议你添加上中文错误提示
        }
            try...catch...的性能非常差,几乎是所有代码里效能最差的,放在try里面的代码
            效率都会被降到最低
*可用一个技术代替他:分支技术!
*开发经验的累积:记住一切的客户端输入/用户输入都是坏人 - 但是不必担心,只要做好防护就
                不会出错(!isNaN、正则) 
 抛出自定义错误:只要是报错,就会导致后续代码不执行
     throw new Error("自定义错误消息");
     
事件轮询
    JS其实是单线程应用,代码必须是从上向下,一步一步执行的,如果某一个代码非常耗时,可能会
    导致整个页面都卡住了,尤其是如果把JS放在head里面,用户可能只能看到一个白板
   1、宏任务
        不会再卡住我们的单线程应用,可以让后续代码先走,我们慢慢跟着来,但是问题在于,多个
        宏任务同时存在,到底谁先执行,谁后执行,分不清楚
        1、定时器:setIntervalsetTimeout
        2AJAX - 他是前端和后端连接的关键点,他也是一个异步宏任务 
   2.微任务
       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对象,所以我们才在操作
      里面returnPromise
     console.log("后续代码跑的依然更快")
封装一个运动(动画)函数
每次都要写elem.style.css属性名="css属性值",简化这一句话
    function animate(selector,obj){
        var elem=document.querySelector(selector);
        for(var i in obj){
            console.log(i)
            elem.style[i]=obj[i];
        }
    }