JavaScript中的操作符用于表达式的操作。JavaScript提供了很多操作符供我们选择,有些操作符是我们在开发中是经常使用的,有些可能很少使用。但是,这些操作符我们都需要有所了解,因为即使自己不用,当你看到旧项目中的一些罕见的操作符时,还是要理解这个操作符的意义。
1.一元操作符
1.1 递增和递减操作符
- (++i、–i):操作符位于变量的前面,表示先递增(递减),后执行语句;
- (i++、i–):操作符位于变量的后面,表示先执行语句,后递增(递减);
- 将不同类型的数据转化为数字类型,在执行递增递减
1.2 加和减操作符
- 加和减操作符既是一元操作符,也是二元操作符。
- 一元加和减操作符主要用于基本的算术运算
- 可以用于数据类型的转换,将不同类型的数据转化为数字类型
2.位操作运算符
现代计算机中数据都是以二进制
的形式存储的,即0、1两种状态,计算机对二进制数据进行的运算加减乘除等都是叫位运算,即将符号位共同参与运算的运算。
2.1 按位与操作符(&)
按位与操作符(&)会对参加运算的两个数据按二进制位进行与运算,即两位同时为 1 时,结果才为1,否则结果为0。
// 判断奇偶数
num & 1 === 1 // num 为奇数
num & 1 === 0 // num 为偶数
2.2 按位或操作符(|)
按位或操作符(|)会对参加运算的两个对象按二进制位进行或运算,即参加运算的两个对象只要有一个为1,其值为1。
2.3 按位非操作符 (~)
按位非操作符 (~)会对参加运算的一个数据按二进制进行取反运算。即将0变成1,1变成0。
2.4 按位异或运算符(^)
按位异或运算符(^)会对参加运算的两个数据按二进制位进行“异或”运算,即如果两个相应位相同则为0,相异则为1。
// 比较整数值是否相等
1 ^ 1 // 0 相等返回0 不相等返回非0
// 将字符串类型的数字转化为数字再进行运算
'2'^2 // 0
// 但是不能比较小数的值
2.1^2.7777 // 0
2.5 左移操作符(<<)
左移操作符(<<)会将运算对象的各二进制位全部左移若干位,左边的二进制位丢弃,右边补0。
2.6 右移运算符(>>)
有符号右移操作符(>>)会将数值的32位全部右移若干位(同时会保留正负号)。正数左补0,负数左补1,右边丢弃。操作数每右移一位,相当于该数除以2。
2.7 无符号右移操作符(>>>)
- 无符号右移操作符(>>>)会将数值的32位全部右移。对于正数,有符号右移操作符和无符号右移操作符的结果是一样的。对于负数的操作,两者就会有较大的差异。
- 无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。所以,对负数进行无符号右移操作之后就会变的特别大。
// 十进制转二进制
function decimalToBinary(num) {
let str = num.toString(2).padStart(8,'0');
return str;
}
// 二进制转十进制
function binaryToDecimal(num) {
let str = parseInt(num,2);
return str;
}
3.加减乘除运算符
3.1 加法操作符(+)
- 二元加操作符用于
计算数值
操作或者拼接字符串
操作 - 在进行加操作时,如果两个操作数都是数值或者都是字符串,那么执行结果就分别是计算出来的数值和拼接好的字符串。
- 除此之外,执行结果都取决于类型转化的结果:它会优先进行字符串拼接
- 只有操作数是字符串或者是可以转化为字符串的对象,另一个操作数也会被转化为字符串,并执行拼接操作。
- 只有任何操作数都不是字符串时才会执行加法操作。
1 + 2 // 3
"1" + "2" // "12"
"1" + 2 // "12"
1 + {} // "1[object Object]"
true + false // 1 布尔值会先转为数字,再进行运算
1 + null // 1 null会转化为0,再进行计算
1 + undefined // NaN undefined转化为数字是NaN
3.2 减法操作符(-)
- 减法操作和加法操作符类似, 但是减法操作符只能用于数值的计算,不能用于字符串的拼接。
- 如果两个操作数都是数值,就会直接进行减法操作,
- 如果有一个操作数是非数值,就会将其转化为数值,再进行减法操作
3 - 1 // 2
3 - true // 2
3 - "" // 3
3 - null // 3
NaN - 1 // NaN
3.3 乘法操作符(*)
- 乘法操作符用于计算两个数的乘积。
- 如果两个操作数都是数值,则会执行常规的乘法运算。
- 如果不是数值,会将其转化为数值,在进行乘法操作。
3.4 除法操作符(/)
- 除法操作符用于计算一个操作数除以第二个操作数的商。
- 如果两个操作数都是数值,则会执行常规的除法运算。
- 如果不是数值,会将其转化为数值,在进行除法操作。
3.5 取余操作符(%)
取余操作符用于计算一个数除以第二个数的余数。
3.6 指数操作符(**)
在ECMAScript 7中新增了指数操作符(**),它的计算效果是Math.pow()
是一样的:
Math.pow(2, 10); // 1024
2 ** 10; // 1024
4.布尔操作符
在开发时,布尔操作符是很有用的,可以精简很多代码,干掉很多多余的if-else
语句
4.1 逻辑非操作符(!)
- 逻辑非操作符可以用于JavaScript中的任何值,这个操作符使用返回布尔值。
- 逻辑非操作符首先会将操作数转化为布尔值,然后在对其取反。
逻辑非操作符也可以用于将任何值转化为布尔值,同时使用两个!,相当于调用了Boolean()
方法:
!!"blue" // true
!!0; // false
!!NaN // false
!!"" // false
!!12345 // true
4.2 逻辑与操作符(&&)
逻辑与操作符的两个操作数都为真时,最终结果才会返回真。该运算符可以用于任何类型的数据。如果有操作数不是布尔值,则结果并一定会返回布尔值:
- 如果第一个操作数是对象,则返回第二个操作数;
- 如果第二个操作数是对象,则只有在第一个操作数的求值结果为true的情况下才会返回该对象;
- 如果两个操作数都是对象,则返回第二个操作数;
- 如果第一个操作数是null,则返回null;
- 如果第一个操作数是NaN,则返回NaN;
- 如果第一个操作数是undefined,则返回undefined;
根据第二条规则,我们可以对我们项目代码中的代码进行优化:
if(data) {
setData(data);
}
// 改写后:
data && setData(data);
逻辑与操作符是一种短路操作符,只要第一个操作数为false,就不会继续执行运算符后面的表达式,直接返回false。
4.3 逻辑或操作符(||)
逻辑或操作符和逻辑与操作符类似,不过只要两个操作数中的一个为真,最终的结果就为真。该运算符可以用于任何类型的数据。如果有操作数不是布尔值,则结果并一定会返回布尔值,会遵循以下规则:
- 如果第一个操作数是对象,则返回第一个操作对象;
- 如果第一个操作数的求值结果是false,则返回第二个操作数;
- 如果两个操作数都是对象,则返回第一个操作数;
- 如果两个操作数都是null,则返回null;
- 如果两个数都是NaN,则返回NaN;
- 如果两个数都是undefined,则返回undefined。
逻辑或操作符也是具有短路特性,如果第一个操作数为真,那么第二个操作数就不需要在进行判断了,会直接返回true
可以利用这个特性给变量设置默认值:
let res = records || []
5.比较运算符
5.1 相等操作符
相等操作符包括四种:
- 等于(
==
)两个操作数都会进行强制类型转换,在确实是否相等。 - 不等于(
!=
)只有在强制类型转化后不相等才会返回true。 - 全等(
===
)只有当两个操作数的数据类型和值都相等时,才会返回true。它并不会进行数据类型的转化。 - 不全等(
!==
)只有两个操作数在不进行类型转化的情况下是不相等的,才会返回true。
5.2 关系操作符
关系操作符包括四种:
- 小于(<)
- 大于(>)
- 小于等于(<=)
- 大于等于(>=)
这几个操作符都会返回一个布尔值,他们操作时会遵循以下规则:
- 如果这两个操作数都是数值,则执行数值比较;
- 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值;
- 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较;
- 如果一个操作数是对象,则调用这个对象的valueOf()方法,并用得到的结果根据前面的规则执行比较;
- 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。
6.其他运算符
6.1 扩展操作符(…)
扩展操作符可以用来扩展一个数组对象和字符串。它用三个点(…)表示,可以将可迭代对象转为用逗号分隔的参数序列。
(1)用于展开数组:
const a = [1, 2, 3],
const b = [...a, 4, 5] // [1, 2, 3, 4, 5]
(2)用于展开对象:
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
(3)用于展开字符串
const str = "hello";
[...str] // ["h", "e", "l", "l", "o"]
(4)用于函数传参
const f = (foo, bar) => {}
const a = [1, 2]
f(...a)
(5)将类数组对象变成数组:
const list = document.getElementsByTagName('a');
const arr = [..list];
6.2 条件操作符(?:)
条件运算符实际上就是我们常说的三目运算符。看一个例子:
let res = num1 > num2 ? num1 : num2;
这里,将num1和num2中的最大值赋值给了res。
使用条件操作符可以代替很多if-else,使得代码很简洁。
6.3 赋值操作符
其实赋值操作符有很多种,包括简单的赋值操作符(=),以及一些复合赋值操作符:
- 乘赋值操作符:*=
- 除赋值操作符:/=
- 模赋值操作符:%=
- 加赋值操作符:+=
- 减赋值操作符:-=
- 左移操作符: <<=
- 有符号右移赋值操作符:>>=
- 无符号右移赋值操作符:>>>=
这些仅仅是他们对应的简写形式,并不会产生其他影响。
6.4 in操作符(in)
in操作符可以用来判断一个属性是否属于一个对象,它的返回值是一个布尔值:
const author = {
name: "CUGGZ",
age: 18
}
"height" in author; // false
"age" in author; // true
还可以用来判断一个属性是否属于对象原型链
的一部分:
let arr = ["hello", "jue", "jin"];
"length" in arr; // true
6.5 delete操作符(delete)
delete 操作符用于删除对象的某个属性或者数组元素。对于引用类型的值,它也是删除对象属性的本身,不会删除属性指向的对象。
const o = {};
const a = { x: 10 };
o.a = a;
delete o.a; // o.a属性被删除
console.log(o.a); // undefined
console.log(a.x); // 10, 因为{ x: 10 } 对象依然被 a 引用,所以不会被回收
注意:
- 原型中声明的属性和对象自带的属性无法被删除;
6.6 instanceof操作符(instanceof)
instanceof
运算符用来判断一个构造函数的prototype
属性所指向的对象是否存在另外一个要检测对象的原型链上。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
可以看到,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。
6.7 typeof操作符(typeof)
typeof
是一元运算符,它的返回值是一个字符串,它是操作数的类型
typeof 2 // number
typeof true // boolean
typeof 'str' // string
typeof [] // object
typeof function(){} // function
typeof {} // object
typeof undefined // undefined
typeof null // object
可以看到,typeof只能正确判断基本数据类型,而不能判断null、[]、{}
。
6.8 空值合并操作符(??)
在编写代码时,如果某个属性不为 null 和 undefined,那么就获取该属性,如果该属性为 null 或 undefined,则取一个默认值:
const name = dogName || 'default';
但是 || 的写法存在一定的缺陷,当 dogName 为 0 或 false 的时候也会走到 default 的逻辑。所以 ES2020 引入了 ?? 运算符。只有 ?? 左边为 null 或 undefined时才返回右边的值:
const dogName = false;
const name = dogName ?? 'default'; // name = false;
6.9 可选链操作符(?.)
在开发过程中,我们可能需要获取深层次属性,例如 system.user.addr.province.name。但在获取 name 这个属性前需要一步步的判断前面的属性是否存在,否则并会报错。
可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为null 或 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined。
const name = system?.user?.addr?.province?.name || 'default';
可选链有以下三种形式:
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
6.10 逗号运算符
逗号操作符对它的每个操作数求值(从左到右),并返回最后一个操作数的值。
function reverse(arr) {
return [arr[0], arr[1]]=[arr[1], arr[0]], arr[0] - arr[1]
}
const list = [1, 2]
reverse(list) // 返回 1,此时 list 为[2, 1]
上述代码将数组的第一项和第二项调换,并返回两项之差
6.11 双位运算符 ~~
可以使用双位操作符来替代正数的 Math.floor( )
,替代负数的 Math.ceil( )
。双否定位操作符的优势在于它执行相同的操作运行速度更快。
~~4.5 // 4
Math.floor(4.5) // 4
Math.ceil(4.5) // 5
~~-4.5 // -4
Math.floor(-4.5) // -5
Math.ceil(-4.5) // -4
6.12 void操作符(void)
- void 是一元运算符,它可以出现在任意类型的操作数之前执行操作数,会忽略操作数的返回值,返回一个 undefined。
- void 常用于 HTML 脚本中执行 JavaScript 表达式,但不需要返回表达式的计算结果。比如对于链接标签,我们并不想让它发生跳转,就可以设置
href="javascript:void(0)
。
在下面代码中,使用 void 运算符让表达式返回 undefined:
let a = b = c = 2; // 定义并初始化变量的值
d = void (a -= (b *= (c += 5))); // 执行void运算符,并把返回值赋予变量d
console.log(a); // -12
console.log(b); // 14
console.log(c); // 7
console.log(d); // undefined
由于 void 运算符的优先级比较高,高于普通运算符的优先级,所以在使用时应该使用小括号明确 void 运算符操作的操作数,避免引发错误。
6.13 new操作符(new)
在 JavaScript 中,创建对象的方式有两种:对象字面量
和使用 new 表达式
。
new 表达式是配合构造函数
使用的,通过 new 一个构造函数去继承
构造函数的属性
new 运算符创建一个用户定义的对象数据类型的实例或者具有构造函数内置对象的实例
function Test(name) {
this.name = name
}
Test.prototype.sayName = function () {
console.log(this.name)
}
const t = new Test('yck')
console.log(t.name) // 'yck'
t.sayName() // 'yck'
它进行的操作:
- 首先创建一个新的空对象
- 然后将空对象的
__proto__
指向构造函数的原型- 它将新生成的对象的
__prop__
属性赋值为构造函数的prototype
属性,使得通过构造函数创建的所有对象可以共享相同的原型。 - 这意味着同一个构造函数创建的所有对象都继承自一个相同的对象,因此它们都是同一个类的对象。
- 它将新生成的对象的
- 改变
this
的指向,指向空对象 - 对构造函数的返回值做判断,然后返回对应的值
- 一般是返回第一步创建的空对象;
- 但是当
构造函数有返回值时
则需要做判断再返回对应的值,是对象类型则返回该对象
,是原始类型则返回第一步创建的空对象
。
自己实现 new 操作符:
function myNew(Con, ...args) {
// 创建一个新的空对象
let obj = {};
// 将这个空对象的__proto__指向构造函数的原型
// obj.__proto__ = Con.prototype;
Object.setPrototypeOf(obj, Con.prototype);
// 将this指向空对象
let res = Con.apply(obj, args);
// 对构造函数返回值做判断,然后返回对应的值
return res instanceof Object ? res : obj;
}
验证一下:
function Person(name) {
this.name = name;
return '十二点的程序员'
}
let per = myNew(Person, '你好,new');
// 而当构造函数返回基础类型的数据,则会被忽略
console.log(per); // {name: "你好,new"}