- 函数的扩展
-
基本用法
-
函数参数的默认值
javascriptES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
-
参数变量是默认声明的,所以不能用
let
或const
再次声明。下面代码中,参数变量x
是默认声明的,在函数体中,不能用let
或const
再次声明,否则会报错。javascriptfunction foo(x = 5) { let x = 1; // error const x = 2; // error }
-
-
与解构赋值默认值结合使用
-
参数默认值可以与解构赋值的默认值,结合起来使用。下面代码使用了对象的解构赋值默认值,而没有使用函数参数的默认值。只有当函数
foo
的参数是一个对象时 ,变量x
和y
才会通过解构赋值而生成。如果函数foo
调用时参数不是对象,变量x
和y
就不会生成,从而报错。如果参数对象没有y
属性,y
的默认值5才会生效。javascriptfunction foo({x, y = 5}) { console.log(x, y); } foo({}) // undefined, 5 foo({x: 1}) // 1, 5 foo({x: 1, y: 2}) // 1, 2 foo() // TypeError: Cannot read property 'x' of undefined
下面两种写法都对函数的参数设定了默认值,区别是写法一函数参数 的默认值 是空对象 ,但是设置 了对象解构赋值的默认值 ;写法二函数参数 的默认值 是一个有具体属性 的对象 ,但是没有设置对象解构赋值 的默认值。
javascript// 写法一 function m1({x = 0, y = 0} = {}) { return [x, y]; } // 写法二 function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; } 值。 // 函数没有参数的情况 m1() // [0, 0] m2() // [0, 0] // x和y都有值的情况 m1({x: 3, y: 8}) // [3, 8] m2({x: 3, y: 8}) // [3, 8] // x有值,y无值的情况 m1({x: 3}) // [3, 0] m2({x: 3}) // [3, undefined] // x和y都无值的情况 m1({}) // [0, 0]; m2({}) // [undefined, undefined] m1({z: 3}) // [0, 0] m2({z: 3}) // [undefined, undefined]
个人理解当m1没有传入参数时,参数设置为空对象,使用对象结构值为{x = 0, y = 0};当m2没有传入参数时,使用设置的默认参数
-
-
参数默认值的位置
-
通常情况 下,定义 了默认值 的参数 ,应该是 函数的尾参数 。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部 的参数设置默认值 ,实际上这个参数是没法省略 的。
javascript// 例一 function f(x = 1, y) { return [x, y]; } f() // [1, undefined] f(2) // [2, undefined]) f(, 1) // 报错 f(undefined, 1) // [1, 1] // 例二 function f(x, y = 5, z) { return [x, y, z]; } f() // [undefined, 5, undefined] f(1) // [1, 5, undefined] f(1, ,2) // 报错 f(1, undefined, 2) // [1, 5, 2]
-
如果传入**
undefined
** ,将触发该参数等于默认值 ,null
则没有这个效果 。javascriptfunction foo(x = 5, y = 6) { console.log(x, y); } foo(undefined, null) // 5 null
-
-
函数的length属性
-
指定 了默认值 以后,函数的**
length
属性** ,将返回没有指定默认值 的参数个数 。也就是说,指定了默认值后,length
属性将失真。javascript(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2
-
因为
length
属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数 也不会计入length
属性 。javascript(function(...args) {}).length // 0
-
如果设置了默认值 的参数不是尾参数 ,那么**
length
属性** 也不再计入后面的参数 了。javascript(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1
-
-
作用域
-
下面代码中,函数
foo
的参数x
的默认值也是x
。这时,默认值x
的作用域是函数作用域,而不是全局作用域。由于在函数作用域中,存在变量x
,但是默认值在x
赋值之前先执行 了,所以这时属于暂时性死区 (参见《let和const命令》一章),任何对x
的操作都会报错。javascriptvar x = 1; function foo(x = x) { // ... } foo() // ReferenceError: x is not defined
-
-
应用
-
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
javascriptfunction throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter
上面代码的
foo
函数,如果调用的时候没有参数,就会调用默认值throwIfMissing
函数,从而抛出一个错误。从上面代码还可以看到,参数
mustBeProvided
的默认值等于throwIfMissing
函数的运行结果(即函数名之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。另外,可以将参数默认值设为
undefined
,表明这个参数是可以省略的。javascriptfunction foo(optional = undefined) { ··· }
-
-
rest参数
-
ES6引入rest参数(形式为"...变量名 "),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中 。
javascriptfunction add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10
-
注意,rest参数 之后不能再有其他参数(即只能是最后一个参数 ),否则会报错。
javascript// 报错 function f(a, ...b, c) { // ... }
-
函数的length属性 ,不包括rest参数 。
javascript(function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
-
-
扩展运算符
-
扩展运算符(spread)是三个点(
...
)。它好比rest参数的逆运算 ,将 一个数组转为用逗号分隔的参数序列 。javascriptconsole.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 [...document.querySelectorAll('div')] // [<div>, <div>, <div>]
-
该运算符主要用于函数调用。
javascriptfunction push(array, ...items) { array.push(...items); } function add(x, y) { return x + y; } var numbers = [4, 38]; add(...numbers) // 42
上面代码中,
array.push(...items)
和add(...numbers)
这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。 -
扩展运算符与正常的函数参数可以结合使用,非常灵活。
javascriptfunction f(v, w, x, y, z) { } var args = [0, 1]; f(-1, ...args, 2, ...[3]);
-
-
扩展运算符的应用
-
合并数组 :扩展运算符提供了数组合并的新写法。
javascript// ES5 [1, 2].concat(more) // ES6 [1, 2, ...more] var arr1 = ['a', 'b']; var arr2 = ['c']; var arr3 = ['d', 'e']; // ES5的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
-
与解构赋值结合 :扩展运算符可以与解构赋值结合起来,用于生成数组 。
javascript// ES5 a = list[0], rest = list.slice(1) // ES6 [a, ...rest] = list
下面是另外一些例子。
javascriptconst [first, ...rest] = [1, 2, 3, 4, 5]; first // 1 rest // [2, 3, 4, 5] const [first, ...rest] = []; first // undefined rest // []: const [first, ...rest] = ["foo"]; first // "foo" rest // []
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
javascriptconst [...butLast, last] = [1, 2, 3, 4, 5]; // 报错 const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错
-
字符串
-
扩展运算符还可以将字符串转为真正的数组。
javascript[...'hello'] // [ "h", "e", "l", "l", "o" ]
正确返回字符串长度的函数,可以像下面这样写。
javascriptfunction length(str) { return [...str].length; }
-
-
实现了Iterator接口的对象
-
任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。下面是一个最特殊的例子。它不是数组 ,而是 一个类似数组的对象 。这时,扩展运算符可以将其转为真正的数组,原因 就在于**
NodeList
对象实** 现了Iterator接口 。javascriptvar nodeList = document.querySelectorAll('div'); var array = [...nodeList];
-
对于那些没有部署Iterator接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。下面代码中,
arrayLike
是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。这时,可以改为使用Array.from
方法将arrayLike
转为真正的数组。javascriptlet arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // TypeError: Cannot spread non-iterable object. let arr = [...arrayLike];
-
-
-
Map和Set结构,Generator函数
-
扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。
javascriptlet map = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let arr = [...map.keys()]; // [1, 2, 3]
-
Generator函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
javascriptvar go = function*(){ yield 1; yield 2; yield 3; }; [...go()] // [1, 2, 3]
下面代码中,变量
go
是一个Generator函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。如果对没有
iterator
接口的对象,使用扩展运算符,将会报错。javascriptvar obj = {a: 1, b: 2}; let arr = [...obj]; // TypeError: Cannot spread non-iterable object
-
-
name属性
-
函数的
name
属性,返回该函数的函数名。javascriptfunction foo() {} foo.name // "foo" var func1 = function () {}; // ES5 func1.name // "" // ES6 func1.name // "func1" const bar = function baz() {}; // ES5 bar.name // "baz" // ES6 bar.name // "baz"
-
-
箭头函数
- 函数体 内的**
this
对象** ,就是定义时 所在的对象 ,而不是使用时 所在的对象
- 函数体 内的**
-
- 对象的扩展
-
属性的简洁表示法
- ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。下面代码表明,ES6允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。下面是另一个例子。
-
属性名表达式
-
ES6 允许字面量定义对象时,用(表达式)作为对象的属性名,即把表达式放在方括号内。
javascriptlet propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };
-
注意,属性名表达式 与简洁表示法 ,不能同时使用 ,会报错。
javascript// 报错 var foo = 'bar'; var bar = 'abc'; var baz = { [foo] }; // 正确 var foo = 'bar'; var baz = { [foo]: 'abc'};
-
注意,属性名表达式 如果是 一个对象 ,默认情况 下会自动将对象转为字符串
[object Object]
,这一点要特别小心。下面代码中,[keyA]
和[keyB]
得到的都是[object Object]
,所以[
**keyB]**会把[keyA]
覆盖掉 ,而myObject
最后只有一个[object Object]
属性。javascriptconst keyA = {a: 1}; const keyB = {b: 2}; const myObject = { [keyA]: 'valueA', [keyB]: 'valueB' }; myObject // Object {[object Object]: "valueB"}
-
-
-
ES5比较两个值是否相等,只有两个运算符:相等运算符(
==
)和严格相等运算符(===
)。它们都有缺点,前者会自动转换数据类型,后者的NaN
不等于自身,以及+0
等于-0
。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。ES6提出"Same-value equality"(同值相等)算法,用来解决这个问题。Object.is
就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。javascriptObject.is('foo', 'foo') // true Object.is({}, {}) // false //不同之处只有两个:一是+0不等于-0,二是NaN等于自身。 +0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
-
-
Object.assign()
-
Object.assign
方法用于对象的合并,将源对象(source)的所有可枚举属性 ,复制到目标对象 (target)。Object.assign
方法的第一个参数是目标对象 ,后面 的参数都是源对象 。注意,如果目标对象与源对象有同名属性 ,或多个源对象有同名属性,则后面的属性 会覆盖前面 的属性。 如果只有一个参数,Object.assign
会直接返回该参数。如果该参数不是对象 ,则会先转成对象 ,然后返回 。由于**undefined
和null
无法转成对象** ,所以如果它们作为参数(仅限第一个参数),就会报错(其它参数无法转成对象,会直接跳过)。javascriptvar target = { a: 1 }; var source1 = { b: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
-
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
javascriptvar v1 = 'abc'; var v2 = true; var v3 = 10; var obj = Object.assign({}, v1, v2, v3); console.log(obj); // { "0": "a", "1": "b", "2": "c" }
-
注意点:
-
Object.assign
方法实行 的是浅拷贝 ,而不是深拷贝 。深拷贝实现方式如下javascriptObject.assign(obj2,JSON.parse(JSON.stringify(obj1)))
-
常见用途
-
为对象添加属性
javascriptclass Point { constructor(x, y) { Object.assign(this, {x, y}); } }
-
为对象添加方法
javascriptObject.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同于下面的写法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };
-
克隆对象
javascriptfunction clone(origin) { return Object.assign({}, origin); }
-
合并多个对象
javascriptconst merge = (target, ...sources) => Object.assign(target, ...sources);
-
为属性指定默认值
javascriptconst DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { options = Object.assign({}, DEFAULTS, options); }
-
-
-
-
属性的可枚举性
-
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor
方法可以获取 该属性 的描述对象 。javascriptlet obj = { foo: 123 }; Object.getOwnPropertyDescriptor(obj, 'foo') // { // value: 123, // writable: true, // enumerable: true, // configurable: true // }
-
描述对象的enumerable属性,称为"可枚举性",如果该属性为false ,就表示某些操作 会忽略当前属性。ES5有三个操作会忽略enumerable为false的属性。
for...in 循环:只遍历对象自身的和继承的可枚举的属性
Object.keys() :返回对象自身的所有可枚举的属性的键名
JSON.stringify() :只串行化对象自身的可枚举的属性
ES6新增 了一个操作Object.assign(),会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。这四个操作之中,只有for...in会返回继承的属性。实际上,引入enumerable的最初目的 ,就是让某些属性可以规避掉for...in操作 。比如 ,对象原型的toString方法 ,以及数组的length属性,就通过这种手段,不会被for...in遍历到。
javascriptObject.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable // false Object.getOwnPropertyDescriptor([], 'length').enumerable // false
-
上面代码中,
toString
和length
属性的enumerable
都是false
,因此for...in
不会遍历到这两个继承自原型的属性。另外,ES6规定,所有Class的原型的方法都是不可枚举的。javascriptObject.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable // false
-
总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用
for...in
循环 ,而使用Object.keys()
代替。
-
-
__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
- __proto__属性
-
__proto__
属性(前后各两个下划线),用来读取或设置当前对象的prototype
对象。目前,所有浏览器(包括IE11)都部署了这个属性。javascript// es6的写法 var obj = { method: function() { ... } }; obj.__proto__ = someOtherObj; // es5的写法 var obj = Object.create(someOtherObj); obj.method = function() { ... };
-
该属性没有写入ES6的正文 ,而是写入了附录,原因 是
__proto__
前后的双下划线,说明它本质上是一个内部属性 ,而不是一个正式的对外的API,只是由于浏览器广泛支持,才被加入了ES6。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为 这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性 ,而是使用下面的Object.setPrototypeOf()
(写操作)、Object.getPrototypeOf()
(读操作)、Object.create()
(生成操作)代替。
-
- Object.setPrototypeOf()
-
Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象。它是ES6正式推荐的设置原型对象的方法。
javascript// 格式 Object.setPrototypeOf(object, prototype) // 用法 var o = Object.setPrototypeOf({}, null);
-
该方法等同于下面的函数。
javascriptfunction (obj, proto) { obj.__proto__ = proto; return obj; }
-
下面是一个例子。
javascriptlet proto = {}; let obj = { x: 10 }; Object.setPrototypeOf(obj, proto); proto.y = 20; proto.z = 40; obj.x // 10 obj.y // 20 obj.z // 40
上面代码将proto对象设为obj对象的原型,所以从obj对象可以读取proto对象的属性。
-
- Object.getPrototypeOf()
-
该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象
javascriptObject.getPrototypeOf(obj);
-
下面是一个例子。
javascriptfunction Rectangle() { } var rec = new Rectangle(); Object.getPrototypeOf(rec) === Rectangle.prototype // true Object.setPrototypeOf(rec, Object.prototype); Object.getPrototypeOf(rec) === Rectangle.prototype // false
-
- __proto__属性
-
Object.values()
-
Object.values
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。javascriptvar obj = { foo: "bar", baz: 42 }; Object.values(obj) // ["bar", 42]
-
返回数组的成员顺序,与《属性的遍历》介绍的排列规则一致。(首先遍历 所有属性名为数值 的属性,按照数字排序 。其次遍历 所有属性名为字符串 的属性,按照生成时间排序 。最后遍历 所有属性名为Symbol 值的属性,按照生成时间排序。 )
javascriptvar obj = { 100: 'a', 2: 'b', 7: 'c' }; Object.values(obj) // ["b", "c", "a"]
-
上面代码中,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是
b
、c
、a
。Object.values
只返回对象自身的可遍历属性。javascriptvar obj = Object.create({}, {p: {value: 42}}); Object.values(obj) // []
-
上面代码中,
Object.create
方法的第二个参数添加的对象属性(属性p
),如果不显式声明,默认是不可遍历的。Object.values
不会返回这个属性。Object.values
会过滤属性名为Symbol值的属性。javascriptObject.values({ [Symbol()]: 123, foo: 'abc' }); // ['abc']
-
如果
Object.values
方法的参数是一个字符串,会返回各个字符组成的一个数组。javascriptObject.values('foo') // ['f', 'o', 'o']
-
上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。因此,
Object.values
返回每个属性的键值,就是各个字符组成的一个数组。如果参数不是对象,
Objec``t.values
会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values
会返回空数组。Object.values(42) // [] Object.values(true) // []
-
-
Object.entries()
-
Object.entries
方法返回一个数组 ,成员是 参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组 。javascriptvar obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]
-
除了返回值不一样,该方法的行为与
Object.values
基本一致。如果原对象的属性名是一个Symbol值,该属性会被省略。
-
-
对象的扩展运算符
- 解构赋值
-
对象的解构赋值用于从一个对象取值,相当于将所有可遍历的 、但尚未被读取的属性 ,分配到指定的对象上 面。所有的键和它们的值,都会拷贝到新对象上面。
javascriptlet { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 }
-
扩展运算符
-
扩展运算符(
...
)用于取出参数对象 的所有可遍历属性 ,拷贝到当前对象之中 。javascriptlet z = { a: 3, b: 4 }; let n = { ...z }; n // { a: 3, b: 4 }
-
扩展运算符可以用于合并两个对象。
javascriptlet ab = { ...a, ...b }; // 等同于 let ab = Object.assign({}, a, b);
-
-
- 解构赋值
-
Object.getOwnPropertyDescriptors()
-
ES7有一个提案,提出了
Object.getOwnPropertyDescriptors
方法,返回指定对象所有自身属性(非继承属性)的描述对象。javascriptconst obj = { foo: 123, get bar() { return 'abc' } }; Object.getOwnPropertyDescriptors(obj) // { foo: // { value: 123, // writable: true, // enumerable: true, // configurable: true }, // bar: // { get: [Function: bar], // set: undefined, // enumerable: true, // configurable: true } }
Object.getOwnPropertyDescriptors
方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。
-
-
ES6基础3
清风霁玥缘2024-03-12 16:50
相关推荐
吃瓜群众i37 分钟前
理解Javascript闭包安大桃子41 分钟前
Mapbox GL + Deck.gl 三维实战:Mapbox 加载 Tileset3D 倾斜摄影模型yede44 分钟前
多行文本省略号显示,更多按钮展开全部就是我1 小时前
React 应用性能优化实战G扇子1 小时前
深入解析XSS攻击:从原理到防御的全方位指南snakeshe10101 小时前
入解析React性能优化策略:eagerState的工作原理六边形6661 小时前
Vue中的 ref、toRef 和 toRefs 有什么区别kovli1 小时前
红宝书第十八讲:详解JavaScript的async/await与错误处理前端付豪1 小时前
🚀 React 应用国际化实战:深入掌握 react-i18next 的高级用法代码小学僧1 小时前
使用 Cloudflare workers 做一个定时发送消息的飞书机器人