原生JS(对象、函数)

290 阅读20分钟

对象

对象的创建

  1. 造函数创建法

    • var obj=new Object();
  2. 字面量创建法

    var obj = {
      // key:value;
      name: hm,
      // 字符型key
      "name": hm,
      // 变量型key
      [a]: 12,
    };
    
    • 点语法拒绝使用非字符类型属性
    • 在对象中 key 除了字符类型以外只能支持 symbol
    • 所有对象在转换成字符串以后都会变成[Object Object](第一个 Object 说明是一个对象 第二个 Object 说明是对象类型);
    var a = "keys";
    var obj = {
      // key:value;
      name: "hm",
      // 字符型key
      name: "hm",
      // 变量型key
      [a]: 12,
    };
    var o = {
      a: 1,
    };
    var o1 = {
      b: 2,
    };
    
    obj[o] = 10; //在添加属性时,对象o被转换为字符串类型[Object Object],所以在obj中会增加[Object Object]:10;
    console.log(obj[o1]); //10   s在获取对象的属性值时,o1会转换成字符串[Object Object],所以能获取到10
    obj[undefined] = 2;
    obj[null] = 3;
    var arr = [1, 2, 3]; //数组类型不变
    var arr1 = [4, 5, 6];
    obj[arr] = 40;
    console.log(obj[arr]);
    console.log(obj);
    

对象的引用存储

  • 对象存储在堆中

  • 对象的属性之间没有关联关系

  • 判断对象是否相等,仅判断地址,不是判断对象是值

    var obj = { a: 1 };
    var obj1 = obj; //将对象{a:1}的地址赋值给obj1,所以obj和obj1指向同一个对象
    console.log(obj === obj); //判断对象是否相等,仅判断地址,而不是判断对象内容
    console.log(JSON.stringify(o) === JSON.stringify(o1)); //判断对象内容是否相等,有漏洞,有可能会出错
    
  • 判断两个对象的值是否相等,可以将对象转成 JSON 字符串,但是可能会出错

    var str = JSON.stringify(obj); //对象转换为JSON格式的字符串
    
    • 当属性自身具有特殊性时,会出错
  • JSON

    • 将对象转换成字符串

    • JSON 字符串

      • '{"a":1,"b":2,"c":"hm","checked":true,"o":{}}'
      var str = JSON.stringify(obj); //将对象转换成JSON字符窜
      
      var s = '{"a":1,"b":2,"c":"hm","checked":true,"o":{}}';
      var o = JSON.parse(s); //将JSON字符串转换成对象
      console.log(o);
      
  • 垃圾回收机制
    • 在堆中的对象可能被若干个变量引用其地址,如果这个对象在后面的内容中不再使用,我们需要将堆堆中的这个对象清除,否则不会被浏览器自动清除,这样就会造成内存垃圾的产生,当不断产生这种垃圾时,我们就把这种情况叫内存泄漏。
    • 如何处理内存泄漏
      • 先创建每个对象的管理池,针对每个对象所引用它的变量做统一存储管理,如果需要清理该对象时,将引用它的所有变量设置为 null,当内存上限达到峰值时,系统会通过垃圾回收车将这些堆中无引用的对象全部回收,这就是垃圾回收机制

对象遍历

  • 对象的遍历是通过添加属性的先后顺序遍历的

  • 广度遍历

    • for...in...
    for (var prop in obj) {
      console.log(prop, obj[prop]);
    }
    //对象的遍历是通过添加属性的先后顺序遍历的
    console.log(obj);
    
    • 对象复制
      • 浅复制
        • 只是第一层的值不会改变,原对象的第二层改变,新对象的第二层也会跟着改变
      • 深复制
        • 两个对象是完全独立的个体,原对象的改变不会引起新对象的改变
        • JSON 实现深复制
          var obj2 = JSON.parse(JSON.stringify(obj));
          console.log(obj2);
          
          • 不能复制方法
        • 递归实现深复制
          function cloneObj(obj,target){
              target=target||{};
              for(var prop in obj){
                  if(typeof obj[prop]==="object" && obj[prop]!==null){
                      target[prop]={};
                      cloneObj(obj[prop],target[prop]);
                  }else{
                      target[prop]=obj[prop];
                  }
              }
              return target;
          }
          

