首頁  >  文章  >  web前端  >  詳解JS的三座大山:作用域與閉包、原型與原型鏈、非同步與單線程

詳解JS的三座大山:作用域與閉包、原型與原型鏈、非同步與單線程

青灯夜游
青灯夜游轉載
2023-04-18 17:16:511071瀏覽

js 作為前端的中堅。那麼 javascript 三座大山,你知道是哪些呢?

1️⃣ 作用域與閉包

作用域 指程式碼當前上下文,控制變數和函數的可見性和生命週期。最大的作用是隔離變量,不同作用域下同名變數不會衝突。

作用域鏈 指如果在目前作用域中沒有查到值,就會向上級作用域查詢,直到全域作用域,這樣一個尋找過程所形成的鏈條就稱為作用域鏈。 【推薦學習:javascript影片教學

作用域可以堆疊成層次結構,子作用域可以存取父作用域,反之則不行。

作用域具體可細分為四種:全域作用域模組作用域##、函數作用域區塊級作用域

#全域作用域: 程式碼在程式的任何地方都能被訪問,例如window 物件。但全域變數會污染全域命名空間,容易引起命名衝突。

模組作用域: 早期 js 語法中沒有模組的定義,因為最初的腳本小而簡單。後來隨著腳本越來越複雜,就出現了模組化方案(AMD、CommonJS、UMD、ES6模組等)。通常一個模組就是一個檔案或一段腳本,而這個模組擁有自己獨立的作用域。

函數作用域: 顧名思義由函數建立的作用域。閉包就是在該作用域下產生,後面我們會單獨介紹。

區塊層級作用域: 由於 js 變數提升存在變數覆蓋、變數污染等設計缺陷,所以 ES6 引入了區塊層級作用域關鍵字來解決這些問題。典型的案例就是 let 的 for 迴圈和 var 的 for 迴圈。

// var demo
for(var i=0; i<10; i++) {
    console.log(i);
}
console.log(i); // 10

// let demo
for(let i=0; i<10; i++) {
    console.log(i);
}
console.log(i); //ReferenceError:i is not defined

了解完作用域再來談談

閉包 函數A裡包含了函數B,而函數B使用了函數A的變量,那麼函數B被稱為閉包或閉包就是能夠讀取函數A內部變數的函數。

可以看出閉包是函數作用域下的產物,閉包會隨著外層函數的執行而被同時創建,它是一個函數以及其捆綁的周邊環境狀態的引用的組合。換而言之,

閉包是內層函數對外層函數變數的不釋放

閉包的特徵:

    函數中存在函數;
  • 內部函數可以存取外層函數的作用域;
  • 參數和變數不會被GC,總是駐留在記憶體中;
  • 有記憶體地方才有閉包。
所以使用閉包會消耗記憶體、不正當使用會造成記憶體溢出的問題,在退出函數之前,需要將不使用的局部變數全部刪除。如果不是某些特定需求,在函數中建立函數是不明智的,閉包在處理速度和記憶體消耗方面對腳本效能有負面影響。

以下整理了閉包的應用場景:

// demo1 输出 3 3 3
for(var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
} 
// demo2 输出 0 1 2
for(let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
// demo3 输出 0 1 2
for(let i = 0; i < 3; i++) {
    (function(i){
        setTimeout(function() {
        console.log(i);
        }, 1000);
    })(i)
}
/* 模拟私有方法 */
// 模拟对象的get与set方法
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
    privateCounter += val;
}
return {
    increment: function() {
    changeBy(1);
    },
    decrement: function() {
    changeBy(-1);
    },
    value: function() {
    return privateCounter;
    }
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
/* setTimeout中使用 */
// setTimeout(fn, number): fn 是不能带参数的。使用闭包绑定一个上下文可以在闭包中获取这个上下文的数据。
function func(param){ return function(){ alert(param) }}
const f1 = func(1);setTimeout(f1,1000);
/* 生产者/消费者模型 */
// 不使用闭包
// 生产者
function producer(){
    const data = new(...)
    return data
}
// 消费者
function consumer(data){
    // do consume...
}
const data = producer()

// 使用闭包
function process(){
    var data = new (...)
    return function consumer(){
        // do consume data ...
    }
}
const processer = process()
processer()
/* 实现继承 */
// 以下两种方式都可以实现继承,但是闭包方式每次构造器都会被调用且重新赋值一次所以,所以实现继承原型优于闭包
// 闭包
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}
// 原型
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

