美文网首页
JavaScript基础

JavaScript基础

作者: 渚清与沙白 | 来源:发表于2023-10-30 17:30 被阅读0次

    数据类型有 StringNumberBooleanNullUndefinedObject
    十六进制:0x 开头
    八进制: 0 开头
    二进制:0b 开头 Chrome与firefox支持
    Infinity: 无穷
    typeof:是一个运算符,用于获取数据类型,其结果类型是一个字符串类型
    instanceof: 检查一个对象是否是一个类的实例
    isNaN():用来判断是否是非数字类型
    NaN: undefined and Number,undefined和数字相加结果是 NaN

    数据类型强制转换

    转换为String
    • 调用toString()方法,nullundefined 类型没有toString()方法
    • 使用String()函数,nullundefined 类型可以被转换成字符串。
      对于Number和Boolean的转换,底层调用的仍是toString()方法,对于null直接转换成“null”;undefined也是如此。
    转换为Number

    一、使用Number()函数

    • 字符串转数字
      1. 如是纯数字,直接转换为数字
      2. 如含有非数字的内容,则转换为 NaN
      3. 如是空串或全是空格字符串,则转换为 0
    • Boolean转数字 true 转为 1false 转为 0
    • null 转数字 值为 0
    • undefined 转数字 值为NaN

    二、字符串转为Number类型

    • parseInt() 可以将字符串中有效的【整数】取出来转换为Number,小数部分不会提取。
    • parseFloat() 可以将字符串中有效的【小数】取出来转换为Number
    • 对于非String类型数据,先将其转换为字符串,再使用该方式进行转换
    <script>
      var a = '100px';
      a = parseInt(a,10);// 100 第二个参数10表示10进制
    </script>
    
    转换成Boolean类型
    • 使用Boolean()函数
      Boolean('') :false
      Boolean(0) :false
      Boolean(null) :false
      Boolean(NaN) :false
      Boolean(undefined) :false

    运算符

    • 对非Number类型的值进行运算,会将这些值转换为Number再做运算
    • 任何值和NaN做运算都是NaN
    • 任何值和字符串做加法运算,都会先转换为字符串,然后再拼接字符串
    • ! 非,对非布尔值进行取反,会将其转换为布尔值,再取反
    • !! 对一个任意数据类型两次取反,将其转换为布尔值,结果就是真实正确的布尔值
    • &&false && alert('是否显示‘) 该语句不会弹出对话框。
      两边的值都是true,则返回后面结果
      两边的值有false,则返回靠前的false
    var res = 5 && 6; // 6
    res = 0 && 2;// 0
    res = 2 && 0;// 0
    res = NaN && 0; // NaN
    res = 0 && NaN;// 0
    
    比较运算
    • 任何值和NaN作比较都是false
    • 符合两侧的值都是字符串类型,不会转换为数字进行比较,会比较Unicode编码
    • 比较字符编码,一位一位进行比较,两位一样,比较下一位 ’abc‘ < 'bcd' 结果是 true
    // 将10转换为了数字类型
    console.log('10000' > + '10')  //  true
    
    // js中使用Unicode编码  前缀必须是\u  , 跟上四位编码
    console.log("\u0054")
    
    // html中使用Unicode编码  前缀必须是&#  , 跟上四位编码的十进制,最后分号结尾
    <h1> 
      &#9760; 
    </h1>
    
    • == 比较两个值,类型不同会自动进行类型转换,再做比较
    var a = 10;
    a == 4  // false
    "1" == 1  // true
    true == "1"  // true
    null == 0  // false
    undefined == null  // true   undefined衍生自null
    
    NaN == NaN  // false  NaN不和任何值相等,包括他本身。  isNaN() 可以来判断是否是NaN
    
    
    • === 全等,判断两个 值是否全等,不会自动做类型转换,类型不同直接 返回false
    • !== 不全等,判断是否不全等,不会自动做类型转换,类型不同直接返回true

    对象

    • 创建:
      1. 构造函数方式创建:var obj = new Object()
      2. 字面量方式创建:var obj = {}
      3. 工厂方法创建:批量创建对象 new Object()
        工厂方法创建对象有一定局限性,类型都是Object,无法区分对象的具体类型
      4. 构造函数方式创建
    // 工厂方法创建
    function createObj(name){
      var obj = new Object();
      obj.name = name
      return obj;
    }
    var a = createObj('A');
    
    // 构造函数创建
    function Person(name){
      this.name = name;
    }
    var per = new Person('A');
    
    • 添加属性: obj.name = "张三"
    • 读取属性: const name = obj.name | const name = obj['name']
    • 修改属性: obj.name = "李四" | obj['name']= "李四"
    • 删除属性: delete obj.name
    1. 通过[]访问属性,[]中可以传递变量。如 var a = 'name'; obj[a] = '你好'
    2. in运算符可以检查一个对象中是否含有指定的属性。如’name‘ in obj,返回true
    3. 对象的属性值可以是任意类型,也可以是函数,函数也可以称为对象的属性,这个函数就是对象的方法
    4. 枚举对象中的属性 for ... in,原型中的属性也会遍历出来
    5. hasOwnProperty() 检查对象自身是否有该属性,原型中的不会查找出来
    6. 查找一个对象的属性,如果没有回返回undefined
    函数
    • 创建 var fun = new Function('console.log('函数传递字符串类型的js代码')')
    • 执行函数中的代码 fun()
      函数也是一个对象,typeof检查类型是function
    • 声明函数
    function 函数名 ([形参...]){
      语句
    }
    

    函数内部可以声明函数,函数没有返回值,获取返回值是undefined

    • 立即执行函数
    // 匿名函数
    (function(){
      console.log('1');
    })()
    
    // 传参
    (function(x, y){
      console.log(x);
      console.log(y);
    })(100, 200)
    

    立即执行函数只会执行一次

    • 函数提升
      函数在声明之前可以被调用,函数表达式不存在提升的现象。函数提升出现在相同作用域中。
      1. 函数提升是把所有函数声明提升到当前作用域的最前面。
      2. 只提升函数的声明,不提升赋值
    fn(); // 函数声明会提升到最前面,这里可以调用函数
    function fn(){
      console.log(1);
    }
    
    fun(); // var 声明的变量会提升,但不赋值,所以此时无法调用函数
    var fun = function(){
      console.log(2);
    }
    
    作用域

    作用域规定了变量能够被访问的范围。作用域分为全局作用域局部作用域,局部作用域分为函数作用域块作用域

    • 全局作用域
      1. 直接编写在script标签中的js代码,都在全局作用域
      2. 全局作用域在页面打开时创建,关闭页面时销毁
      3. 在全局作用域中有一个全局对象window,可以直接使用
      4. 在全局作用域中声明的变量都会作为window对象的属性保存
      5. 在全局作用域中声明的函数都会作为window对象的方法保存
      6. 变量的声明提前:【只有】使用var声明的变量,会在所有代码执行之前被声明但不会赋值
      7. 使用函数声明形式创建的函数会在所有的代码执行之前就被创建。
      8. 使用函数表达式创建的函数不会在所有的代码执行之前创建。
      9. 全局作用域中创建的变量都是全局变量
      10. 将函数定义在全局作用域中,会污染全局作用域的命名空间,而且很不安全,原型可以解决这个问题
    // 函数声明形式创建函数
    // 会在所有代码执行之前创建函数,可以在这里之前调用fun函数
    function fun(){}
    
    // 函数表达式创建函数
    // fun2变量会提前声明,但未赋值,执行到这里才会赋值。所有不能在之前调用fun2函数
    var fun2 = function(){}
    
    • 函数作用域
      1. 调用函数时创建函数作用域,函数执行完毕,函数作用域销毁
      2. 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的。
      3. 函数作用域中可以访问到全局变量
      4. 函数作用域中声明的变量,在全局作用域中无法访问到
      5. 在函数作用域中操作变量,会先在自身作用域中寻找,再向上一级作用域中寻找
      6. 函数作用域中也有声明提前的特性。使用var声明的变量,会在函数中所有的代码执行之前被声明。未赋值,undefined。
      7. 函数声明也会在函数中所有代码执行之前声明
      8. 定义形参相当于在函数作用域中声明了变量
    • 块作用域
      {}包裹的区域就是代码块。代码块被声明的变量外部将有可能无法被访问。如for循环
      1. constlet声明的变量会产生块作用域,var声明的变量不会产生块作用域。
      2. 不同代码块相互之间无法访问
      3. 推荐使用letconst
    for(let i = 0; i< 10; i++){
      // 块用域,外部无法访问变量 i
    }
    for(var i = 0; i< 10; i++){
      // 块用域,外部可以访问变量 i
    }
    
    • 作用域链
      作用域链式在底层变量的查找机制
      1. 优先查找当前作用域
      2. 依次逐级查找父级作用域,直到全局作用域
    this

    解析器在调用函数每次都会向函数内部传递一个隐含的参数this。this指向的是一个对象,这个对象称之为函数执行的上下文对象。根据函数的调用方式不同,this会指向不同的对象。

    • 以函数形式调用(fun()),this指向window
    • 以方法形式调用(obj.fun()),this指向方法调用的那个对象
    • 以构造函数形式调用时,this就是新创建的那个对象
    构造函数

    构造函数调用需要使用new函数名首字母大写,这是与普通函数的区别。
    使用同一个构造函数创建的对象,称之为一类对象,也将一个构造函数称之为

    • 构造函数执行流程:
      1. new时立刻创建一个新的空对象
      2. this指向新的空对象
      3. 执行函数中的代码,修改this,添加新属性
      4. 将新建的对象作为返回值
    • 说明
      实例化没有参数时可以省略小括号
      构造函数无需return
      静态方法中的this指向构造函数,静态方法和静态属性是添加在构造函数身上的,通过构造函数访问。
    function MyClass(){
    }
    function MyClass(name, age){
      this.name = name; // this指向构造函数内创建的对象
      this.age = age;
    
      // 不推荐
      this.say = function(){} // 存在浪费内存问题,可以使用原型,实例对象都可以调用
    }
    
    // 静态属性和静态方法
    MyClass.name = '';
    MyClass.say = function say(){}
    
    // 访问静态属性和方法
    const name = `${MyClass.name}你好`
    MyClass.say();
    

    原型 prototype

    1. 创建的每一个函数,解析器都会向函数中添加一个属性prototype。这个属性对应着一个对象,这个对象就是原型对象
    2. 如果函数作为普通函数调用,prototype没有任何作用
    3. 当函数以构造函数调用时,它所创建的对象都会有一个隐含的属性指向该构造函数的原型对象,可以通过proto来访问该属性
    4. 原型对象相当于一个公共的区域,所有的实例都可以访问到这个原型对象,可以将对象中共有的属性和方法统一设置到原型对象中
    5. 当访问对象的一个属性或方法时,会先从对象自身中寻找,再去对象原型中寻找,直到原型的原型对象去找
    6. 原型对象也是对象,它也有原型;Object对象的原型没有原型。
    function MyClass(){
    
    }
    // 向MyClass的原型中添加属性name
    MyClass.prototype.name = 'A';
    // 向MyClass的原型中添加方法setName
    MyClass.prototype.setName = function setName(name){
      console.log(name);
    };
    
    var mc = new MyClass();
    console.log( mc.__proto__ == MyClass.prototype ); // true
    
    image.png
    • 声明一个构造函数
    function User{
      this.name = 'zs',
      this.age = 19,
    }
    
    • 创建一个Use实例对象
      const user = new User()
      显式原型属性:User.prototype 只有函数才有显式原型属性
      隐式原型属性:user.__proto__ 实例对象拥有隐式原型属性
      User.prototypeuser.__proto__都指向原型对象,通过显式原型属性User.prototype操作原型对象,可以追加属性;通过实例对象隐式原型链查找,可以访问到追加的属性。
    // 给原型对象追加sex属性
    User.prototype.sex = '男';
    // 实例对象访问追加的sex属性
    const sex = user.sex;
    
    • 实例的隐式原型属性永远指向自己缔造者的原型对象
    • 原型对象prototype的属性constructor
    function MyClass(){
    }
    const res = MyClass.prototype.constructor === MyClass; // true
    
    // 覆盖原有的原型对象,会失去constructor
    MyClass.prototype = {
      constructor: MyClass, // constructor指回构造函数,否则prototype就失去了constructor
      function fn(){},
      function fun(){},
      function func(){},
    }
    
    对象原型

    对象原型是实例对象身上的属性,这是一个只读属性。
    对象原型中也存在一个constructor,指向创建实例对象的构造函数
    对象原型指向该构造函数的原型对象

    构造函数、原型对象和对象原型的关系.jpg
    function MyClass(){
    }
    const cls = new MyClass();
    const res = MyClass.prototype === cls.__proto__; // true
     
    
    原型链

    原型链其实就是一个查找规则。使用一些属性和方法时,去查找这些属性和方法的规则。

    原型链.jpg
    原型继承

    通过原型对象来实现继承

    // 父级对象
    function Parent() {
      this.a = 1;
      this.b = 2;
    }
    // 子级对象
    function Child(){ 
    }
    // 通过Child的原型对象来继承Parent对象
    Child.prototype = new Parent();
    // 指回原来的构造函数
    Child.prototype.constructor = Child;
    
    数组

    数组是一个对象,typeof的结果是object。
    读取不存在的索引,不会报错,而是返回undefined

    • 创建数组对象
      构造函数形式创建:var arr = new Array(10,20,11)
      字面量形式创建:var arr = [];
    • 数组属性 length
      用于[设置][获取]数组长度
      连续的数组获取到的长度是数组的长度,也是元素的个数
      非连续的数组获取的长度是数组的长度,不是元素的个数

    可以通过修改 length属性来删除一些元素,数组元素可以是任意类型的数据,包括函数、对象、数组。

    • 数组方法

      1. push() 向数组末尾添加一个或多个元素,返回数组新的长度
      2. pop() 删除数组最后一个元素
      3. unshift() 向数组开头添加一个或多个数组,返回新长度
      4. shift() 删除数组第一个元素,并返回被删除的元素
      5. forEach() 循环遍历,支持IE8以上,参数是函数
      6. slice(start, end) 提取指定范围的元素 传递正负值参数截取顺序不一样
      7. splice(start, delCount, newElement...) 删除、添加新元素,会影响原数组
      8. contact() 连接两个或多个数组、元素,返回新数组 不会影响原数组
      9. join() 将数组转换为一个字符串 不会影响原数组
      10. reverse() 反转数组 会影响原数组
      11. sort() 默认按照Unicode编码排序 会影响原数组
      12. valueOf() 返回数组的原始值
      13. map() 迭代数组,返回新数组,forEach不会返回新数组
      14. filter() 过滤数组,返回新数组
      15. reduce() 累计器 常用于求和
      16. find() 查找符合条件的第一个元素
      17. findIndex() 查找符合条件的第一个元素索引值
      18. every() 检查所有元素是否符合指定条件 返回 true | false
      19. some() 检查数组是否有元素符合指定条件 有则返回 true
      20. fill() 将一个固定值替换数组的元素
      21. ......
    • Array静态方法

      1. from() 可迭代或类数组对象创建一个新的浅拷贝的数组实例
      2. isArray() 确定传递的值是否是一个数组
      3. of() 通过可变数量的参数创建一个新的 Array 实例,而不考虑参数的数量或类型
    回调函数需要定义2个形参,第一个形参肯定在第二个形参的前边
    如果返回一个大于0的值,元素会交换位置
    如果返回一个小于0的值。则元素位置不变
    如果返回一个0,则认为两个元素相等,不交换位置
    升序:a - b
    降序: b - a
    // 排序
    aar.sort((a, b)=>{
      return a - b;  // 升序
    })
    
    函数对象的方法

    需要通过函数对象来调用,调用call()apply()都会调用函数执行。
    在调用这两个方法可以将一个对象指定为第一个参数,函数中的this就是传递的这个参数

    1. call(this指向的对象, 参数) 用于调用函数,可以改变this的指向
    2. apply(this指向的对象, 数组参数) 与call一样,区别在于传参必须是一个数组
    3. bind(this指向的对象, 参数) 语法与call相似,可以改变this指向,区别在于不会调用函数
    <script>
      function fun(){
        alert(this); // this就是obj,如果没有传递obj ,this则是window
      }
      var obj = {};
      // 使用 call 调用 fun 函数
      fun.call(obj);
       // 使用 apply 调用 fun 函数
      fun.apply(obj);
    
      // call 和 apply的方式调用 fun函数 并传参
      function fun(a, b){
        console.log(a, b) // 2, 3
      }
      var obj = {};
      fun.call(obj, 2, 3);  
      fun.apply(obj, [2, 3]); // 以数组形式传参
      
      // apply使用场景 求数组最大值
      const max = Math.max(1, 2, 3);
      const max = Math.max(...arr); // 展开运算符
      const max = Math.max.apply(null, [1,2,3]); // this 指向 null
      const max = Math.max.apply(Math, [1,2,3]); // this 指向 Math
    
      // 3 bind() 不会调用函数,但可以改变this指向,返回值是一个新的函数
      const f = fun.bind(obj); // 会拷贝一份fun 函数,但是this指向了obj
      f(); // 调用新函数
      
      // bind() 使用场景
      btn.addEventListener('click',function(){
        this.disabled = true; // this指向 btn
        setTimeout(function(){
          this.disabled = false; 
        }.bind(this), 2000); // this指向 btn
      });
      // 箭头函数
      btn.addEventListener('click',function(){
        this.disabled = true; // this指向 btn
        setTimeout(()=>{
          this.disabled = false; // this指向 btn
        }, 2000); 
      });
    
    </script>
    
    • this指向问题
      以函数形式调用,this是window
      以方法调用时,this是调用方法的对象
      以构造函数的形式调用时,this是新创建的那个对象
      使用 call 和 apply 调用是,this是指定的那个对象
    Date对象
    <script>
       // 当前时间
      var d = new Date();
    
      //创建指定时间
      var d2 = new Data("12/21/2023 14:00:00");
      // 获取一个月中的第几天 1 - 31
      var date = d2.getDate();
      // 获取一周中的第几天  0 - 6
      var day = d2.getDay();
      // 获取月份  0 - 11
      var month = d2.getMonth();
      // 获取年份
      var year = d2.getFullYear();
      // 获取小时 0 - 23
      var hour = d2.getHours();
      // 获取分钟 0 - 59
      var minute = d2.getMinutes();
      // 获取秒 0 - 59
      var second = d2.getSeconds();
      // 获取毫秒数  0 - 999
      var mill = d2.getMilliseconds();
      // 获取当前时间对象的时间戳
      var time = d2.getTime();
      // 获取当前时间戳
      time = Date.now();
      
    </script>
    
    包装类

    JS提供了三个包装类可以将基本数据类型转换为对象

    • String()
    • Number()
    • Boolean()
      当对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后再调用对象的属性和方法
    var num = 123;
    // 先将值使用包装类转换为对象,再调用对象的toString()方法
    num = num.toString(); 
    var type = typeof num; // String
    
    • String()
      在底层字符串是以字符数组的形式保存的
    var str = "name";
    // 字符串长度
    str.length
    // 根据索引获取指定字符
    str.charAt(0)
    // 根据索引获取指定字符的Unicode编码
    str.charCodeAt(0) 
    // 根据字符编码获取字符
    String.fromCharCode(72)  // H
    // 连接多个字符串
    str.concat("张","三")
    // 检索字符串是否含有指定内容,返回第一次出现的索引位置。第二个参数是指定查找的开始位置
    str.indexOf('e',1); // 3 
    // 从末尾开始查找  返回的index是从前开始数,第二个参数指定从前开始查找,找到指定位置结束
    str.lastIndexOf(’e‘,3)
    // 截取指定范围的字符串  参数可以为负数
    str.slice(0,2)
    // 截取字符串  参数不可为负数
    str.substring(0, 2)
    // 截取字符串  1参:开始位置   2参:长度
    str.substr(0, 2)
    // 拆分字符串
    str.split()
    // 转小写  不影响源数据
    str.toLowerCase()
    // 转大写  不影响源数据
    str.toUpperCase()
    
    正则表达式
    • 作用

      1. 检查字符串是否符合规则
      2. 提取符合规则的字符串
    • 正则对象的方法

      1. test() 查找指定值,返回布尔值
      2. exec() 查找指定值,返回找到的值,并确定其位置
      3. compile() 编译正则表达式
    • 支持正则的String对象的方法

      1. search() 查找与正则匹配的值 只会查找第一个,即便设置全局匹配也无效
      2. match() 查找一个或多个与正则的匹配 返回数组
      3. replace() 替换与正则匹配的子串
      4. split() 字符串分割为数组 不指定全局匹配也会全局进行拆分
    • 创建

    // 函数形式创建
    /*
    *语法:
    *  RegExp(’正则表达式‘, '匹配模式')
    *  匹配模式:
    *     i: 忽略大小写
    *     g: 全局匹配
    *      可以为一个正则设置多种匹配模式  顺序无关   /a/ig 或 /a/gi
    */
    var reg = new RegExp('a','i');
    
    /*
    * 字面量创建
    *  语法:
    *    /正则表达式/匹配模式
    */
    reg = /a/i; // 匹配a 忽略大小写
    
    // 或:|
    reg = /a|b|c/; // 匹配a或b或c
    /*
    *  或:[]
    *  [a-z]  任意小写字母
    *  [A-Z] 任意大写字母
    *  [A-z]  任意字母
    *  [0-9]  任意数字
    */
    reg = /[a-z]/
    // 检查是否含有 abc 或 adc 或 aec
    reg = /a[bde]c/
    
    /*
    * [^ ] 排除
    */
    reg = /[^ab]/; // 除了a, b都满足
    
    /*
    *  量词:通过量词设置一个内容出现的次数
    *  只对前面的一个内容起作用
    *  次数:{n}, {m, n},{m,}
    *  至少1个:    +   相当于{1,}
    *  0个或多个:   *   相当于{0,}
    *  0个或1个:    ?   相当于{0,1}
    */
    reg = /a{3}/; // 匹配连续出现3次的a
    reg = /(ab){3}/; // 匹配连续出现3次的ab
    reg = /ab{1,3}c/; // 出现1 到 3次 的b
    reg = /ab{3,}c/; // 3次以上
    reg = /ab+c/; // 至少一个b
    reg = /ab*c/; // 0个或多个b
    
    /*
    *  检查一个字符是否以 xx 开头
    *  ^  表示开头
    *  $  表示结尾
    */
    reg = /^a/; //  匹配开头的a
    reg = /a$/; //  匹配结尾的a
    // 如果同时使用 ^和$,则要求字符串必须完全符合正则
    reg = /^a$/; //  匹配开头的a,同时也是结尾的a
    reg = /^a|a$/;  // 以a开头或者以a结尾
    
    //手机号正则
    reg = /^1[3-9][0-9]{9}$/;
    
    /*
    *  .  表示任意字符
    *  \  转义字符
    *  \\  表示 \
    *  注意:使用构造函数创建正则时,他的参数是一个字符串,而\是字符串中的转义字符,
    *     如果使用\则需要使用\\来代替
    */
    reg = /./;  //  匹配任意字符
    reg = /\./;  //  匹配 .
    reg = new RegExp("\\.");  //  需要写2个\
    
    /*
    *  \w  任意字母、数字、_  [A-z0-9_]
    *  \W  除了字母、数字、_  [^A-z0-9_]
    *  \d  任意的数字  [0-9]
    *  \D  除了数字  [^0-9]
    *  \s  空格
    *  \S  除了空格
    *  \b  单词边界
    *  \B  除了单词边界
    */
    // 检查是否有child单词
    reg = /\bchild\b/;
    str = str.replace(/^\s*/,"");  //  去除开头的空格
    str = str.replace(/\s*$/,"");  //  去除末尾的空格
    str = str.replace(/^\s*|\s*$/g,"");  //  去除开头或结尾的空格
    
    /*
    *  电子邮件
    *    hello            .  world            @  abc         .  com.cn
    *    任意字母数字下划线  .  任意字母数字下划线  @  任意字母数字  .  任意字母
    *    \w{3,}    (\.\w+)*  @  [A-z0-9]+  (\.[A-z]{2,5}){1,2}
    */
    reg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/;
    

    垃圾回收机制

    全局变量在页面关闭时回收,局部变量使用完毕自动回收

    • 内存生命周期

      1. 内存分配
      2. 内存使用
      3. 内存回收
    • 内存泄漏
      程序中分配的内存因某种原因程序未释放无法释放叫做内存泄漏

    • 垃圾回收算法
      堆栈空间分配区别

      1. 堆:一般由程序员分配释放,如不释放,则由垃圾回收机制回收,复杂数据放到堆中。
      2. 栈:由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈中。

      浏览器垃圾回收机制算法

      1. 引用计数法
        IE采用的算法,定义‘内存不再使用’。就是看一个对象是否有指向它的引用,没有就回收。
        算法:
        (1)跟踪被引用的次数
        (2)被引用一次就记录1次,多次引用就累加
        (3)减少一个引用,就减一次
        (4)引用次数为0,则释放内存
        缺点:
        嵌套引用,两个对象相互引用,无法进行回收,导致内存泄漏。
      2. 标记清除法
        现代浏览器都是用该算法。
        算法:
        (1)将不再使用的对象标记为无法达到的对象
        (2)从根部出发定时扫描内存中的对象,从根部可达就是还需要使用的对象,不可达对象标记为不再使用的对象,稍后进行回收。
    闭包

    闭包 = 内层函数 + 外层函数的变量

    // 1. 基本形式
    
    // 外层函数
    function fun(){
      let a = 1; // 外层函数的变量
      // 内层函数
      function fun2(){
        console.log(a); // 内层函数使用了外部函数的变量
      }
      fun2(); // 必须要调用,外部无法访问 fun2 函数
    }
    fun(); // 必须要调用
    
    // 2. 常见形式
    function fun(){
      let a = 1; 
      // 内层函数
      function fun2(){
        console.log(a); 
      }
     return fun2; // 返回一个函数
    }
    const fn = fun(); // 接收这个函数
    fn(); // 调用这个函数
    

    作用:支持外部访问函数内部的变量
    应用:实现数据的私有
    闭包中外层函数的变量不会被回收,应该被回收但是没有被回收,闭包存在内存泄漏的风险。

    函数参数

    函数参数分为动态参数和剩余参数

    动态参数

    arguments

    arguments参数仅仅存在于函数中。
    调用函数时,浏览器都会传递两个隐含的参数,一个是this,一个是arguments
    arguments是一个类数组对象,但并不是数组,用来封装实参。
    Array.isArray(arguments) : false
    arguments instanceof Array : false
    在调用函数传递的实参都会在arguments中保存
    arguments的length就是实参的长度,通过下标索引来获取实参
    不定义形参,也可以通过arguments来获取实参
    属性callee对应一个函数对象,就是当前正在执行的函数的对象

    <script>
      function fun(a, b){
        console.log(arguments instanceof Array);
        console.log(Array.isArray(arguments));
        console.log(arguments[0]);
        console.log(arguments.length);
        console.log(arguments.callee);
      }
    </script>
    
    剩余参数

    将不定数的参数表示为一个数组
    语法符号:...
    放于最末函数形参之前,用于获取多余的实参,获取到的实参是个真数组

    function fun(a, b, ...num){
      console.log(num); // 真数组
       // 展开运算符
       // 求最值
      const max = Math.max(...num);
      const min = Math.min(...num);
      // 合并数组
      const arr1 = [1,2];
      const arr2 = [3,4];
      const arr = [...arr1, ...arr2];
    }
    

    Object常用静态方法

    Object.keys():获取对象所有属性名
    Object.values():获取对象所有属性值
    Object.assign():对象拷贝,应用场景-给对象添加属性

    const obj = {name: 'A', age: 10}
    // 获取属性名
    const arr = Object.keys(obj)
    
    // 获取属性值
    const values = Object.values(obj)
    
    // 拷贝对象
    const obj2 = {};
    Object.assign(obj2, obj);
    // 给obj对象添加gender属性
    Object.assign(obj, {gender: 1});
    
    深浅拷贝
    • 浅拷贝
      浅拷贝拷贝的是地址,只拷贝最外面一层,嵌套层级不会拷贝
      1. 拷贝对象
        Object.assign() 或展开运算符
      2. 拷贝数组
        Array.prototype.concat或展开运算符
    const obj = {
      name:' ',
      bean: {
        age: 10,
      }
    }
    const o = {};
    Object.assign(o, obj);// 将bean对象的地址拷贝给o对象,两个对象的bean都是同一个对象
    o.bean.age = 100; // 修改拷贝后的对象属性值
    o.name = 'red';
    console.log(obj.bean.age); // 100
    console.log(obj.name); // ''
    
    • 深拷贝
      1. 通过函数递归实现
      2. lodash/cloneDeep
      3. JSON.stringify()
    // 1 递归深拷贝
    function deepCopy(newObj, oldObj){
      for(let k in oldObj){
        if(oldObj[k] instanceof Array){
          newObj[k] = [];
          deepCopy(newObj[k], oldObj[k]);
        }else if(oldObj[k] instanceof Object){
          newObj[k] = {};
          deepCopy(newObj[k], oldObj[k]);
        }else{
          newObj[k] = oldObj[k];
        }
      }
    }
    
    // 2 lodash / cloneDeep
    const newObj = _.cloneDeep(oldObj)
    
    // 3 JSON. stringify()
    const newObj = JSON.parse(JSON.stringify(oldObj));
    
    异常处理
    • 抛异常throw

    • 捕获异常try...catch

    // 抛出异常
    function fn(a){
      if(!a){
        throw new Error('参数有误');
      }
      return a.toString();
    }
    
    // 捕获异常
    try{
      ...
    }catch(e){
       throw new Error('捕获到异常');
    }finally{
      // finally 这里一定会执行
    }
    
    
    防抖与节流

    防抖:单位时间内触发重新计时,单位时间到了才会执行
    节流:单位时间内只执行一次

    • lodash/debounce(func, [wait=0], [option=]) 防抖
    • lodash/throttle(func, [wait=0], [option=]) 节流

    延迟一段时间再调用函数,底层是利用定时器来实现

    // setTimeout定时器实现防抖
    
    function debounce(fn, t){
      let timer = null;
      // 需要返回一个函数,作为事件的处理函数
      return function(){
        if(timer) clearTimeout(timer);
        timer = setTimeout(function(){
          fn();
        }, t)
      }
    }
    
    // setTimeout实现节流
    function throttle(fn, t){
      let timer = null;
      return function(){
        if(!timer){
          timer = setTimeout(function(){
            fn();
            timer = null; // 在定时器回调函数中是无法清除定时
          }, t);
        }
      }
    }
    

    相关文章

      网友评论

          本文标题:JavaScript基础

          本文链接:https://www.haomeiwen.com/subject/vvesidtx.html