delete 删除对象属性

delete obj.a;
console.log(obj);
  • delete 不能删除任何 window 下的属性

函数

函数基础

  • 抽象
    • 在设计函数时,尽量不要让函数内部与外部有关联,尽量让函数内部是一个独立的个体
  1. 函数的创建

    • 函数是一个对象,存储在堆中,具备引用关系,可以在函数里面增加对象属性

    • 普通函数

    function fn(arg1,arg2,...){
        //函数的语句块
    }
    
    • 特点

      • 普通函数创建,在 script 标签被执行时,就放入堆中,并且在堆中以函数名作为变量引用堆中这个函数地址
      • 函数在创建时,就创建了这个函数名的变量,因为它是全局变量,就会被污染覆盖
      • 覆盖前仍然可以执行当前函数,覆盖后,函数不能执行
      • 如果函数中没有使用 return 关键字,返回 undefined
    • console.dir()将特殊对象以对象形式展现

    • 匿名函数创建

    var fn = function () {
      //匿名函数创建,创建好的匿名函数赋值给fn这个变量
    };
    
    • 变量什么时候定义,这个变量才能在栈中产生,才可以被调用,变量未定义之前执行函数会报错
    • 使用场景
        bn.onclick = function () {
          console.log("aa");
        };
      
    • 自执行、自调用函数
    (function () {
        console.log("aa");
    })();
    
     - 会自动执行,以后永远不能再调用
    
    • 构造函数创建
    var fn = new Function("a","b","consoloe.log(a+b"));
    
    • 注意:
      • 构造函数创建,每个参数都必须是字符串
      • 除了最后一个参数是函数的语句块以外,前面所有的参数都是函数的参数
    • 效率最低
    • 特点:只要是字符串,就可以创建函数
  2. 函数的参数

    • 在函数的()里面所填写的变量

    • 使用参数的目的

    • 函数如果没有参数,处理时是不能变化的,参数的目的是在不同情况下解决不同问题

    • 问题

      • js 是一种弱类型语言,不能对参数约束其类型,就会造成因为使用者输入的参数不符合需求而造成代码出错,无法通过白盒测试
      • ES5 版本中,js 中参数不能设置初始值不填写参数就是 undefined
  • 形参

    • 函数定义时的参数
      • fn.length
      • 形参数量
    • arguments.callee.length < arguments.length;
      • 形参长度小于实参长度
  • 实参

    • 函数执行时填入的参数
    • 实参按照形参顺序一一赋值给形参
    • 如果实参数量小于形参数量,后面的形参值是 undefined
  • arguments 的使用

    • 如果实参数量大于形参时,使用 arguments

    • arguments 只能出现在函数语句块中

    • 用于当前函数的参数不固定数量

    • 属性

      • arguments.length
        • 实参长度
        • arguments.callee.name
        • 当前函数的名字
        • arguments.callee
        • 当前函数
        • arguments.callee.caller
        • 调用当前函数的外部函数
  1. 变量的作用域
  • 变量重新定义不赋值不起作用

  • 局部变量

    • 生命周期

      • 函数执行完成后即销毁
    • 变量只能在局部中使用

    • 被定义再函数内部的变量,使用范围仅在函数内部,并且当前函数执行完成以后,该变量会销毁,下一次函数执行时,会重新定义该变量,并且变量不能保存在函数执行完成后还能使用

    • 当全局变量和局部变量同名时,函数优先使用局部变量,此时如果想使用全局变量,需要使用 window.全局变量,该方法在 ES6 中被禁止

      • 如果当前函数内没有定义与全局变量相同的局部变量,函数内会直接使用全局变量
    • 当前函数内存在于全局变量同名的未赋值的局部变量时,遵照局部变量优先原则

    • 当前函数中存在于全局变量同名的局部变量,但函数使用该变量在局部变量定义之前,仍然遵循局部变量优先原则

    • 在函数中只要看到使用 var 定义的变量,这个变量就一定是局部变量,不论是在循环里、条件里,都会变成局部变量,而且这个变量被优先

    • 在函数中设置了参数,就相当于这个参数就是被定义好的局部变量

    • 如果函数内定义的局部变量与参数同名,参数会被新定义的局部变量覆盖,但是如果局部变量未赋值,则不会影响参数值(变量提升)

  • 全局变量

    • 生命周期
      • 定义后一直存在
    • 在任何地方都可以使用的变量
    • 所有的全局变量都是属于 window 的属性
  1. return

    • return 语句会终止函数的执行并返回函数的值,在调用函数的地方可以用变量接收函数的返回值。

    • return 关键字可以返回任何形式的变量或表达式,或者什么都不返回

    • return 可以阻止后续代码的执行,这样可以有效的控制语句在一定的条件下执行,但与 break 不同

    • break 用在条件中,跳出的是条件和循环

    • return 可以跳出函数,仅针对函数使用,没有函数不能使用 return

    • 函数在执行时,返回函数中 return 的内容

    • 如果没有 return,将返回 undefined

    • 函数返回的作用

      • 返回局部变量
      • 返回参数
      • 跳出,切断,不继续执行函数
  2. 函数的运行过程

    • 函数执行中其他函数,另一个函数中的内容执行完成后才会继续执行当前函数。当需要并列执行多个时,可以在一个函数中统一调配执行顺序
  3. 回调函数

    • 将一个函数以参数的形式传入到另一个函数中,并且在那个函数执行
    function fn1(fn) {
      fn();
    }
    function fn2() {}
    fn1(fn2);
    
  4. 应用场景

  • 一般用于处理某件事情需要等待时,设置回调

  • 当不需要担心具体后续需要处理的事情时,设置回调

  • setTimeout(超时执行的函数,超时时间,执行函数的参数)

    var id = setTimeout(
      function (n) {
        console.log(n);
        clearTimeout(id); //清除时间间隔
      },
      2000,
      5
    ); //异步
    
    • 设置一个时间,当时间到达时,执行函数,只执行一次
    • 返回一个 id 数字
    • 异步执行,一定时间后处理问题
  • clearTimeout(id);

    • 清除超时函数

    • setInterval();

    var id = setInterval(fn, 2000);
    var num = 0;
    function fn() {
      console.log("aaa");
      num++;
      if (num > 3) clearInterval(id);
    }
    
    • 每隔一定时间就会执行一次函数,会执行多次
    • clearInterval(id);
    • 清除时间间隔函数
  1. 递归函数
  • 自调用函数,在函数体内部直接或间接的自己调用自己,即函数的嵌套调用是函数本身

  • 根据内存大小设置递归上限的次数,如果递归次数太多,会堆栈上限溢出

  • 每递归一次就会创建一个副本

    var i = 0;
    fn1();
    function fn1() {
      i++;
      if (i < 3) fn1();
      console.log(i);
    }
    // 以下是递归过程
    function fn1() {
      i++;
      if (i < 3) fn2();
      console.log(i);
    }
    function fn2() {
      i++;
      if (i < 3) fn3();
      console.log(i);
    }
    
    function fn3() {
      i++;
      if (i < 3) fn4();
      console.log(i);
    }
    //不被执行
    function fn4() {
      i++;
      if (i < 3) fn4();
      console.log(i);
    }
    
  • 函数调用机制

    • 任何函数之间不能嵌套定义
    • 调用函数与被调用函数之间相互独立(彼此可以调用)
    • 发生函数调用时, 被调函数中保护了调用函数的运行环境和返回地址,使调用函数的状态可以在被调用函数

