تعداد بازدید: 445

Javascript Variable Scope – Mutable

هر statement بر اساس جایگاهی از کد که در آن قرار گرفته است سطح دسترسی به سایر قسمت های کد دارد که به آن Scope می گوییم. این سطح دسترسی شامل توابع و متغیر ها و کلاس ها و سایر statement ها می شود.سطوح دسترسی معمولا بر اساس عمق تعیین می شوند.

هر scope بلوک کدی است که میان {#} قرار می گیرد. این بلوک کد می تواند یکی از موارد function , loop , condition , class , methods , closure,… باشند. هر scope متغیر هایی دارد که با توجه به نوع آن متغیر و نوع آن ناحیه – scope – می تواند در نواحی دیگر فراخوانده شود.

فلسفه تعریف scope ها در زبان های برنامه نویسی برای عدم وقوع خطا در جریان برنامه هایی است که روند اجرای آن طولانی می باشد. دیاگرام رنگی scope در زیر رابطه میان inner scope و outer scope می باشد.

javascript-scope

در تصویر بالا ۳ نوع ناحیه دسترسی – scope – داریم که هر کدام دسترسی به یک سری نواحی دیگر دارند. با توجه به شکل اگر قرار است به نواحی بیرونی (خارج از scope) دسترسی داشته باشیم outside است . اگر قرار است به نواحی داخلی دسترسی داشته باشیم یعنی نواحی داخلی که تعریف شده است ما در inside حرکت کرده ایم .

حال در inside ما دسترسی به نواحی داخلی نداریم و در outside ما دسترسی به نواحی بیرونی داریم . یعنی scope 3 به نواحی 1,2,3 دسترسی دارد و scope 2 به نواحی 1,2 و scope 1 تنها به ناحیه 1 دسترسی دارد.

در تعریف دیگر scope 3  باکس رنگ بنفش در نواحی 1,2 قرار دارد (باکس بنفش داخل باکس سبز و باکس قرمز قرار گرفته است ) پس به مقادیر آن ها دسترسی مستقیم دارد و scope 2 در ناحیه 1 قرار دارد به عبارتی باکس سبز در باکس قرمز قرار گرفته است پس به آن ناحیه دسترسی دارد .

یک مثال عملی از این باکس ها می زنیم و بلوک های کد را به باکس های رنگی تصویر بالا نگاشت می دهیم:

var a = 10
function outside(){
    var b = 20
    function inside(){
        var c = 30
        console.log(c)
    }
}

در بلوک کد بالا global scope معادل باکس رنگ قرمز و تابع outside معادل رنگ سبز و تابع inside معادل رنگ بنفش می باشد و متغیر های a , b , c با توجه به این نگاشت :

  • در تابع inside به متغیر های a,b,c دسترسی داریم
  • در تابع outside به متغیر های a,b دسترسی داریم (سایر متغیر ها غیر مجاز است)
  • در global scope به متغیر a دسترسی داریم (سایر متغیر ها غیر مجاز است)

Variable Scopes

هر scope نسبت به scope های دیگر خود رابطه inner , outer دارد. یعنی یا داخل یک scope دیگری است یا یک scope داخل خود دارد. به طور مثال در دیاگرام رنگی بالا بلوک بنفش داخل بلوک سبز است. پس در این رابطه بلوک بنفش inner scope است و بلوک سبز outer scope است.

نواحی دسترسی به موارد زیر تقسیم بندی می شوند:

  • Global Scope
  • Local Scope
    • Block Scope
    • Function Scope
  • Lexical Scope

در تمامی نواحی بالا رابطه inner به تمامی نواحی بالاتر از خود دسترسی دارد . در روابط outer نسبت به نوع متغیر ممکن است دسترسی باشد یا نباشد.

Global Scope

هر متغیری که در بدنه اصلی برنامه خارج از هر گونه متد یا تابع یا شئ قرار داشته باشد در ناحیه عمومی یا global scope قرار گرفته است. همان طور که از نام این نوع ناحیه مشخص است برای عموم نواحی قابل دسترس است و مستقیم در RAM قرار می گیرد.اگر متغیر جدیدی با همان نام در ناحیه عمومی تعریف گردد جایگزین متغیر قبل در حافظه RAM می گردد.

var name = 'ali'
function foo(){
    var famil = 'alavi'
    console.log(name + ' ' + famil)
}
name //'ali'
famil //Uncaught ReferenceError: famil is not defined

همان طور که در مثال بالا مشاهده می کنید در ناحیه عمومی متغیر name قابل دسترس در سایر نواحی یعنی ناحیه داخلی تابع foo است. و متغیر famil در ناحیه عمومی که outer scope ناحیه تابع foo است نمایش داده نمی شود.( ناحیه عمومی ناحیه مهربانی است که مورد کم لطفی سایر نواحی قرار گرفته با این که همه متغیر هاش قابل دسترس است ولی متغیر های سایر نواحی local داخلش قابل نمایش نیست)

متغیر هایی که در این ناحیه تعریف می گردد تا انتهای برنامه قابل دسترس می باشند . این متغیر ها قابل ویرایش و نمایش توسط سایر local scope ها می باشند. دقت داشته باشید اگر متغیری در بدنه اصلی تعریف بشود شامل ناحیه عمومی می گردد و بلوک تابع jQuery شامل این موضوع نمی گردد و برای تعریف متغیر عمومی در jQuery لازم است که در مرورگر ها از window استفاده کنیم

jQuery(document).ready(function($){
    window.name = 'ali'
    var famil = 'alavi'
})
name //"ali"
famil //Uncaught ReferenceError: famil is not defined

ناحیه عمومی در آن this برابر window (BOM) باشد . هر تابع نسبت به این که در چه ناحیه ای قرار گرفته this در آن می تواند برابر هر شئ باشد (منظور ناحیه ای که تابع در آن قرار گرفته است . ممکن است در window باشد یا کلاس خاص یا شئ خاص)

Local Scope

هر بلوک جدیدی که داخل {#} در داخل global scope تعریف گردد یک ناحیه داخلی یا local scope است.حال این ناحیه local scope تابع یا متد یا یک تابع عضو یک شئ باشد. متغیر ها و پارامتر هایی که در این ناحیه تعریف می شوند تنها در همین ناحیه و نواحی داخلی inner scopes قابل دسترس می باشند.

function foo(){
    var name = 'ali'
    var getName = function(){
        return name
    }
    return getName
}

foo()() //"ali"

local scope به دو زیر دسته Function Scope , Block Scope تقسیم می گردد. هر کدام از این نواحی رفتار منحصر به فردی نسبت به انواع متغیر ها دارد. در مثال زیر تابع foo یک local scope است از نوع Function Scope .

function foo(famil){
  var name = 'ali'
}

Function Scope

هر ناحیه ای که داخل یک function , class , method , closure , object element function , constructor , قرار بگیرد جزو local scopes از نوع function scope است . در این ناحیه تمامی انواع متغیر در outer scope غیر قابل دسترس است .

function foo(){
    var name = 'ali'
    var famil = 'alavi'
}
famil // Uncaught ReferenceError: famil is not defined

پس رفتار var , let , const در Function scope دقیقا مشابه هم است و در outer scope غیر قابل دسترس است . متغیر های داخلی کلاس از طریق کلمه کلیدی this قابل دسترس است و جزو Function scope ها می باشند و به متغیر های outer scopes دسترسی دارند.

var name = 'ali'
class person {
    getName(){
        return name
    }
}
p1 = new person
p1.getName() //"ali"

Block Scope

به بلوک کدی که در بین {#} قرار گرفته باشد و جزو Function scope نباشد Block scope می گوییم . حلقه ها و شرط ها loops , conditions جزو این دسته از نواحی دسترسی است .

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

رفتار متغیر های let , const در این ناحیه با var متفاوت است . در این ناحیه اگر بخواهیم متغیر را به صورت خصوصی در این نواحی متغیر تعریف کنیم باید از let , const استفاده کنیم و اگر قرار باشد به صورت عمومی برای scope های داخلی تعریف کنیم باید از var استفاده کنیم.

var cond = true
if(cond){
    var name = 'hello world'
}
name //"hello world"
var cond = true
if(cond){
    let str = 'hello world'
}
str //Uncaught ReferenceError: str is not defined

Declaration keywords

در جاوا اسکریپت نوع داده را در زمان تعریف متغیر تعیین نمی کنیم و نوع داده همانند سایر زبان های اسکریپتی مثل php با توجه به محتوای متغیر تعیین می گردد. تفاوت کلمات کلیدی در تعریف نام متغیر در scope یا محدوده دسترسی به آن مقادیر است .

مبحث Variable Privacy یک مبحث مهم در زبان های برنامه نویسی است که تعیین می کند که کدامین نوع متغیر در کدامین نوع ناحیه قابل دسترس می باشد. در Object Oriented Programming ما به این قضیه Encapsulation می گوییم.

بر اساس نوع سطح دسترسی به متغیر ها (scope) و ویژگی های منحصر بفرد هر یک سه نوع عبارت برای تعریف متغیر در جاوا اسکریپت داریم :

var

تعریف متغیر و اقلب مقدار دهی به آن . در این نوع ما می توانیم بعد ها متغیری دقیقا با همین نام تعریف کنیم.

var name='ali'
/* process and statements **/
var name='zahra'

let

تعریف متغیر محلی یا متغیر قابل تعریف در یک بلوک کد . در این نوع تعریف در بلوک تعیین شده در ساختار کد متغیر با نام تعیین شده نمی توان تعریف کرد و در این صورت خطا خواهیم داشت. همچنین در بلوک های کد قبل تر قابل دسترسی نخواهند بود.

let name='ali'
/* do code and statement and also on this block **/
let name='zahra'
Uncaught SyntaxError: redeclaration of let name

همان طور که در خط انتهایی گفته شد خطای تعریف مجدد متغیر name بعد از تعریف آن پدیدار شده است. به طور مثال this در یک تابع متفاوت است از this در global scope . کلمات کلیدی let , const تنها در scope مجاز خود قابل دسترس می باشند. حلقه ها و توابعی که در یک شئ یا کلاس دیگر تعریف می شوند توابع تو در تو (nested) دارای scope های مجزا می باشند.

let name = 'ali'
let famil = 'sabagh'

const person = {
   name: 'abolfazl',
   getName: function(){
      return this.name
   },
   getFamil: function(){
      return famil
   },
   getThis: function(){
      return this
   }
}
function globalScopeFunction(){
   return this
}

person.getName() //'abolfazl'
person.getFamil() //'sabagh'
person.getThis() //Object { name: "abolfazl", getName: getName(), getFamil: getFamil(), getThis: getThis() }
globalScopeFunction() //Window https://gnutec.net

همان طور که در مثال مشاهده می کنید متغیر های let در scope های داخلی (inner) قابل دسترس می باشند و در scope های بیرونی (outer) قابل دسترس نمی باشند.

متغیر از نوع const برای تعریف تنظیمات CONFIG و کلید های اعتبارسنجی TOKEN , API_KEY , AUTH_KEY , … و تعریف نام توابع کاربرد دارد.

برای تفاوت سطح دسترسی به متغیر ها در let , var به مثال زیر توجه کنید. اگر شمارنده با let تعریف شده باشد تنها در همان scope حلقه قابل دسترسی است ولی اگر با var تعیین شده باشد خارج از scope حلقه هم قابل دسترس است.

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

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

const

رفتار این عبارت بسیار شبیه به let است . به طوری که بعد از تعریف کردن قابل تعریف مجدد نیست (برای ساختمان داده های ساده مثل string , number , …) . اگر از این عبارت برای تعریف آرایه و یا شئ استفاده کنیم اعضای این آرایه یا شئ قابل ویرایش و حذف هستند (برای ساختمان داده های پیشرفته مثل Array , Object ) و در تمامی ساختمان داده ها قابلیت تعریف مجدد وجود ندارد.

const numbers = [1,2,3,4,5,6]

متغیر const قابل دوباره تعریف کردن نیست (به هیچ وجه) و همچنین مقادیر غیر Object آن نیز قابل تغییر دادن نیستند.

const a =10
undefined
a=11 //Uncaught TypeError: invalid assignment to const 'a'
a='hello' //Uncaught TypeError: invalid assignment to const 'a'
const a = 'hello' //Uncaught SyntaxError: redeclaration of const a

نکته مهم : اگر اشیا در const تعریف گردد ویژگی های آن قابل ویرایش و حذف و اضافه هستند:

const box = {
    color: 'blue',
    size: 10,
}
box.color = 'red' //"red"
console.log(box) //Object { color: "red", size: 10 }

نکته مهم : در تعریف توابع و ساختمان داده های پیشرفته مثل آرایه و شئ (Array , Objects) بهتر از متغیر const استفاده کنیم.

در تعریف شمارنده حلقه ها از const استفاده نکنید چرا که قابلیت مقدار گذاری در پیمایش های حلقه را ندارد و در دومین پیمایش برای مقدار دهی دوم شمارنده خطا خواهیم داشت.

for(const j = 0; j < 10; j++){
    console.log(j)
}
0
 debugger eval code:2:10

Mutable/Immutable Variables

انواع متغیر در جاوا اسکریپت و سایر زبان های برنامه نویسی به یکی از اشکال mutable/immutable می باشد (یک دسته بندی از متغیر هاست). Immutable در لغت به معنی غیر قابل تغییر می باشد. یعنی این متغیر مقدار آن پس از assign در یک بلوک جدید از حافظه رم (Memory) ذخیره می گردد و از این رو مصرف رم آن بیشتر از نوع Mutable است . این حالت به این معنی است که اگر شما یک مقدار جدید برای یک متغیر تعیین کنید عملا یک بلوک جدید برای آن در نظر گرفته اید و حال این یک متغیر با نام جدید باشد یا یک مقدار جدید. واژه Immutable از این رو برای این نوع متغیر در نظر گرفته شده است که بلوک حافظه غیر قابل تغییر است و باید برای تعیین مقدار جدید بلوک جدیدی از این متغیر را تعیین کنیم.

let name = 'ali'
let newName = name
newName = `${newName} reza`

console.log(name,newName)

let a = 10
let b = a
b = 20
console.log(a,b,a===b)

پروسه تغییر مقدار متغیر در نوع داده Immutable به شرح زیر است :

  • یک بلوک حافظه برای متغیر name تعیین می شود و مقدار ali در آن قرار می گیرد.
  • متغیر newName یک کپی از مقدار name می گیرد و در بلوک جدید حافظه قرار می گیرد.
  • مقدار جدید برای newName در بلوک جدید از حافظه قرار می گیرد و بلوک قبلی که در آن مقدار ali بود وجود دارد و ازاد نشده است .
  • حال متغیر newName به بلوک جدید در حافظه اشاره دارد و در کل ۳ بلوک در این پروسه ما درگیر کرده ایم.

نکته مهم : در جاوا اسکرپیت تمامی نوع داده های Primitive به صورت Immutable است . تنها نوع داده های Immutable اشیا و ارایه ها Object,Array است.

Mutable Object

اشیا در جاوا اسکریپت به این شکل است که رفتار آن ها دقیقا به صورت Mutable است . یعنی پس از این که assign شدند (حال به متغیر جدید یا مقدار جدید) در بلوک موجود وارد می شوند . از این رو به آن ها قابل تغییر می گوییم.

let a = {
   foo: 'bar'
}

let b = a
b.foo='test'
console.log(a.foo,b.foo,a===b) //test,test,true
/* the object a is mutable */

Immutable Object

اشیا در صورتی که بخواهند به صورت Immutable مقدار دهی شوند و ما بلوک جدید حافظه برای آن ها در نظر بگیریم باید از متد ایستا Object.assign استفاده کنیم (یا برخی کتابخانه های موجود با این هدف)

let a = {
   foo: 'bar'
}

let b = Object.assign({},a)
b.foo='test'
console.log(a.foo,b.foo,a===b) //'bar','test',true

پس ما دو دسته بندی در متغیر ها در جاوا اسکریپت داریم دسته اول Primitive / none Primitive است که به صورت خاص قابل تعریف است . در دسته دوم ما Mutable و Immutable را داریم . در این حالت Primitive ها همگی Immutable هستند (مثلا String , Number , Undefined ) و اشیا و آرایه ها none Primitive هستند و در بلوک جدید تعریف می شوند.

Immutable Array

زمانی که قرار است یک آرایه را از روی یک آرایه جدید بسازیم می توانیم از متد slice استفاده کنیم که در یک بلوک حافظه جدید این آرایه کپی را برای ما برگرداند:

let timeList = [12,14,22,07]
let times = timeList.slice()
times.push(00)
console.log(timeList,times)

/*Array(4) [ 12, 14, 22, 7 ]
Array(5) [ 12, 14, 22, 7, 0 ]*/

 

مطالب مشابه

Set Standard Build-in Object

Set یک موجودیت با رفتاری شبیه به آرایه است و می تواند در مواقع خاص جایگزین آن شود . این شرایط خاص با توجه به محتوای...

webpack javascript module bundler

Webpack Module Bundler Part 2

وب پک یک نرم افزار است که در قالب یک کتابخانه و یک ابزار توسعه برای ساخت سایر نرم افزار ها در با استفاده از node...

اشتراک گذاری :

مدیر وب سایت گنوتک . برنامه نویسی رو با زبان C در هفده سالگی شروع کردم . در حال حاضر به برنامه نویسی php برپایه معماری MVC , HMVC و همچنین سیستم مدیریت محتوای WordPress و فریم ورک محبوب لاراول علاقه مند هستم و دوست دارم اطلاعاتم رو با شما به اشتراک بگذارم.

۰ دیدگاه برای Javascript Variable Scope – Mutable

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *