JavaScript 基础理解二

3 阅读10分钟

浅浅的梳理下,变量就是用来存储和使用的。方法是包含许多指令,来完成某一项任务或行为。

面向过程和面向对象,个人将面向对象进行拆分理解,“面向”这两个字就是转换思路,面对解决不同的事情,要用对应的办法。对象就是当前的事物,事物已对象形式,包含属性(特征)和行为(动作)。

面向对象和 “面向过程”的核心区别,就是前者先想 “有什么事物(对象)”,再想 “这个事物能做什么”;后者先想 “要做哪些步骤”,再一步步实现。

面向过程适合单一、独立、步骤化的小功能,面向对象适合关联、需要整合 / 复用的大模块。

  • 面向过程:就像 “只实现汽车刹车这一个功能”—— 你只需要关注 “踩刹车→切断动力→刹车片夹紧→车速降为 0” 这几个步骤,按顺序写代码就行,不用管汽车的品牌、颜色这些无关属性。它的核心是 “按步骤做事”,适合单一、独立、步骤明确的小功能。

  • 面向对象:就像 “开发整个汽车控制系统”—— 你需要统筹汽车的所有属性(品牌、速度、油量)和所有行为(启动、加速、刹车、鸣笛),把这些都封装在 “汽车” 这个对象里

面向对象就是把计算机里抽象的 “数据” 和 “操作”,包装成我们能直观理解的 “现实事物模型”,更好理解的去进行编程实现代码逻辑。

函数

函数声明: function A () , 单独声明,不赋值给变量,有提升特性

函数表达式: var A = function() ,把匿名函数赋值给变量A,这里的函数是匿名函数(没有名字)。或者 var A = function fn() , 把名为fn的函数赋值给A,这里就不是匿名函数了.

这种写法的意义:函数名fn仅在函数内部可用,方便递归调用(比如阶乘函数),但外部依然用变量名调用。

// 调用时用变量名,而非函数名
fn console.log(multiply(2, 3)); // 输出:6 
console.log(fn(2, 3)); // 报错!fn是函数内部的局部名称,外部访问不到

箭头函数(匿名函数表达式的简化) const divide = (a, b) => a / b;

特征函数表达式(匿名 / 具名)函数声明
核心形式函数作为值赋值给变量单独声明函数
提升特性变量提升(值为 undefined)函数整体提升
命名可匿名 / 具名必须有名字
适用场景按需定义、作为参数传递全局 / 局部通用函数

document对象

通过使用document对象可以访问和修改用页面上的内容,进行交互式相应。

document对象下方的每一个方块被称为节点,每一个节点都是对象形式,即每个元素都会创建各自的节点(一种对象)

console.log(document) 这是看不到像普通 JS 对象那样的 “键值对” 格式,而是显示成页面 DOM 结构的可视化形式,这并不是因为 document 不是对象,而是浏览器对这类特殊对象做了可视化优化document 本质上是一个对象(更准确地说是 DOM 对象 / 宿主对象),只是浏览器控制台为了方便你调试页面结构,把它渲染成了 DOM 树的可视化格式,而非普通 JS 对象的键值对格式。

但可以通过console.dir(document)标准对象键值对格式查看 DOM 对象的所有属性 / 方法。

加分号

计算机是一步一步执行指令的,每一条单独的指令或者步骤都被称为一条语句,所以每一条语句用分号结束,让代码更容易阅读,分号同时也告知解析器这个步骤执行完毕,应当开始执行下一条语句。

表达式

常见表达式类型:字面量、算术、赋值、函数调用、成员访问、逻辑、三元表达式等,都是日常开发中高频使用的。

断标准:表达式有 “计算结果”,语句是 “执行动作”