数组

  • 数组是对象类型,也是引用类型,引用地址赋值

    • 数组在强转或隐式转换为字符串时,都会转换为数组的元素连接字符串值
  • 有序列表存储若干个无序的元素

  • 将元素放在列表的第几位,从 0 开始计算,这个位置就是下标,即索引值

  • 元素被存储在列表中,这个数据就是元素,简称元

  • 从列表中获取这个元素的方法,使用数组名[下标]就可以得到这个元素,这种方式叫做下标变量

  • 对象和数组的区别

    • 数组是紧密型结构,仅用下表存储对应的值,当删除一个元素时,因为紧密结构的关系,就会将后面的元素向前递进,数组是有长度的,数组中可以知道存储了多少元素,因为插入和删除都会影响数组的元素的位置和结构,因此插入和删除都会影响数组的运行效率,时间复杂度较高。数组的存储依靠下标,如果需要查找一个值,就需要遍历数组的每一个元素,已到达找到目标元素,因此时间复杂度是极高的,数组在使用时,因为是紧密型结构,我们可以根据上一个内容找到与其相关联的其他元素,例如我们可以利用数组排序,找一个最小值,还可以迅速找第二位最小值

    • 对象是一种松散型结构,属性以键值对存储,当删除一个元素时,对象的其他值不会发生变化,对象没有元素的个数,也就是对象长度,对象中不知道存储了多少个元素,插入和删除都不会影响其他数据,所以时间复杂度极低,通过 key 一一对应存储一个值,所以获取时,只需要根据 key 去取值,所以时间复杂度极低。对于对象来说,因为每个数据都独立存在,不存在关联关系,更不能排序,所以不能因关联关系找到对应的值

