ES6-变量与函数

ECMAScript 6+

介绍

  • ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准。因为当前版本的ES6是在2015年发布的,所以又称ECMAScript 2015。也就是说,ES6就是ES2015。

  • ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,所以它可以理解为是JavaScript的一个标准,但实际上后两者是ECMA-262标准的实现和扩展。

  • ES6+提供了许多新的语法和编程特性以提高ECMAScript的开发效率并优化ECMAScript的开发体验。ECMAScript的发展速度在不断加快,影响范围越来越大,除了Web前端开发以外,借助着Node.js的力量在服务器、桌面端甚至硬件设备等领域中也发光发热着。

let

概念: ES6新增声明变量的指令 let,它的用法类似与var,他是为了解决var在作用域方面出现的异常情况而新增使用词法作用域或块级作用域的变量
语法

let name = '小明' 

let 与 var 的区别

块级作用域变量获取,在循环中块级作用域会针对每一次迭代创建一个新的作用域

// var
 for (var i = 0; i < 5; i++) {
            setTimeout(function() {
                console.log(i) 
            },1000)
}
// 输出结果 5 5 5 5 5 

var 的声明等价于,在循环的外部创建一个变量,循环内部计时器中的调用都是这这个全局变量

因为 js 先同步执行再异步执行,当setTimeout 被执行时 for 循环已经结束了

这时每个setTimeout引用都是循环结束后的全局变量i,所有打印都是5

// 上面的代码等价于
var i = 0 
for ( i = 0; i < 5; i++) {
            setTimeout(function() {
                console.log(i) 
            },1000)
} 
// let
 for (let i = 0; i < 5; i++) {
            setTimeout(function() {
                console.log(i) 
            },1000)
}
 
// 输出结果 0 1 2 3 4 

let 只在自身的作用域内有效

上面的for循环 其实等价于每次循环都在循环内创建了一个新的let i(i只属于代码块的内部)
内部setTimeout引入其对应作用域的i值 输出的值就是 0 1 2 3 4

for(var j = 0;j < 5; j++) {
    let i = j
            setTimeout(function() {
                console.log(i) 
            },1000)
} 

块级作用域

概念:拥有块级作用域的变量只能在当前当前代码块中使用。不能在外部访问。

  • 块级作用域还有一个特点他们不能在声明之前读写,直到声明let变量的代码之前的区域都被称为暂时性死区
console.log(age)  // 正确, var 允许变量提前

var age = "小明"

console.log(name) // 错误, let 不允许变量提前

let name = "小明"
  • 重复声明,块级作用域变量不允许在同一个作用域下被重复声明
let name = "小明"
let name = "小华"   // 错误,同一个作用域下let变量不可以重复声明
var name = "小刚"   // 错误,同一个作用域下let变量也不可以使用var重复声明

const

概念: const是声明变量的另一种方式,它与let相似。但是const声明的变量被赋值后不能再改变。(我们可以理解为他是一个常量),并且const声明的变量必须在声明时赋值。

const name = "小明"
      name = "小华" // 错误,const只允许赋值一次
console.log(name)

const age; // 错误 const必须在初始化时赋值

注意:

  1. 基本数据类型存放在栈中,引用数据类型存放在堆中
  2. 栈中的数据大小已知
  3. 栈中的数据由系统自动开辟自动释放
  4. 栈的赋值都是深拷贝(创建了新的内存空间)
  5. 堆中的数据大小未知
  6. 堆中的数据由开发人员手动开辟手动释放
  7. 堆的赋值都是浅拷贝(引用同一段内存地址)
"use strict";

const person = {
            name: 18,
            age: 19
        }
       
person.name = 1 // 正确,修改引用数据类型属性不会报错

console.log(person)

const s = [5, 6, 7];

s = [1, 2, 3]; // 试图给 const 变量赋值,报错

s[2] = 45; // 与用 var 或 let 声明的数组一样,这个操作也会成功

console.log(s); // 返回 [5, 6, 45]