表达式类型示例计算结果(示例)
字面量表达式123"hello"true字面量本身(123"hello"
算术表达式a+b10*5x/2计算后的数值(如10*5=50
赋值表达式e = 1s = a+b被赋的值(如e = 1结果是1
函数调用表达式Math.max(1,2)alert()函数的返回值(如Math.max(1,2)=2
成员访问表达式document.titlearr[0]访问到的属性 / 元素值
逻辑表达式a && bx > 5布尔值(如3>5=false
三元表达式age >= 18 ? "成年" : "未成年"条件成立的结果

对象

对象中的属性,其实可以看成一个变量来理解,隶属于某个对象的变量,但这个 “变量” 的 “使用范围” 完全依附于所属的对象 —— 只有通过这个对象(或对象的实例)才能访问 / 修改它,脱离了这个对象,就无法直接使用这个属性。

修改对象的基本操作:

对象名.属性名 = 属性值

对象名['属性名'] = 属性值

删除对象的基本操作:

delete 对象名.属性名

this

this是一个关键字,通常在函数内部或者对象内部使用,通常是指向当前函数所操作的对象。this取什么样的值是在调用时,而非定义时。

    var width  = 600
    var shape  = {width:300}
    
    var showwidth = function(){
        document.write(this.width)
    }
    
    shape.getWidth = showwidth;
    shape.getWidth()
    
    输出的是300 , 这里this指向当前调用的对象,而不是全局上下文。
    

对象中顺序不是很重要,因为是通过键访问每一个数据,使用点语法。

数组也是一种特殊类型的对象,存储一组相关的数据。

微任务和宏任务

js是一个单线程,同一时间只能做一件事情。如果操作DOM是多线程,浏览器无法以哪个线程为主,造成页面操作错乱。

但为了不被耗时操作(比如网络请求、定时器)阻塞,就设计了任务队列机制

  • 同步代码:直接在主线程执行,执行完才会处理异步任务;

  • 异步任务:分成宏任务微任务两类,放进对应的队列,等同步代码执行完后再按规则执行。

所以微任务和宏任务出现在异步任务中,宏任务是 “粗粒度” 的异步任务,执行优先级更低,每次执行一个宏任务后,会先清空所有微任务,再执行下一个宏任务。

常见的宏任务类型

  • setTimeoutsetIntervalsetImmediate(Node 环境)

  • requestAnimationFrame(浏览器环境)

  • I/O 操作(比如文件读写、网络请求)

  • 页面渲染(浏览器环境)

  • script 标签中的整体代码

微任务执行优先级更高,所有微任务会在当前宏任务执行完、下一个宏任务执行前全部执行完毕

常见的微任务类型

  • Promise.then()Promise.catch()Promise.finally()

  • async/await(本质是 Promise 的语法糖,await 后的代码会进入微任务)

  • queueMicrotask()(手动创建微任务)

  • MutationObserver(浏览器环境)

    // 同步代码(第一个宏任务的开头)
    console.log('1. 同步代码');
    
    // 宏任务:setTimeout
    setTimeout(() => {
      console.log('4. 宏任务 - setTimeout');
      // 宏任务内的微任务
      Promise.resolve().then(() => {
        console.log('5. 宏任务内的微任务 - Promise.then');
      });
    }, 0);
    
    // 微任务:Promise.then
    Promise.resolve().then(() => {
      console.log('3. 微任务 - Promise.then');
      // 微任务内嵌套微任务,仍会在本轮微任务阶段执行
      queueMicrotask(() => {
        console.log('3.1 嵌套微任务 - queueMicrotask');
      });
    });
    
    // 同步代码
    console.log('2. 同步代码');
    
    
    1. 同步代码
    2. 同步代码 
    3. 微任务 - Promise.then 
    4. 嵌套微任务 - queueMicrotask 
    5. 宏任务 - setTimeout 
    6. 宏任务内的微任务 - Promise.then
    
  • 先执行所有同步代码,输出 12

  • 同步代码执行完,清空微任务队列:先执行 Promise.then 输出 3,再执行嵌套的 queueMicrotask 输出 3.1

  • 微任务队列清空后,执行下一个宏任务(setTimeout),输出 4

  • 这个宏任务执行完,又清空它内部的微任务队列,执行 Promise.then 输出 5

async/await 与微任务的关系

async/await 是 Promise 的语法糖,await 后面的代码会被包裹成 Promise.then(微任务):

async function test() {
  console.log('1. async 同步代码');
  await Promise.resolve(); // 此处暂停,后面的代码进入微任务
  console.log('3. await 后的微任务');
}

console.log('0. 最外层同步代码');
test();
console.log('2. 最外层同步代码');

0. 最外层同步代码 
1. async 同步代码 
2. 最外层同步代码 
3. await 后的微任务

this

this 的指向不是在定义时确定,而是在调用时确定—— 谁调用函数,this 就指向谁。


    function fn1(){
        console.log(this)
    
    }
    
    fn1() //window
    
    fn1.call({x:100})  // {x:100}
    
    const fn2 = fn1.bind({x:200})

    fn2()  //{x:200}

new 调用(构造函数)→ this 指向新实例

// 构造函数
function Person(name) {
  this.name = name; // this 指向 new 出来的实例
  console.log(this); // 输出:Person { name: '张三' }
}

// new 调用
const p1 = new Person('张三');
console.log(p1.name); // 输出:张三(this 绑定到 p1)


call/apply/bind 调用 → this 指向指定对象

通过 call/apply/bind 显式绑定 this

  • call/apply:立即执行函数,第一个参数是 this 指向的对象;

  • bind:返回一个新函数,永久绑定 this,不会被覆盖(除非用 new

    function sayName() {
      console.log(this.name);
    }
    
    const obj1 = { name: '李四' };
    const obj2 = { name: '王五' };
    
    // call 绑定 this 到 obj1
    sayName.call(obj1); // 输出:李四
    
    // apply 绑定 this 到 obj2(和 call 唯一区别:参数传递方式不同)
    sayName.apply(obj2); // 输出:王五
    
    // bind 绑定 this 到 obj1,返回新函数
    const fn = sayName.bind(obj1);
    fn(); // 输出:李四
    fn.call(obj2); // 仍输出:李四(bind 绑定后不会被 call 覆盖)
    

对象方法调用 → this 指向调用对象

当函数作为对象的方法调用时,this 指向调用这个方法的对象

普通函数调用 → this 指向全局对象

当函数既不是 new 调用、也不是显式绑定、也不是对象方法调用时,就是 “普通调用”:

  • 浏览器环境:this 指向 window

  • Node 环境:this 指向 global

  • 严格模式(use strict):this 指向 undefined(避免污染全局)。

箭头函数 → 无自己的 this,继承外层作用域的 this

箭头函数是特例:没有自己的 this,它的 this 是定义时外层作用域的 this

  • 箭头函数不能用 new 调用(没有 constructor);

  • 箭头函数不能用 call/apply/bind 修改 this

    const obj = {
      name: '孙八',
      // 普通方法:this 指向 obj
      normalFn: function() {
        console.log(this.name); // 输出:孙八
      },
      // 箭头函数:this 继承外层(这里外层是全局,所以指向 window)
      arrowFn: () => {
        console.log(this.name); // 输出:undefined(window 没有 name)
      },
      // 解决回调函数 this 问题(常用)
      fixCallback: function() {
        // 箭头函数继承外层的 this(即 obj)
        setTimeout(() => {
          console.log(this.name); // 输出:孙八
        }, 0);
      }
    };
    
    obj.normalFn();
    obj.arrowFn();
    obj.fixCallback();
    

DOM 事件处理函数 → this 指向触发事件的元素

在浏览器中,DOM 事件绑定的函数里,this 指向触发事件的 DOM 元素

const btn = document.getElementById('btn');
btn.onclick = function() {
  console.log(this); // 输出:<button id="btn">点击</button>
  this.style.color = 'red'; // 按钮文字变红
};

特殊

const zhangsan = {

    name:'张三',
    sayHi(){
        console.log(this) //当前对象 zhangsan
    },
    wait(){
        //setTimeout中有个函数,这个函数this指向是window,
        //这里的触发执行是setTimeout触发的,而不是zhangsan对象触发执行
        setTimeout(function (){
            
            console.log(this) //window
        })
    },
    
    waitAgain(){
        //setTimeout中的函数是箭头函数,虽然是setTimeout触发,但是箭头函数取值是上级作用域的值
        //箭头函数不会创建自己的 `this` 上下文,是继承的是外层作用域
        //这里箭头函数的外层作用域是 `waitAgain` 方法的作用域,`this` 指向 `zhangsan`
        setTimeout(()=>{
            
            console.log(this) //当前对象
        })
    }

}



// 箭头函数写法

waitAgain() {
  setTimeout(() => {
    console.log(this); // zhangsan
  });
}

// 等价于传统保存 that 写法
waitAgain() {
  const that = this;
  setTimeout(function () {
    console.log(that); // zhangsan
  });
}
调用场景this 指向优先级
new 调用(构造函数)新创建的实例对象最高
call/apply/bind 调用显式指定的对象次高
对象方法调用(obj.fn ())调用方法的对象(obj)中等
普通函数调用(fn ())全局对象(window/global)/ 严格模式 undefined较低
箭头函数定义时外层作用域的 this特殊
DOM 事件函数触发事件的 DOM 元素特殊
  • this 指向 “调用者”,调用时才确定,不是定义时;

  • 优先级:new > call/apply/bind > 对象方法 > 普通调用;

  • 箭头函数无自己的 this,继承外层的 this,无法被修改