数组的创建

  • 因为 js 是弱类型语言,因此数组较随意,可以任意扩张,不需要限定数组的长度

    1. 字面量创建
    var arr = [1, 2, 3, 4, 5, 6];
    
    • 创建空数组

      var arr = [];
      
    • 在数组中混合存储数据,存储效率极低,通常把同种类型的数据存储在一个数组中

      var arr = [1, "a", true, undefined];
      
    1. 构造函数创建

      var arr = new Array(1, 2, 3, 4);
      
      var arr = Array(0);
      
    • 如果构造函数创建数组时,仅有一个参数且这个参数是一个大于等于 0 的正整数,这个数就是这个新数组的长度,并且没有元素。如果这个数是负数或者小数,就会报错。

    • 如果是非数值类型,就会将这个元素放在数组的第一位

    1. 对象创建

      var arr = new Object([1, 2, 3, 4]);
      
      console.log(typeof arr);//结果是 object
      
    • 区分对象与数组的方法

      console.log(arr.constructor === Array);
      
      console.log(Array.isArray(arr));
      
    • 仅限 ES6 以后的浏览器支持

      console.log(String(arr) !== "[Object Object]");
      
    • 空数组与其他值的比较

      console.log([] === []); //f
      console.log([] == []); //f
      console.log([] == ""); //t
      console.log([] == 0); //t
      console.log([] == null); //f
      console.log([] == undefined); //f   除 null和undefined以外,undefined与任何值都不相等
      console.log(![] == []); //t    ![]会转换成false,空数组先转换成空字符串,然后转换成布尔值false
      

数组的长度

  • arr.length
    • 长度,数组中元素的个数

    • 数组的长度是一个可读可写的属性

    • 如果数组的长度修改为比原来的长度小,就会把对应多出来的元素删除

      var arr = [1, 2, 3, 4];
      console.log(arr);
      arr.length--; //删除最尾部的一个元素
      console.log(arr);
      arr.length = 0; //清空数组
      console.log(arr);
      arr.length++; //在数组尾部增加一个空元素
      console.log(arr);
      arr.length--;//删除最尾部的一个元素
      arr.length=0;//清空数组中所有元素
      arr.length++;//在数组尾部添加一个空元素
      //数组的长度比数组的最大下标大1
      arr[arr.length]=5;//在数组的尾部增加一个元素5
      //如果数组下标大于数组的长度时,就会在对应的下标位增加元素,中间增加空元素
      
    • 数组使用for循环遍历会将所有下标遍历,不遍历数组的对象属性,但是会遍历到空元素,遍历时,下标都是数字

    • 数组使用for in循环遍历会将所有的可枚举属性遍历,如果该属性没有值,就不遍历,例如数组中下标为空元素的,就不会被遍历,但是数组的对象属性会被遍历,遍历时,下标都会转换成字符串

          for(var i=0;i<arr.length;i++){
              console.log(arr[i]===undefined);
              console.log(i in arr);//可以判断对象中这个key对应的值是否存在,在数组中下标相当于对象中的key,可以判断它的对应值是否存在
          }
          for(var index in arr){
              console.log(arr[index]);
          }
      
          arr.a=10;
          arr.b=20;
          for(var i=0;i<arr.length;i++){
              console.log(arr[i]);//取不到a,b属性的值
          }
          for(var index in arr){
              console.log(arr[index]);//能取到a,b的值
              //使用for in会遍历到数组的属性
          }
      