注意:因为引用数据类型声明的const 变量只是一段地址值,内部属性发生变化不会影响地址。所以即使是const声明的对象、数组依然可以修改他们的属性方法。本质上来说const并不能保证其值是不能改变,只能保证变量指向的那个内存地址不能改动。

备注:const声明并不会真的保护你的数据不被改变。为了确保数据不被改变,JavaScript 提供了一个函数Object.freeze来防止数据改变。

当一个对象被冻结的时候,你不能再对它的属性再进行增、删、改的操作。任何试图改变对象的操作都会被阻止,却不会报错。

let obj = {
  name:"FreeCodeCamp",
  review:"Awesome"
};
Object.freeze(obj);
obj.review = "bad"; // obj 对象被冻结了,这个操作会被忽略
obj.newProp = "Test"; // 也会被忽略,不允许数据改变
console.log(obj);
// { name: "FreeCodeCamp", review:"Awesome"}

顶层对象属性

概念:全局变量一般与window对象的属性(顶层对象属性)挂钩。ES6中使用let、const可以声明作用在全局的变量。但是这些变量不是顶层对象的属性

const person = {
            name: 18,
            age: 19
}

console.log(window.person) // undefinded

var a = 6

console.log(window.a) // 6

globalThis (ES2020)

概念: 不同js环境下的全局属性 globalThis 包含全局的 this 值,类似于全局对象(global object)。

function canMakeHTTPRequest() {
  return typeof globalThis.XMLHttpRequest === 'function';
}

console.log(canMakeHTTPRequest());
// expected output (in a browser): true

在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 window、self 或者 frames 取到全局对象,但是在 Web Workers 中,只有 self 可以。在 Node.js 中,它们都无法获取,必须使用 global。

在松散模式下,可以在函数中返回 this 来获取全局对象,但是在严格模式和模块环境下,this 会返回 undefined。

globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。

函数

默认值

概念:在ES6中函数的参数支持使用默认值,当在调用函数时如果没有给函数传递实参则形参会使用默认值。
语法

function test(x = 1, y = 2) {
            console.log(x, y)
        }
        
test() // 1,2
test(null, 1) // null 1
test(15, 16) // 15, 16
test(undefined, 20) // 1, 20 可以使用但不推荐

注意

  1. 函数中的任意形参都可以包含默认值
  2. 传递null并不会使得形参使用默认值, 参数的值将会变成null
  3. 传递undefined会使得形参使用默认值
  4. 推荐传递了实参的参数前的参数不要使用默认值
  5. 设置默认值形参后面的所有形参会被函数的length属性忽略
var a = function (a,b,c,d ) {}
a.length // 4

var a1 = function (a,b,c,d = 2 ) {}
a1.length // 3  忽略 d 只有abc

var a1 = function (a,b = 0,c,d ) {}
a1.length // 1 忽略b之后所有形参 a
  1. 函数的任何一个形参一旦被设置默认值,函数在声明初始化时。所有的参数都将变为独立作用域的参数
function a(x, x, y ) { }  // 正确,形参可以重复的定义
function a(x, x, y = 2 ) { }  // 错误,所有形参变成了拥有独立作用域的变量。不可以有同名参数
  1. 函数的默认值是惰性求值的,默认值只会在函数调用的时候才会执行,观察下面代码:
let x = 5        

function a(y = x) { 
// 正确, 设置了默认值后 y 作用域当前函数作用域内变量。但是赋值给y的x变量作用域函数外部
    let x = 1
    console.log(y,x)
}
// 函数的默认值是惰性求值,并不是在声明函数时就已经传递了默认值。而是在每次调用时才会调用默认值
a() //等价于 a(x) 5 1 

x = 200
a() //等价于 a(x) 200 1,函数每次调用时才会将默认值赋值给形参

在开发中,函数默认值惰性赋值特性可以实现函数的特定参数不可以为空

function throwErrorOfUndefined(funcName, funcArg) {
      throw new Error('函数:'+ funcName+"缺少必传参数'"+funcArg+"'")
}
// 因为是惰性,所以函数在声明时throwErrorOfUndefined不会立即执行
function test (a = throwErrorOfUndefined('test','a')) {

}

test(123) // 因为没有使用默认值,所以默认值也不会被调用

