前端基础-JavaScript基础

109 阅读7分钟

值类型和引用类型

  • 值类型:指基本类型,即 number, string, boolean, undefined, null, symbol。它们总是通过值复制的方式赋值和传递值。
  • 引用类型:Object, Array, Date, Regexp, Function。它们总是以引用复制的方式复制和传递值。

手写获取引用类型的函数

方法:利用 Object.prototype.toString.call() 可以获取内部资源的特性,可以变向获取数据的类型。

    function getType(data) {
        let res = Object.prototype.toString.call(data); //获取内部属性
        let index = res.indexOf(' '); //找到第一个空格的下标
        return res.slice(index + 1, -1) //截取
    }

手写深度拷贝

方法:使用 WeapMap 处理循环引用

    function deepClone(obj, cloneObjects = new WeakMap()) {
        //当obj为null或者不是数组或对象的处理
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }

        //如果已经克隆过该对象,直接返回克隆的对象,避免循环引用
        if (cloneObjects.has(obj)) {
            return cloneObjects.get(obj);
        }

        //根据原对象创建数组或对象
        let clone = Array.isArray(obj) ? [] : {};

        //将克隆的对象加入obj的映射中
        cloneObjects.set(obj, clone);

        // 递归克隆原对象的每个属性
        for (let key in obj) {
            //不克隆原对象的原型属性
            if (obj.hasOwnProperty(key)) {
                clone[key] = deepClone(obj[key], cloneObjects);
            }
        }

        return clone;
    }

    //例子
    let obj = { name: '小明', value: { a: 1, b: 2, arr: [0, 1, 2, 3] } };
    let clone = deepClone(obj);
    clone.name = '小红';
    clone.value.b = 10;
    clone.value.arr[1] = 10;
    console.log(obj)
    console.log(clone);

效果:
image.png

JavaScript的作用域和作用域链

作用域 是指在 JavaScript 中变量的可访问性和可见性。也就是说在程序中哪些部分可以访问这个变量,或者变量在哪些部分中是可见的。

作用域的类型

  • 全局作用域
  • 函数作用域
  • 块级作用域

全局作用域

任何不在函数或者大括号里声明的变量,都是在全局作用域下的,全局作用域下声明的变量可以在程序中任何位置访问到。例如:

    let a = 'abc'; // 全局变量
    function fuc() {
        console.log(a);
    }
    fuc(); // 打印 'abc'

函数作用域

函数作用域也叫局部作用域,在函数内部声明的变量都是在函数作用域下的,函数作用域下声明的变量只能在函数内部中访问,函数以外不能访问。例如:

    function fuc() {
        let a = 'abc'; // 函数作用域
        console.log(a);
    }
    fuc(); // 打印 'abc'
    console.log(a); // 报错 Uncaught ReferenceError: a is not defined

块级作用域

在 ES6 中引入了 letconst,与 var 不同,用 letconst 声明的变量在大括号中的 是存在块级作用域中的,在大括号外部不能访问。例子:

    {
        //块级作用域
        let a = 'abc';
        const b = 'bcd';
        var c = 'cde'
        console.log(a); // 打印 'abc'
        console.log(b); // 打印 'bcd'
        console.log(c); // 打印 'cde'
    }

    console.log(c); // 打印 'cde'
    console.log(a); // 报错 Uncaught ReferenceError: a is not defined

上面的代码可以看出,var 在大括号外也可以访问。使用 var 声明的变量不存在块级作用域中。

作用域链

在 js 中要使用到一个变量时,会在当前作用域下寻找该变量,如果没找到,会一层一层的往上找,直到全局变量还是没找到就宣布放弃,这就是作用域链。

    var a = 'abc';
    function f1() {
        var b = 'bcd';
        function f2() {
            var c = 'cde';
            console.log(a); // 自由变量,顺着作用域链向上找到变量 a 打印 'abc'
            console.log(b); // 自由变量,顺着作用域链向上找到变量 b 打印 'bcd'
            console.log(c); // 本作用域的变量 打印 'cde'
        }
        f2();
    }
    f1();

闭包

概念
简单来说,就是能够访问另外一个函数作用域中的变量的函数,例如:

    function fn() {
        let a = 6
        return function () {
            console.log(a)
        }
    }

用途

  • 避免全局变量的污染
  • 储存变量
  • 封装私有变量

特性

  • 函数嵌套函数
  • 函数内部可以引入外部函数的参数和变量
  • 参数和变量不会被 JS 的垃圾回收机制回收

缺点
闭包在会在内存中常驻,容易导致内存增大,如果使用不当,会发生内存泄漏。最好在使用完这个函数或者变量在函数内部用完时,进行销毁。

this 的值

  • this 永远指向一个对象
  • this 的指向完全取决于函数调用的位置