数组的方法

  • Array.from()
    • 从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
  1. push()
  • 可以在数组尾部添加若干元素,返回新数组的长度
  1. pop()
  • 删除数组尾部的最后一个元素,并将删除的元素返回
    • 删除时可能会出现 3 次 undefined
      • 删除空元素
      • 删除 undefied
      • 数组为空
    • 循环删除数组中的元素
      • 删除到某元素为止
      while (
        arr.pop() != 3 //删除到某元素为止
      )
        console.log(arr);
      
      • 如果数组中存储的是对象,必须将所有的对象引用全部设为 null
      while (arr.length > 0) {
        arr[arr.length - 1] = null;
        arr.pop();
      }
      //下面这种效率更高
      for (var i = 0; i < arr.length; i++) {
        arr[i] = null;
      }
      arr.length = 0;
      
  1. unshift()
  • 在数组头部添加若干元素,并返回新数组的长度
  1. shift()
  • 删除数组头部的一个元素,并返回删除的元素

  • 以上四个方法执行后,原数组的引用关系不变,即都是改变原数组

  1. concat()
  • 数组连接若干个数组或元素,返回一个新的连接完成的数组,与原数组没有引用关系
  1. join()
  • 使用某个字符作为连接符,将所有数组元素连接成为一个字符串并返回这个字符串
  • 如果没有连接符,默认逗号连接
  1. splice()
  • 返回新数组,改变原数组

    • 将数组中从第几位开始删除多少个元素,并返回被删除元素组成的新数组
    • 将数组中从第几位开始删除多少个元素后,在该位置插入新的若干元素,返回删除元素组成的新数组
    • 在数组的第几位开始插入若干个新元素,并返回空的新数组
    var arr1 = arr.splice(1, 2); //将数组从第1位开始删除两个元素,并返回被删除的元素组成的新数组
    console.log(arr1);
    var arr1 = arr.splice(1); //从第1位开始删除到尾部,并删除被删除元素组成的新数组
    console.log(arr1);
    var arr1 = arr.splice(-1); //如果第一个参数是负数,从后向前开始,删除最后一位元素,并返回删除元素组成的数组
    console.log(arr1);
    var arr1 = arr.splice(0); //将一个数组的所有元素转移到另一个数组中
    console.log(arr1);
    var arr1 = arr.splice(); //没有删除,返回空数组
    console.log(arr1);
    var arr1 = arr.splice(-3, 2); //从倒数第3个元素向后删除2个元素,并返回被删除元素组成的新数组
    console.log(arr1);
    
    var arr1 = arr.splice(1, 2, -1, -2); //从第一位开始向后删除两个元素,并在该位置插入-1,和-2,并且返回被删除的两个元素组的数组
    console.log(arr1);
    console.log(arr);
    
    var arr1 = arr.splice(1, arr.length, 0, -1, -2); //从第一位开始删除到尾部,并添加0,-1,-2
    console.log(arr1);
    console.log(arr);
    
    var arr1 = arr.splice(1, 0, 0); //在第一位插入一个元素0,不删除,返回空数组
    console.log(arr1, arr);
    for (var i = 0; i < arr.length; i++) {
      //在i不断增加的过程中,arr.length在不断减小,当arr.length减小到比i小时,循环结束
      arr.pop();
    }
    console.log(arr);
    
    • 数值型数组转数组的方法
    var divs = document.getElementsByTagName("div");
    var arr = Array.from(divs);
    var arr = Array.prototype.slice.call(divs);
    var arr = [].slice.call(divs);
    var arr = Array.prototype.concat.apply([], divs);
    var arr = [].concat.apply([], divs);
    
  1. slice
  • 从第几项开始到第几项结束复制这些元素到新数组中,原数组不改变