test() // 当你不传参时,函数才会调用默认值,浏览器根据默认值抛出异常

作用域

概念:作用域就是一个变量的有效范围,在ES6之前js只有函数作用域和全局作用域。ES6引入了块级作用域,在ES6中创建块级作用域最简单方法时使用大括号"{}" (在ES6中代表块)配合拥有块级作用域的变量let const使用

{
          // 大括号代表一个代码块,有自己独立的块级作用域
          let a = 7
          console.log(a) // 7
          var b = 1
          console.log(b) // 1
}

console.log(b) // 1
console.log(a) // a is not defined

使用ES5实现方法实现块

  • 方法一 利用函数作用域
(function(a) { console.log(a) })(9)
console.log(a)
  • 方法二 使用try catch实现块级作用域
try {
     throw 7
} catch (a) {
     console.log(a)
}

console.log(a)
  • 方法三 通过严格模式 eval()方法实现内部代码独立作用域(后面严格模式会讲到该知识点)
"use strict"

eval('var a = 10086')

console.log(a)

name属性

概念:ES6函数拥有name属性,属性值为当前函数的函数名

function aaa () {
    console.log(this)
}
console.log(aaa.name) // "aaa"

let demo = function () {
    
}
console.log(demo.name) // ES5 "" ES6 "demo"

严格模式(use strict)

概念:让js代码在严格的条件下执行。非严格模式下的代码很多是不符合逻辑的,严格模式可以让我们的代码更规范(提高编译效率,消除语法不严谨不合理)。也为js的升级做好铺垫。
语法:严格模式声明是将 "use strict" 字面量声明在文档的头部、函数的外部或函数的内部(ES6不允许)

 "use strict" //正确 写在外部
 function a(x, y ) { 
   "use strict" // 正确,ES5允许将 "use strict"写到函数内部
 }
 
 function a(x, y = 5) { // 错误 ES6 语法中不允许将 "use strict"写到函数内部
     "use strict"
 }
 

为什么在ES6中 "use strict" 不能写在函数内部,我们看下面的代码:

// 因为在ES6中函数的默认值是惰性赋值的
// 在函数的声明时8进制数077 不会立即赋值  "use strict" 没有办法检测到这里的问题
function a( y = 077) {
     "use strict"
      return y
}
// 当调用该函数时 8进制先赋值再执行 "use strict" 不符合开发需求
a()

ES6为了避免这些问题,规定 "use strict" 不能写在 es6的函数内部。

注意:严格模式规范

  • 不能使用未声明的变量
a = 7 // Error
  • 不能删除对象的属性,不能删除不允许删除的属性
var x = 7
delete x // Error
delete Object.prototype //Error
  • 不允许变量重命名
var a = 1
var a = 2 // Error
function (x,x) {} // Error
  • 不允许使用八进制
var num = 070 // Error
  • 不允许使用除\u以外的其他转义符
var \u710A = 15
console.log(\u710A) 
var \010 = 'ascii' // Error
  • 不允许修改只读属性 不允许对只有get方法的属性进行赋值
const person = {};

Object.defineProperty(person, 'name', {
  value: "小明",
  writable: false // 是否允许修改该属性 false不允许 true允许
});

person.name = "小刚" // Error

const person = {
    name: '小王',
    get age() {return 18}
}
person.age = 12 // Error
  • 不允许声明变量名为关键字的变量
var eval = 1 // Error
var arguments = 2 // Error
var let = 3 // Error
var static = 4 // Error
var public = 5 // Error
var private = 5.1 // Error
var protected = 5.2 // Error
var package = 6 // Error
var interface = 7 // Error
var implements = 8 // Error
var yield = 9 // Error

  • eval 作用域独立
eval("var a =10086") 
console.log(a)// undefined
  • 不允许this 指向全局对象
"use strict"

function aaa () {
    console.log(this)
}
aaa() // undefined

箭头函数

概念: 在ES6中允许使用 => 形式声明一个函数
语法

function a (x,y) {
           return x + y + 1
       }
// 上面的函数就等等价于下面的箭头函数
let a = (x, y) => x + y + 1

