解锁 JavaScript ES6:函数与对象的高级扩展功能



个人主页:学习前端的小z

个人专栏:JavaScript 精粹

本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论!


ES5、ES6介绍


文章目录

  • ES6函数扩展
    • [1 默认参数](#1 默认参数)
      • [1.1 之前写法](#1.1 之前写法)
      • [1.2 ES6 写法](#1.2 ES6 写法)
      • [1.3 注意点](#1.3 注意点)
    • [2 reset 参数](#2 reset 参数)
    • [3 name属性](#3 name属性)
    • [4 箭头函数](#4 箭头函数)
      • [4.1 this指向](#4.1 this指向)
      • [4.2 练习](#4.2 练习)
  • ES6对象扩展
    • [1 表示方法](#1 表示方法)
    • [2 属性名表达式](#2 属性名表达式)
    • [3 属性的可枚举性和遍历](#3 属性的可枚举性和遍历)
      • [3.1 可枚举性](#3.1 可枚举性)
      • [3.2 属性的遍历](#3.2 属性的遍历)
    • [4 super 关键字](#4 super 关键字)
    • [5 对象拷贝与解构](#5 对象拷贝与解构)
      • [5.1 对象解构](#5.1 对象解构)
      • [5.2 对象拷贝](#5.2 对象拷贝)
      • [5.3 链判断运算符](#5.3 链判断运算符)


ES6函数扩展

1 默认参数

javascript函数中 我们经常需要给必要参数加以默认值 防止参数为传的情况下出现错误

1.1 之前写法

js 复制代码
function count(x,y){
    return x + y;
}
count(3); //因为参数y没有传递 默认值为 undefined 3+undefined返回 NaN
js 复制代码
function count(x,y){
    x = x||0;
	y = y||0;  //如果参数y为undefined 返回0设置给y
    return x + y;
}
count(3); //3 


function count(x,y){
    x = x??0;
	y = y??0;//ES2019新增 空值合并运算符?? 详见对应文档
    return x + y;
}
count(3); //3 

1.2 ES6 写法

ES6 的写法不仅简洁 而且易读 让其他开发者能够快速了解参数类型 是否可省等信息, 也不会对函数体代码造成过多负担 有利于后期优化重构

js 复制代码
function count(x = 0,y = 0){
    return x + y;
}
count(3); //3 

1.3 注意点

js 复制代码
// 使用默认参数 无法在函数体内重新声明同名变量
function count(x = 0,y = 0){
   let x; //报错  
   const y; //报错
}
js 复制代码
//参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

let num = 1;
function count(x = num + 1 , y = 0){
    return x;
}
count() // 2

num = 99;

count(); // 100
js 复制代码
//参数也可以作为默认值 但要注意顺序

function fn(x = 10, y = x){
    console.log(x,y);
}

fn(20); //20 20
fn(); // 10 10

----------------------------
//错误写法 x参数调用y时 y还未定义
function fn(x = y, y = 10){
    console.log(x,y);
}

fn(); //Cannot access 'y' before initialization
js 复制代码
//参数默认值为变量时 如果外部作用域有对应变量 指向外部变量对应值
let  w = 10;
function fn(x = w){
    let w = 20;
    return x;
}
fn(); //10;

//在() 阶段 x已经赋值 后续修改w 也无法改变x的值
let  w = 10;
function fn(x = w){
    w = 20;
    return x;
}
fn(); //10;

2 reset 参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

js 复制代码
//类似结构赋值 以后就不用call来使arguments可以调用数组方法了
function count(...values) {
  console.log(values); //[2,5,3]
  return values.reduce((acc,curr)=> acc + curr);
}
add(2, 5, 3) // 10
js 复制代码
//注意 reset参数必须作为函数最后一个参数

function count(...values,a) { //ERROR Rest parameter must be last formal parameter
  console.log(values); //[2,5,3]
  return values.reduce((acc,curr)=> acc + curr);
}

3 name属性

函数的name属性,返回该函数的函数名。

js 复制代码
function count(){}
console.log(count.name); //"count"

(new Function).name // "anonymous"

function foo() {};
foo.bind({}).name // "bound foo"

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound " // "bound "

(function(){}).name // ""

4 箭头函数

ES6 容许使用 "箭头" => 定义函数

js 复制代码
function count(x,y){
	return x + y;
}

let count = (x, y) => x + y;

let getName = o => o.name; //getName({name:"海牙"})  "海牙"
js 复制代码
//函数体可以 直接书写表达式 或()内书写表达式 或 {} 书写多行语句

let count = (x,y)=>{
   y = 100;
   x = x * y;
   return x + y;  
}

count(3,4); //400

//()内可以通过,书写多个短语语句, 最后一个 "," 之后的值为返回值
let count = (x,y)=>(x = 100, y = 10, x + y);
count(3,4); //110 

// 报错 会识别{}为函数体
let count = id => { id: id, name: "Kyogre" };

// 不报错
let count = id => ({ id: id, name: "Kyogre" });

4.1 this指向

js 复制代码
/*函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。*/
//箭头函数可以让setInterval里面的this,绑定定义时所在的作用域,而不是指向运行
const DATE = {
    time:0
}
function clock(){
   setInterval(()=>{
        this.time++; //this指向 clock作用域内的this
        console.log(this.time);
	},100);
}
clock.call(DATE);


---------------------------------
    
const DATE = {
    time:0
}
function clock(){
   setInterval(function(){}{
        this.time++; //this通过bind绑定为clock作用域的this
        console.log(this.time);
	}.bind(this),100);
}
clock.call(DATE);
js 复制代码
*注意: `箭头函数里面根本没有自己的this,而是引用外层的this。由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。`
(function() {
  return [
    (() => this.x).bind({ x: 'inner' })()
  ];
}).call({ x: 'outer' });
//['outer']


//注意避免
const PERSON = {
    name:"kyogre",
    showName:()=>{
        return this.name;
    }
}

PERSON.showName(); //'' showName使用箭头函数 this指向了 全局作用域


//addEventListener回调函数内部this指向oBtn 如果使用了箭头函数 this指向就错了
let oBtn = document.querySelector('.btn');
oBtn.addEventListener('click', () => {
  this.classList.toggle('on');
});

4.2 练习

js 复制代码
//pipeline 通道组合函数

function double(x) {
    return x + x;
}

function triple(x) {
    return 3 * x;
}

function quarter(x) {
    return x / 4;
}


function pipe() {
    var funs = Array.prototype.slice.call(arguments);
    return function (input) {
        return funs.reduce(function (acc, currFn) {
            return currFn(acc);
        }, input)
    }
}

var result = pipe(quarter, double);


-----------------------------------
const double = x => x + x;
const triple = x => 3 * x;
const quarter = x => x / 4;

const pipe = (...functions) => input => functions.reduce(
    (acc, fn) => fn(acc),
    input
);

const result = pipe(quarter, double);
result(10); //5

ES6对象扩展

1 表示方法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

js 复制代码
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

//变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。下面是另一个例子。
js 复制代码
function f(x, y) {
  return {x, y};
}

// 等同于

function f(x, y) {
  return {x: x, y: y};
}

f(1, 2) // Object {x: 1, y: 2}


​```方法简写```

const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  method: function() {
    return "Hello!";
  }
};

setter/getter写法

js 复制代码
const cart = {
  _wheels: 4,

  get wheels () {
    return this._wheels;
  },

  set wheels (value) {
    if (value < this._wheels) {
      throw new Error('数值太小了!');
    }
    this._wheels = value;
  }
}

2 属性名表达式

js 复制代码
// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。

但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。

ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

javascript 复制代码
let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
js 复制代码
let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

注意,属性名表达式与简洁表示法,不能同时使用,会报错。

js 复制代码
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };

// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};

注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。

javascript 复制代码
const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

上面代码中,[keyA][keyB]得到的都是[object Object],所以[keyB]会把[keyA]覆盖掉,而myObject最后只有一个[object Object]属性。

3 属性的可枚举性和遍历

3.1 可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

javascript 复制代码
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true, //可写
//    enumerable: true, //可枚举
//    configurable: true //可配置
//  }

描述对象的enumerable属性,称为"可枚举性",如果该属性为false,就表示某些操作会忽略当前属性。

目前,有四个操作会忽略enumerablefalse的属性。

  • for...in循环:只遍历对象自身的和继承的可枚举的属性。
  • Object.keys():返回对象自身的所有可枚举的属性的键名。
  • JSON.stringify():只串行化对象自身的可枚举的属性。
  • Object.assign(): 忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。

这四个操作之中,前三个是 ES5 就有的,最后一个Object.assign()是 ES6 新增的。其中,只有for...in会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。实际上,引入"可枚举"(enumerable)这个概念的最初目的,就是让某些属性可以规避掉for...in操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的toString方法,以及数组的length属性,就通过"可枚举性",从而避免被for...in遍历到。

javascript 复制代码
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false

Object.getOwnPropertyDescriptor([], 'length').enumerable
// false

上面代码中,toStringlength属性的enumerable都是false,因此for...in不会遍历到这两个继承自原型的属性。

另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。

javascript 复制代码
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false

总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替。

3.2 属性的遍历

ES6 一共有 5 种方法可以遍历对象的属性。

(1)for...in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2)Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3)Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4)Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5)Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

  • 首先遍历所有数值键,按照数值升序排列。
  • 其次遍历所有字符串键,按照加入时间升序排列。
  • 最后遍历所有 Symbol 键,按照加入时间升序排列。
javascript 复制代码
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

上面代码中,Reflect.ownKeys方法返回一个数组,包含了参数对象的所有属性。这个数组的属性次序是这样的,首先是数值属性210,其次是字符串属性ba,最后是 Symbol 属性。

4 super 关键字

我们知道,this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。

javascript 复制代码
const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

上面代码中,对象obj.find()方法之中,通过super.foo引用了原型对象protofoo属性。

注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。

javascript 复制代码
// 报错
const obj = {
  foo: super.foo
}

// 报错
const obj = {
  foo: () => super.foo
}

// 报错
const obj = {
  foo: function () {
    return super.foo
  }
}

上面三种super的用法都会报错,因为对于 JavaScript 引擎来说,这里的super都没有用在对象的方法之中。第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。

JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)。

javascript 复制代码
const proto = {
  x: 'hello',
  foo() {
    console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {
    super.foo();
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

上面代码中,super.foo指向原型对象protofoo方法,但是绑定的this却还是当前对象obj,因此输出的就是world

5 对象拷贝与解构

5.1 对象解构

js 复制代码
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

5.2 对象拷贝

javascript 复制代码
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。

javascript 复制代码
// 写法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 写法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 写法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

上面代码中,写法一的__proto__属性在非浏览器的环境不一定部署,因此推荐使用写法二和写法三。

扩展运算符可以用于合并两个对象。

5.3 链判断运算符

编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取message.body.user.firstName,安全的写法是写成下面这样。

js 复制代码
// 错误的写法
const  firstName = message.body.user.firstName;

// 正确的写法
const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';
js 复制代码
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined

-------------------------------
//链判断运算符      
      
 const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value

下面是判断对象方法是否存在,如果存在就立即执行的例子。

js 复制代码
iterator.return?.()

链判断运算符有三种用法。

  • obj?.prop // 对象属性
  • obj?.[expr] // 同上
  • func?.(...args) // 函数或对象方法的调用
js 复制代码
a?.b
// 等同于
a == null ? undefined : a.b

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

使用这个运算符,有几个注意点。

(1)短路机制

?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行。

javascript 复制代码
a?.[++x]
// 等同于
a == null ? undefined : a[++x]

上面代码中,如果aundefinednull,那么x不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。

(2)delete 运算符

javascript 复制代码
delete a?.b
// 等同于
a == null ? undefined : delete a.b

上面代码中,如果aundefinednull,会直接返回undefined,而不会进行delete运算。

(3)括号的影响

如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。

javascript 复制代码
(a?.b).c
// 等价于
(a == null ? undefined : a.b).c

上面代码中,?.对圆括号外部没有影响,不管a对象是否存在,圆括号后面的.c总是会执行。

一般来说,使用?.运算符的场合,不应该使用圆括号。

(4)报错场合

以下写法是禁止的,会报错。

javascript 复制代码
// 构造函数
new a?.()
new a?.b()

// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`

// 链判断运算符的左侧是 super
super?.()
super?.foo

// 链运算符用于赋值运算符左侧
a?.b = c

(5)右侧不得为十进制数值

为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。



JavaScript:https://developer.mozilla.org/en-US/docs/Web/JavaScript


相关推荐
拜晨1 分钟前
用流式 JSON 解析让 AI 产品交互提前
前端·javascript
浩男孩4 分钟前
🍀vue3 + Typescript +Tdesign + HiPrint 打印下载解决方案
前端
andwhataboutit?5 分钟前
LANGGRAPH
java·服务器·前端
无限大66 分钟前
为什么"Web3"是下一代互联网?——从中心化到去中心化的转变
前端·后端·程序员
cypking8 分钟前
CSS 常用特效汇总
前端·css
程序媛小鱼12 分钟前
openlayers撤销与恢复
前端·js
Thomas游戏开发13 分钟前
如何基于全免费素材,0美术成本开发游戏
前端·后端·架构
若梦plus15 分钟前
Hybrid之JSBridge原理
前端·webview
chilavert31815 分钟前
技术演进中的开发沉思-269 Ajax:拖放功能
前端·javascript·ajax
xiaoxue..16 分钟前
单向数据流不迷路:用 Todos 项目吃透 React 通信机制
前端·react.js·面试·前端框架