var arr1 = arr.slice(0); //复制数组
console.log(arr1);
var arr1 = arr.slice(0); //复制数组
console.log(arr1);
var arr1 = arr.slice(); //复制数组
console.log(arr1);
var arr1 = arr.slice(-1); //从后向前数第一个元素到尾部的所有元素复制到新数组
console.log(arr1);
var arr1 = arr.slice(1, 2); //第一项复制到第二项,不包括结束
console.log(arr1);
  1. reverse()
  • 当前数组按照倒装顺序将原数组颠倒,并返回原数组
  1. sort()
  • 排序(字符规则),返回结果
  • 改变原数组
arr.sort(function (a, b) {
  return a - b; //从小到大排序
});
arr.sort(function (a, b) {
  return b - a; //从大到小排序
});
  1. indexOf(要搜索的元素,从第几个下标开始)
  • 不会改变原数组

  • 在数组中查找元素,并返回该元素的下标,如果没有,返回-1

  1. lastIndexOf(要查找的元素,从什么位置开始查找)
  • 不会改变原数组

  • 从后向前查找元素

  1. fill(要填充的值,从什么位置开始,到什么位置之前结束)
  • 只能用于有长度的数组,如果没有开始位置和结束位置,就会全部填充覆盖
  • 填充对象会造成填充同一个引用地址的对象

数组的遍历

  1. forEach
  • 遍历数组,可以获取使用元素和下标,自动过滤空元素,空元素不遍历
  • 比 for in 来说,不会遍历到数组的属性
  • 比 for 来说,不会遍历到空元素
  • 缺点
    • 函数中 this 指向会被改变
  1. map
  • 遍历数组,使用 return 返回元素,这些被返回的元素会被放在一个新数组中,新数组的长度与原数组相同
  1. some()
  • 判断数组是否有满足条件的元素,如果有直接跳出返回 true,否则返回 false
var bool = arr.some(function (item, index, arr) {
  return item > 4;
});
  1. every()
  • 遍历数组,判断每个元素是否满足条件,如果不满足直接跳出并返回 false,否则返回 true
var bool = arr.every(function (item, index, arr) {
  return item > 0;
});
  1. filter()
  • 筛选,将满足条件的元素放在一个新数组中,并且返回这个新数组
var arr1 = arr.filter(function (item) {
  return item > 4;
});
  1. reduce() 归并
  • 如果没有初始值,value 就是数组的第 0 个下标元素,后面的 item 则从下标为 1 开始遍历

    • 遍历的第二次运行时,value 是上次运行 return 的返回值

    • 当数组的每个元素遍历完成后,将最后一次的返回值,返回到外部变量

      var arr = [1, 2, 9, 12, 5, 56, 7];
      var sum = arr.reduce(function (value, item, index, arr) {
        return (value += item);
      });
      console.log(sum);
      
      var arr = [1, 2, 9, 12, 5, 56, 7];
      var max = arr.reduce(function (value, item, index, arr) {
        return value > item ? value : item;
      });
      console.log(max);
      
      var min = arr.reduce(function (value, item) {
        return value < item ? value : item;
      });
      console.log(min);
      
  • 如果有初始值,value 的初始值就是设置的初始值,遍历将从第 0 项开始,item 从下标为 0 元素开始

    var arr = [1, 2, 9, 12, 5, 56, 7];
    var sum = arr.reduce(function (value, item) {
      return (value += item);
    }, 100);
    console.log(sum);
    
  1. flatMap()
  • 扁平化数组

    • 将二维数组扁平化为一维数组,存储在新数组中,只能在第一层进行

      var arr = [[1, 2, 3],[4, 5, 6],[7, 8, 9]];
      var arr1 = arr.flatMap(function (item) {
        console.log(item);
        return item;
      });
      console.log(arr1);
      
  • 注意

    • 静态方法
      • Array.from()
        • 类的方法,处理一类的事务
    • 实例方法
      • arr.filter()
        • 实例的方法,针对一个实例处理事务