注意:

  1. 箭头函数不能直接设置函数名,只能将箭头函数以赋值的形式传递给变量
  2. 若箭头函数 => 符号后没有跟大括号则箭头函数会将 => 后面的代码自动return (注意没有大括号的箭头函数只适用于函数内部只有一行js表达式函数), 如果箭头函数包含大括号, 则默认return功能会被取消。
 let a = (x, y) => x + y
 
 let a = (x, y) => {
     let result = x + y + 1
     // 因为当前箭头函数包含大括号,导致自动return功能被取消
     // 这时必须使用return 返回特定的值
     return result 
 }
// 如果箭头函数使用默认return功能返回一个对象,必须用()将对象包含
let a = (name, age) => ({name: name, age: age})
  1. 若箭头函数有且仅有一个参数,则参数的()可以被省略
let func = (age) => age >= 18
// 等价于
let func = age => age >= 18

4.箭头函数自身没有this。函数内部的this指向声明这段代码的作用域。
例:下面的代码中在setTimeout中使用箭头函数,箭头函数内部的this指向的是声明这段代码的作用域对象,而不是window对象。

function Cat(name) {
            this.name = name
            this.sayHello = function () {
                console.log('Meow !')
               // 如果是普通函数 setTimeout中的this应该指向window
               // 而箭头函数指向当前代码的作用域
                setTimeout(() => console.log('你好,我叫' + this.name), 500)
            }
        }

 let cat = new Cat('旺财')

 cat.sayHello()
 
 // 对象的方法不要使用箭头函数
 let cat1 = {
            name: '阿橘',
            sayHello: () => {
                // 因为对象是全局声明的,他的作用域就是全局
                // 这里的this就是全局对象
                console.log(this)
            }
    }

cat1.sayHello() // window
  1. 箭头函数不能作为构造函数配合 new 关键字使用。箭头函数本身就没有实例(this)。
  2. 箭头函数没有arguments对象。
function test (x,y)  {
    // 伪数组,包含了当前函数的所有实参
    console.log(arguments)  // Arguments[1,2,8,0,4,5,9,7,callee]
}

test(1,2,8,0,4,5,9,7)

let test = (x,y) => {
  
    console.log(arguments)  // Error!箭头函数没有arguments
}

test(1,2,8,0,4,5,9,7)
  1. 箭头函数不能作为 Generator 函数。

优势:

  1. 简化js 回调函数
for (let i = 0; i < 5; i++) {
            setTimeout(() => console.log(i), 100)
        }
$.ajax({
            url: "test.html",
            context: document.body,
            success: () => $(this).addClass("done")
 });

[1, 2, 3, 4, 5].forEach(item => console.log(item));

  1. 简化柯里化函数 (函数一次只能接受一个参数)
// 普通函数
function getPerson(name, age, address) {
    return {
            name: name,
            age: age,
            address: address
           }
}

getPerson('小明',18, 'gz')

//  柯里化函数
function getPerson(name) {
    return function (age) {
        return function (address) {
            return {
                    name: name,
                    age: age,
                    address: address
                    }
                }
            }
        }
// ES6 箭头函数简化柯里化函数
let getPerson = name => age => address => ({
    name: name,
    age: age,
    address: address
})
getPerson('小明')(18)('gz')
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容

  • 此文包含数值的扩展、函数的扩展,总结了一些我认为有可能会用到的,而去除了一些稍稍复杂一时难以去学习理解且目前不想去...
    六个周阅读 543评论 0 7
  • title: es6-函数date: 2018-02-01 21:47:46tags: es6 前言 杭州这周温度...
    aymfx阅读 327评论 0 0
  • 前面的话 函数是所有编程语言的重要组成部分,在ES6出现前,JS的函数语法一直没有太大的变化,从而遗留了很多问题,...
    CodeMT阅读 820评论 0 1
  • 函数参数的默认值 基本用法 ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数lo...
    oWSQo阅读 245评论 0 0
  • 函数参数的默认值 基本用法 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。 上面代码检查函数l...
    陈老板_阅读 448评论 0 1