對於閉包的概念好像懂了但又好像缺少了啥?意猶未盡。我也曾也閉包中迷失,但看完閉包的生命週期讓我重新找回自己。

詳解JS的三座大山:作用域與閉包、原型與原型鏈、非同步與單線程

學完就來一波牛刀小試

function test(a, b){
  console.log(b);
  return {
    test: function(c) {
      return test(c,a);
    }
  }
}

var a = test(100);a.test(101);a.test(102);
var b = test(200).test(201).test(202);
var c = test(300).test(301);c.test(302);

// undefined  100  100
// undefined  200 201
// undefined  300 301

2️⃣ 原型與原型鏈

有物件的地方就有

原型,每個物件都會在其內部初始化一個屬性,就是prototype(原型),原型中儲存共享的屬性和方法。當我們存取物件的屬性時,js引擎會先看目前物件中是否有這個屬性,如果沒有的就會找出他的prototype物件是否有這個屬性,如此遞推下去,一直擷取到 Object 內建物件。這麼一個尋找的過程就形成了 原型鏈 的概念。

理解原型最關鍵的是理清楚__proto__、prototype、constructor三者的關係,我們先看看幾個概念:

  • __proto__属性在所有对象中都存在,指向其构造函数的prototype对象;prototype对象只存在(构造)函数中,用于存储共享属性和方法;constructor属性只存在于(构造)函数的prototype中,指向(构造)函数本身。
  • 一个对象或者构造函数中的隐式原型__proto__的属性值指向其构造函数的显式原型 prototype 属性值,关系表示为:instance.__proto__ === instance.constructor.prototype
  • 除了 Object,所有对象或构造函数的 prototype 均继承自 Object.prototype,原型链的顶层指向 null:Object.prototype.__proto__ === null
  • Object.prototype 中也有 constructor:Object.prototype.constructor === Object
  • 构造函数创建的对象(Object、Function、Array、普通对象等)都是 Function 的实例,它们的 __proto__ 均指向 Function.prototype。

看起来是不是有点乱??别慌!!一张图帮你整理它们之间的关系

詳解JS的三座大山:作用域與閉包、原型與原型鏈、非同步與單線程

相同的配方再来一刀

const arr = [1, 2, 3];
arr.__proto__ === Array.prototype; // true
arr.__proto__.__proto__ === Object.prototype; // true
Array.__proto__ === Function.prototype; // true

3️⃣ 异步和单线程

JavaScript 是 单线程 语言,意味着只有单独的一个调用栈,同一时间只能处理一个任务或一段代码。队列、堆、栈、事件循环构成了 js 的并发模型,事件循环 是 JavaScript 的执行机制。

为什么js是一门单线程语言呢?最初设计JS是用来在浏览器验证表单以及操控DOM元素,为了避免同一时间对同一个DOM元素进行操作从而导致不可预知的问题,JavaScript从一诞生就是单线程。

既然是单线程也就意味着不存在异步,只能自上而下执行,如果代码阻塞只能一直等下去,这样导致很差的用户体验,所以事件循环的出现让 js 拥有异步的能力。

詳解JS的三座大山:作用域與閉包、原型與原型鏈、非同步與單線程

更多编程相关知识,请访问:编程教学!!

以上是詳解JS的三座大山:作用域與閉包、原型與原型鏈、非同步與單線程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除