例子:

    function fn() {
        console.log(this);
    }
    var obj = {
        name : 'jason',
        f: fn
    }
    obj.f();  // 运行环境是在 obj 中,this 指向 obj 对象
    fn();   // 运行环境是在全局作用域中,this 指向全局作用域对象 window

this 常用的 5 个场景

  • 事件绑定
  • 构造函数
  • 定时器
  • 函数对象的 call() 方法
  • 函数对象的 apply() 方法

事件绑定中的 this

三种情况:行内绑定、动态绑定、事件监听
行内绑定:

    <!-- 此函数的运行环境在结点对象中,因此this指向当前结点对象 -->
    <input type="button" value="按钮1" onclick="console.log(this)"> 
    
    <input type="button" value="按钮2" onclick="clickFun()">
    function clickFun() {
        console.log(this) // 此函数的运行环境在全局window对象下,因此this指向window;
    }

动态绑定和事件监听:

<input type="button" value="按钮" id="btn">
    var btn = document.getElementById('btn');
    btn.onclick = function () {
        console.log(this); // 该函数执行环境在该节点对象中, this 指向该结点对象
    }

构造函数的 this

function Pro(){
    this.x = '1';
    this.y = function(){};
}
var p = new Pro();

image.png

定时器中的 this

定时器 setInterval 和 setTimeout 中的 this 是指向全局作用域对象 window ,但有时候需要修改this 的指向,常用的方法:

外部保存 this 值

var name = 'window';
  var obj = {
      name: 'obj',
      fn: function () {
          var that = this;
          var timer = null;
          clearInterval(timer);
          timer = setInterval(function () {
              console.log(that.name);   //obj
         }, 1000)
     }
 }

使用箭头函数
箭头函数值没有自己的 this,this 默认继承外部的作用域

var name = 'window';

var obj = { name: 'obj',

    fn: function () {

       var timer = null;

       clearInterval(timer);

      timer = setInterval( () => {

               console.log(this.name);   //obj

      }, 1000)

    }
}

函数对象的 call() apply() 方法

函数作为对象提供了 call() apply() 方法,他们可以调用函数,用于指定本次调用函数时 this 的指向

call() 方法

call方法使用的语法规则
函数名称.call(obj,arg1,arg2...argN);
参数说明:
obj:函数内this要指向的对象,
arg1,arg2...argN :参数列表,参数与参数之间使用一个逗号隔开

    var lisi = { names: 'lisi' };
    var zs = { names: 'zhangsan' };
    function f(age) {
        console.log(this.names);
        console.log(age);

    }
    f(23);//undefined

    //将f函数中的this指向固定到对象zs上;
    f.call(zs, 32);//zhangsan
    f.call(lisi, 32);//lisi

apply() 方法

函数名称.apply(obj,[arg1,arg2...,argN])
参数说明:
obj :this要指向的对象
[arg1,arg2...argN] : 参数列表,要求格式为数组

    var lisi = { names: 'lisi' };
    var zs = { names: 'zhangsan' };
    function f(age, sex) {
        console.log(this.names);
        console.log(age + sex);

    }

    //将f函数中的this指向固定到对象zs上;
    f.apply(zs, [32, '男']);//zhangsan
    f.apply(lisi, [32, '男']);//lisi

Event loop 事件循环、微任务、宏任务

Js 是单线程,执行顺序:先同后异,先微后宏

同步任务

即主线程上的任务,由上到下顺序依次执行,当前任务执行完毕后,才执行下一个任务。

异步任务

不进入主线程,而是进入任务队列的任务,执行完毕后会产生一个回调函数,并通知主线程。在主线程上的任务执行完毕后,就会调取最早通知的回调函数,使其进入主程序中执行。

异步任务又分为微任务和宏任务

微任务:Promise.then()、async/await
宏任务:定时器、ajax、事件绑定

Event loop 事件循环机制

  1. 进入Script标签,第一次循环
  2. 遇到同步代码,立即执行
  3. 遇到宏任务,加入到宏任务队列
  4. 遇到微任务,加入到微任务队列
  5. 执行同步代码
  6. 执行微任务,清空队列
  7. 执行宏任务,清空队列

Promise、async/await、setTimeout/setInterval 执行顺序

    async function async1() {
        console.log('async1 start');
        await async2();
        console.log('asnyc1 end');
    }
    async function async2() {
        console.log('async2');
    }
    console.log('script start');
    setTimeout(() => {
        console.log('setTimeOut');
    }, 0);
    async1();
    new Promise(function (reslove) {
        console.log('promise1');
        reslove();
    }).then(function () {
        console.log('promise2');
    })
    console.log('script end');

    // 同步
    // script start
    // async1 start
    // async2
    // promise1
    // script end

    // 异步
    // 微任务
    // asnyc1 end
    // promise2

    // 宏任务
    // setTimeOut