es运算符:
1.无符号右移运算,如果是负数的话则结果就不正确
由于无符号右移运算的结果是一个 32 位的正数,所以负数的无符号右移运算得到的总是一个非常大的数字。例如,如果把 -64 右移 5 位,将得到 134217726。如何得到这种结果的呢?
要实现这一点,需要把这个数字转换成无符号的等价形式(尽管该数字本身还是有符号的),可以通过以下代码获得这种形式:
var iUnsigned64 = -64 >>> 0;
然后,用 Number 类型的 toString() 获取它的真正的位表示,采用的基为 2:
alert(iUnsigned64.toString(2));
这将生成 11111111111111111111111111000000,即有符号整数 -64 的二进制补码表示,不过它等于无符号整数 4294967232。
出于这种原因,使用无符号右移运算符要小心。
2.逻辑NOT运算符(!)
- 如果运算数是对象,返回 false
- 如果运算数是数字 0,返回 true
- 如果运算数是 0 以外的任何数字,返回 false
- 如果运算数是 null,返回 true
- 如果运算数是 NaN,返回 true
- 如果运算数是 undefined,发生错误
判断ECMAScript 变量的boolean值时,可以使用两次not运算符
第一个not返回运算符的boolean值,第二个not对该boolean值求负得到真正的变量值
3.逻辑and(&&)
如果某个运算数不是原始的 Boolean 型值,逻辑 AND 运算并不一定返回 Boolean 值:
- 如果一个运算数是对象,另一个是 Boolean 值,返回该对象。
- 如果两个运算数都是对象,返回第二个对象。
- 如果某个运算数是 null,返回 null。
- 如果某个运算数是 NaN,返回 NaN。
- 如果某个运算数是 undefined,发生错误。
4.逻辑或(||)
逻辑 OR 运算并不一定返回 Boolean 值:
- 如果一个运算数是对象,并且该对象左边的运算数值均为 false,则返回该对象。
- 如果两个运算数都是对象,返回第一个对象。
- 如果最后一个运算数是 null,并且其他运算数值均为 false,则返回 null。
- 如果最后一个运算数是 NaN,并且其他运算数值均为 false,则返回 NaN。
- 如果某个运算数是 undefined,发生错误。
5.关系运算符
如果比较一个数字和字符串,es6会把字符串转化为数字然后按照数字的顺序比较他们
注:基本上每种运算符都有特殊情况,上面只是列举了一部分 (他们的特殊情况及相应的规则要特别注意)
es函数
1.arguments对象相当于形参的参数列表
2.函数可以作为参数传给另一个函数
3.function对象的length属性的值是函数期望的参数的个数
4.function对象的方法 valueOf()方法和toString方法,返回的都是函数的源代码
5.闭包:函数可以使用函数之外定义的变量
ecmascript面向对象技术
1.面向对象:
封装、聚集、继承、多态
2.宿主对象:
所有非本地对象都是宿主对象,(由ECMAScript实现的宿主环境提供的对象,如:BOM和DOM)
3.没有私有作用域,规定应该把在属性前后加下划线,看作是私有的,但事实上它还是公有的
没有静态作用域,可以给构造函数提供属性和方法(构造函数只是函数,函数是对象,对象可以有属性和方法)
4.关键字this总是指向调用该方法的对象
5.prototype属性可以定义构造函数的属性和方法,还可以为本地对象添加属性和方法
es继承
1.对象冒充可以支持多重继承
弊端:
例如,如果存在两个类 ClassX 和 ClassY,ClassZ 想继承这两个类,可以使用下面的代码:
function ClassZ() { this.newMethod = ClassX; this.newMethod(); delete this.newMethod; this.newMethod = ClassY; this.newMethod(); delete this.newMethod; }
如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。因为它从后面的类继承。
call方法
第一个参数用作this对象,其他参数都直接传递给函数自身
例:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.call(obj, "The color is ", "a very nice color indeed.");
apply方法
apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
ES6:
1.let命令
let与var的区别
(let的作用域只在let的代码段中有效,且是在变量声明之后有效,在变量声明之前,该变量都是不可用的,成为暂时性死区)
let变量不会像var那样发生“变量提升”现象,所以变量一定要在声明后使用
例:
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
let不允许在相同作用域内重复声明同一个变量,也不允许在函数内部重新声明参数
es5 函数只能在顶层作用域和函数作用域中声明,不能再块级作用域声明
es6允许在块级作用域中声明函数
const命令声明一个只读的常量,且一旦声明,常量的值就不能改变,作用域与let相同,只在声明所在的块级作用域内有效
常量储存的只是一个地址,不可改变的知识这个地址,但是对象本身是可变的
下面是一个将对象彻底冻结的函数。
var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, value) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); };
es6中一方面规定,为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是全局对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于全局对象的属性
例:
var a = 1; // 如果在Node的REPL环境,可以写成global.a // 或者采用通用方法,写成this.a
window.a // 1 let b = 1; window.b // undefined
es6变量的解构赋值
例:
let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3
let [ , , third] = ["foo", "bar", "baz"]; third // "baz"
let [x, , y] = [1, 2, 3]; x // 1 y // 3
let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4]
let [x, y, ...z] = ['a']; x // "a" y // undefined z // []
如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。
// 报错
let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {};
解构赋值允许指定默认值。
var [foo = true] = []; foo // true [x, y = 'b'] = ['a']; // x='a', y='b' [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
es6内部使用严格相等的运算符,判断一个位置是否有值,如果一个成员不严格等于undefined,默认值是不会产生的
var [x = 1] = [undefined]; x // 1
var [x = 1] = [null]; x // null
如果默认值是一个表达式,这个表达式是惰性求值的,即只在用到的时候,才会求值
function f() { console.log('aaa'); } let [x = f()] = [1];
上面代码中,因为x
能取到值,所以函数f
根本不会执行
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb" var { baz } = { foo: "aaa", bar: "bbb" }; baz // undefined
变量名与属性名不一致:
var { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" let obj = { first: 'hello', last: 'world' }; let { first: f, last: l } = obj; f // 'hello' l // 'world'
这实际上说明,对象的解构赋值是下面形式的简写
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
var { foo: baz } = { foo: "aaa", bar: "bbb" }; baz // "aaa" foo // error: foo is not defined
上面代码中,真正被赋值的是变量baz
,而不是模式foo
。
注意,采用这种写法时,变量的声明和赋值是一体的。对于let
和const
来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。
let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo" let baz; let {bar: baz} = {bar: 1}; // SyntaxError: Duplicate declaration "baz"
上面代码中,解构赋值的变量都会重新声明,所以报错了。不过,因为var
命令允许重新声明,所以这个错误只会在使用let
和const
命令时出现。如果没有第二个let
命令,上面的代码就不会报错。
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123; s === Number.prototype.toString // true let {toString: s} = true; s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString
属性,因此变量s
都能取到值。
解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError
函数参数的解构赋值
[[1, 2], [3, 4]].map(([a, b]) => a + b); // [ 3, 7 ]
圆括号问题
不能使用圆括号的情况
以下三种解构赋值不得使用圆括号。
(1)变量声明语句中,不能带有圆括号。
// 全部报错
var [(a)] = [1]; var {x: (c)} = {}; var ({x: c}) = {}; var {(x: c)} = {}; var {(x): c} = {}; var { o: ({ p: p }) } = { o: { p: 2 } };
上面三个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。
(2)函数参数中,模式不能带有圆括号。
函数参数也属于变量声明,因此不能带有圆括号。
// 报错
function f([(z)]) { return z; }
(3)赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。
// 全部报错
({ p: a }) = { p: 42 }; ([a]) = [5];
上面代码将整个模式放在圆括号之中,导致报错。
// 报错
[({ p: a }), { x: c }] = [{}, {}];
上面代码将嵌套模式的一层,放在圆括号之中,导致报错。
可以使用圆括号的情况
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
[(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确
上面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的圆括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,跟圆括号无关;第二行语句中,模式是p,而不是d;第三行语句与第一行语句的性质一致。
变量解构赋值的用途:
1.交换变量的值
2,从函数返回多个值
3.函数参数的定义
4.提取json数据
5.函数参数的默认值
6.遍历map结构
7.输入模块的指定方法
详情见https://www.w3cschool.cn/ecmascript/3yhi1q5h.html最后部分
es6字符串扩展
JavaScript共有6种方法可以表示一个字符。
'\z' === 'z' // true '\172' === 'z' // true '\x7A' === 'z' // true '\u007A' === 'z' // true '\u{7A}' === 'z' // true
es6数组的扩展
Map和Set数据结构有一个has方法,has方法与includes的区别
- Map结构的
has
方法,是用来查找键名的,比如Map.prototype.has(key)
、WeakMap.prototype.has(key)
、Reflect.has(target, propertyKey)
。 - Set结构的
has
方法,是用来查找值的,比如Set.prototype.has(value)
、WeakSet.prototype.has(value)
。
数组的空位
Array(3) // [, , ,]
返回一个具有3个空位的数组
注:空位不是undefined,一个位置的值等于undefined,依然是有值的,空位是没有任何值
es5与es6对空位的处理不一样
es6将空位转为undefined
函数扩展
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。如果非尾部的参数设置默认值,实际上这个参数是没办法省略的
有默认值的参数都不是尾参数,这时无法省略该参数,而不省略它后面的参数,除非显示输入undefined
函数指定了默认值后,将会返回没有指定默认值参数的个数(指定了默认值后,length属性将失真)
如果设置了默认值的参数 不是尾参数,那么length属性也不再计入后面的参数了
作用域:
如果默认值是一个变量,则该变量所处的作用域与其他变量的作用域规则是一样的:先是当前函数的作用域,然后才是全局作用域
ECMAScript 6 函数的扩展
函数参数的默认值
基本用法
在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function log(x, y) { y = y || 'World'; console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello World
上面代码检查函数log
的参数y
有没有赋值,如果没有,则指定默认值为World
。这种写法的缺点在于,如果参数y
赋值了,但是对应的布尔值为false
,则该赋值不起作用。就像上面代码的最后一行,参数y
等于空字符,结果被改为默认值。
为了避免这个问题,通常需要先判断一下参数y
是否被赋值,如果没有,再等于默认值。
if (typeof y === 'undefined') { y = 'World'; }
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello
可以看到,ES6的写法比ES5简洁许多,而且非常自然。下面是另一个例子。
function Point(x = 0, y = 0) { this.x = x; this.y = y; } var p = new Point(); p // { x: 0, y: 0 }
除了简洁,ES6的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。
参数变量是默认声明的,所以不能用let
或const
再次声明。
function foo(x = 5) { let x = 1; // error const x = 2; // error }
上面代码中,参数变量x
是默认声明的,在函数体中,不能用let
或const
再次声明,否则会报错。
与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。
function 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
上面代码使用了对象的解构赋值默认值,而没有使用函数参数的默认值。只有当函数foo
的参数是一个对象时,变量x
和y
才会通过解构赋值而生成。如果函数foo
调用时参数不是对象,变量x
和y
就不会生成,从而报错。如果参数对象没有y
属性,y
的默认值5才会生效。
下面是另一个对象的解构赋值默认值的例子。
function fetch(url, { body = '', method = 'GET', headers = {} }) { console.log(method); } fetch('http://example.com', {}) // "GET" fetch('http://example.com') // 报错
上面代码中,如果函数fetch
的第二个参数是一个对象,就可以为它的三个属性设置默认值。
上面的写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
function fetch(url, { method = 'GET' } = {}) { console.log(method); } fetch('http://example.com') // "GET"
上面代码中,函数fetch
没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method
才会取到默认值GET
。
再请问下面两种写法有什么差别?
// 写法一
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]
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
// 例一
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
。
如果传入undefined
,将触发该参数等于默认值,null
则没有这个效果。
function foo(x = 5, y = 6) { console.log(x, y); } foo(undefined, null) // 5 null
上面代码中,x
参数对应undefined
,结果触发了默认值,y
参数等于null
,就没有触发默认值。
函数的length属性
指定了默认值以后,函数的length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2
上面代码中,length
属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了3个参数,其中有一个参数c
指定了默认值,因此length
属性等于3
减去1
,最后得到2
。
这是因为length
属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也不会计入length
属性。
(function(...args) {}).length // 0
如果设置了默认值的参数不是尾参数,那么length
属性也不再计入后面的参数了。
(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1
作用域
一个需要注意的地方是,如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。
var x = 1; function f(x, y = x) { console.log(y); } f(2) // 2
上面代码中,参数y
的默认值等于x
。调用时,由于函数作用域内部的变量x
已经生成,所以y
等于参数x
,而不是全局变量x
。
如果调用时,函数作用域内部的变量x
没有生成,结果就会不一样。
let x = 1; function f(y = x) { let x = 2; console.log(y); } f() // 1
上面代码中,函数调用时,y
的默认值变量x
尚未在函数内部生成,所以x
指向全局变量。
如果此时,全局变量x
不存在,就会报错。
function f(y = x) { let x = 2; console.log(y); } f() // ReferenceError: x is not defined
下面这样写,也会报错。
var x = 1; function foo(x = x) { // ... } foo() // ReferenceError: x is not defined
上面代码中,函数foo
的参数x
的默认值也是x
。这时,默认值x
的作用域是函数作用域,而不是全局作用域。由于在函数作用域中,存在变量x
,但是默认值在x
赋值之前先执行了,所以这时属于暂时性死区(参见《let和const命令》一章),任何对x
的操作都会报错。
如果参数的默认值是一个函数,该函数的作用域是其声明时所在的作用域。请看下面的例子。
let foo = 'outer'; function bar(func = x => foo) { let foo = 'inner'; console.log(func()); // outer } bar();
上面代码中,函数bar
的参数func
的默认值是一个匿名函数,返回值为变量foo
。这个匿名函数声明时,bar
函数的作用域还没有形成,所以匿名函数里面的foo
指向外层作用域的foo
,输出outer
。
如果写成下面这样,就会报错。
function bar(func = () => foo) { let foo = 'inner'; console.log(func()); } bar() // ReferenceError: foo is not defined
上面代码中,匿名函数里面的foo
指向函数外层,但是函数外层并没有声明foo
,所以就报错了。
下面是一个更复杂的例子。
var x = 1; function foo(x, y = function() { x = 2; }) { var x = 3; y(); console.log(x); } foo() // 3
上面代码中,函数foo
的参数y
的默认值是一个匿名函数。函数foo
调用时,它的参数x
的值为undefined
,所以y
函数内部的x
一开始是undefined
,后来被重新赋值2
。但是,函数foo
内部重新声明了一个x
,值为3
,这两个x
是不一样的,互相不产生影响,因此最后输出3
。
如果将var x = 3
的var
去除,两个x
就是一样的,最后输出的就是2
。
var x = 1; function foo(x, y = function() { x = 2; }) { x = 3; y(); console.log(x); } foo() // 2
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter
rest参数(...变量名)中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。下面是一个利用rest参数改写数组push方法的例子。
function push(array, ...items) { items.forEach(function(item) { array.push(item); console.log(item); }); } var a = []; push(a, 1, 2, 3)
注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。函数的length属性不包括rest参数
扩展运算符 (...),它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列
例:
function push(array, ...items)
{
array.push(...items);
}
function add(x, y)
{
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2)
{
return num1 + num2;
};
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
var getTempItem = id => ({ id: id, name: "Temp" });