语法》第八章 运算符

第二部分 语法
 
**************第八章 运算符***************
 
【阮一峰:运算符是处理数据的基本方法,用来从现有数据得到新的数据。】
 
一、加法
1.加法:js中可以完成两种运算;
 
*算术加法;
 
*字符串连接;
 
“+”运算符在js中的算法如下:
 
    (1)如果运算子是对象,先自动转成原始类型的值(即先执行该对象的valueOf方法,如果结果还不是原始类型的值,再执行toString方法;如果对象是Date实例,则先执行toString方法)。
    (2)两个运算子都是原始类型的值以后,只要有一个运算子是字符串,则两个运算子都转为字符串,执行字符串连接运算。
    (3)否则,两个运算子都转为数值,执行加法运算。
【valueOf()起什么作用?】
'1' + {foo: 'bar'} // "1[object Object]"
'1' + true // "1true"
'1' + [1] // "11"
 
2.这种由于参数不同,而改变自身行为的现象,叫做“重载”(overload)。由于加法运算符是运行时决定到底执行那种运算,使用的时候必须很小心。
 
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
 
3.变通的用法:可以用来将一个值转为字符串。
 
x + ''
 
4.
[1, 2] + [3]
// "1,23"
// 等同于
String([1, 2]) + String([3])
// '1,2' + '3'
// 结果"1,23"是由于两个数组被先转成字符串,这里js隐式调用过toString和ValueOf两种方法,但是究竟是那种方法起的作用,不同情况不一定;
 
var arr=[1,2,3]
alert(arr)
alert(String(arr))
弹出都是//1,2,3
一个隐式调用了toString()方法转换数据类型,一个显示人为调用String方法,为什么打印到页面上不是[1,2,3],个人猜测是不同的子类在继承String方法时做了重写(规定了独属于自己的String方法),这些都是js编程语言内部事先约定好的规则,与编译器无关,与浏览器无关。
 
 
5.加法运算符一定有左右两个运算子,如果只有右边一个运算子,就是另一个运算符,叫做“数值运算符, 数值运算符用于返回右边运算子的数值形式
 
+ - 3 // 等同于 +(-3) //输出-3
+ 1 + 2 // 等同于 +(1 + 2)//输出+3
+ '1' // 1 无疑,这里又隐式的做了数据类型转换
+ [1,2,3]  //NaN 
+ [1] // 1
6.如果只有左边一个运算子,会报错。
1 +//报错
 
7.加法运算符以外的其他算术运算符(比如减法、除法和乘法),都不会发生重载。它们的规则是:所有运算子一律转为数值,再进行相应的数学运算。个人猜测内部调用的Number方法
 
实例:
var now = new Date();
typeof (now + 1) // "string"
typeof (now - 1) // "number"
 
二.算数运算符
JavaScript提供9个算术运算符:
    加法运算符(Addition):x + y
    减法运算符(Subtraction): x - y
    乘法运算符(Multiplication): x * y
    除法运算符(Division):x / y
    余数运算符(Remainder):x % y
    自增运算符(Increment):++x 或者 x++
    自减运算符(Decrement):--x 或者 x--
    数值运算符(Convert to number): +x (下面俩可用来转数据类型)
    负数值运算符(Negate):-x
 
 
1.余数运算符  Js中%运算符代表的是求余运算,不是python中的取模,差别只在两个运算子符号不同的时候能体现出来,【整数商*除数一定比被除数更靠近数轴的原点0!】
 
前一个运算子被后一个运算子除所得的余数
1.1运算结果正负号由第一个运算子决定
-1 % 2 // -1 整商0
1 % -2 // 1 整商0
因此,为得到正确的负数的余数值,需要先使用绝对值函数
 
1.2 余数运算符还可以用于浮点数的运算。但是,由于浮点数不是精确的值,无法得到完全准确的结果。
6.5 % 2.1// 0.19999999999999973
【问题焦点在于你是否搞懂了取余和取模】
1.2自增自减
一元运算符,只需要一个运算子。它们的作用是将运算子首先转为数值,然后加上1或者减去1。它们会修改原始变量。
var x = 1;
++x // 2
x // 2
--x // 1
x // 1
【a++与++a的区别】
++ -- 放在在变量之后,会先返回变量操作前的值,再进行自增/自减操作,在变量下次调用的时候就是新值;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。
 
2.数值运算符、负数值运算符
数值运算符+是一元运算符,需要一个操作数
数值运算符的作用在于可以将任何值转为数值(与Number函数的作用相同)。内部应该是调用了一次Number函数
 
+true //1
+[] // 0
+{} // NaN
非数值类型的值经过数值运算符以后,都变成了数值类型(最后一行NaN也是数值)
 
负数值运算符(-),也同样具有将一个值转为数值的功能,只不过得到的值正负相反。连用两个负数值运算符,等同于数值运算符。
var x = 1;
-x // -1
-(-x) // 1
数值运算符号和负数值运算符,都会返回一个新的值,而不会改变原始变量的值。
 
3.赋值运算符 用于给变量赋值
最常见的是等号= 表达式x = y表示将y的值赋给x
JavaScript还提供其他11个复合的赋值运算符。
x += y // 等同于 x = x + y
x -= y // 等同于 x = x - y
x *= y // 等同于 x = x * y
x /= y // 等同于 x = x / y
x %= y // 等同于 x = x % y
x >>= y // 等同于 x = x >> y
x <<= y // 等同于 x = x << y
x >>>= y // 等同于 x = x >>> y
x &= y // 等同于 x = x & y
x |= y // 等同于 x = x | y
x ^= y // 等同于 x = x ^ y
 
都是先进行指定运算,然后将得到值返回给左边的变量
=的优先级最低
 
 
4.比较运算符
比较运算符用于比较两个值,然后返回一个布尔值,表示是否满足比较条件。
2 > 1 // true
js提供了8个比较运算符
    == 相等
    === 严格相等
    != 不相等
    !== 严格不相等
    < 小于
    <= 小于或等于
    > 大于
    >= 大于或等于
 
4.1比较运算符的算法
比较运算符可以比较各种类型的值,不仅仅是数值。
除了相等运算符号和精确相等运算符,其他比较运算符的算法如下。
 
  1. 如果两个运算子都是字符串,则按照字典顺序比较(实际上是比较Unicode码点)。
  2. 否则,将两个原始类型的运算子之间的比较 都转成数值,再进行比较(等同于先调用Number函数)。
  3. 运算子是符合类型、对象,必须先将其转为原始类型的值, 即先调用valueOf方法,如果返回的还是对象,再接着调用toString方法,valueOf和toString方法都使用过以后还不是原始类型会报错
    之后会根据另外一个运算子决定是否该再调用number再转数字
 
5 > '4' // true
// 等同于 5 > Number('4')
// 即 5 > 4
 
true > false // true
// 等同于 Number(true) > Number(false)
// 即 1 > 0
 
2 > true // true
// 等同于 2 > Number(true)
// 即 2 > 1
 
 
alert(Number({x: 2}) >= Number({x: 1})) //false 两个NaN比较
alert({x: 2} >= {x: 1}) //true 
两个对象都会被转成相同的字符串"[object Object]"
 
 
var x = [2];
x > '11' // true
// 等同于 [2].valueOf().toString() > '11'
// 即 '2' > '11'
var x = [2];
x > 11//false
// 等同于Number( [2].valueOf().toString() )> 11
//即2>11
 
x.valueOf = function () { return '1' };
x > '11' // false
// 等同于 [2].valueOf() > '11'
// 即 '1' > '11'
两个对象之间的比较
[2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'
 
[2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'
 
{x: 2} >= {x: 1} // true
// 等同于 {x: 2}.valueOf().toString() >= {x: 1}.valueOf().toString()
// 即 '[object Object]' >= '[object Object]'
 
 
 
 
4.2字符串的比较
字符串按照字典顺序进行比较。
比较的是每一个下标位置的字符的Unicode码点
JavaScript 引擎内部首先比较首字符的 Unicode 码点,如果相等,再比较第二个字符的 Unicode 码点,以此类推。
'cat' > 'Cat' // true'
小写的c的 Unicode 码点(99)大于大写的C的 Unicode 码点(67),所以返回true
由于所有字符都有 Unicode 码点,因此汉字也可以比较。
 
 
 
严格相等运算符(===)比较它们是否为“同一个值”。如果两个值不是同一类型,严格相等运算符(===)直接返回false
相等运算符(==):如果统一类型的值,比较值是否相等,就是看是不是同一个值,跟严格一样,否则会将它们转化成同一个类型,再用严格相等运算符进行比较。
 
4.3严格相等运算符
 
(1)不同类型的值:直接返回false
(2)同一类的原始类型值:
同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回true,值不同就返回false
 
*十进制的1与十六进制的1,因为类型和值都相同,返回true
*NaN与任何值都不相等(包括自身)。另外,正0等于负0
NaN === NaN  // false
+0 === -0 // true
 
 
(3)同一类的复合类型值:
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象,(比较的是是否指向同一个内存地址)
 
{} === {} // false
[] === [] // false
(function (){} === function (){}) // false
原因是对于复合类型的值,严格相等运算比较的是,它们是否引用同一个内存地址,而运算符两边的空对象、空数组、空函数的值,都存放在不同的内存地址,结果当然是false
 
如果两个变量引用同一个对象,则它们相等。
var v1 = {};
var v2 = v1;
v1 === v2 // true
 
new Date() > new Date() // false
new Date() >= new Date()   //true
new Date() < new Date() // false
new Date() === new Date() // false
上面的三个表达式,前两个比较的是值,最后一个比较的是地址,所以都返回false
alert(new Date()-new Date())//结果是0 中间经历了toString有没有ValueOf不确定,最后走一步Number
typeof((new Date()).valueOf()) //"number"
(4)undefined 和 null
 
undefinednull与自身严格相等。
变量声明后默认值是undefined,因此两个只声明未赋值的变量是相等的。
var v1;
var v2;
v1 === v2 // true
 
(5)严格不相等运算符
 
严格相等运算符有一个对应的“严格不相等运算符”(!==),两者的运算结果正好相反。
 
 
4.4相等运算符
相等运算符比较相同类型的数据时,与严格相等运算符完全一样,看是不是一个值,
所以比较,同一类的复合类型值,依然比的是内存地址
 
 
比较不同类型的数据时,
(1)原始类型的值
原始类型的数据会转换成数值类型再进行比较。
'true' == true // false
// 等同于 Number('true') === Number(true)
// 等同于 NaN === 1
 
'' == 0 // true
// 等同于 Number('') === 0
// 等同于 0 === 0
 
'' == false  // true
// 等同于 Number('') === Number(false)
// 等同于 0 === 0
 
'1' == true  // true
// 等同于 Number('1') === Number(true)
// 等同于 1 === 1
 
'   123  ' == 123 // true
// 因为字符串转为数字时,省略前置和后置的空格
 
原始都转数,对象转原始再转数

(2)对象与原始类型值比较

 
对象(这里指的符合数据类型,包括数组和函数)与原始类型的值比较时,对象转化成原始类型的值(先valueOf再toString),再走上面一步
document.write(String([1]))这是显示调用String
document.write([1])js内部隐式调用toString,隐式转换内部转换规则都是通过调用了ValueOf 和toString来实现的
[1] == 1 // true
 
[1] == '1' // true
 
[1] == true // true
 
数组[1]分别与数值、字符串和布尔值进行比较,会先转成字符串或数值,再进行比较。比如,与数值1比较时,数组[1]会被自动转换成数值1,因此得到true

(3)undefined和null

 
undefinednull与原始类型的值比较时,结果都为false,它们互相比较时结果为true
false == null // falsefalse == undefined // false

0 == null // false0 == undefined // false

undefined == null // true
绝大多数情况下,对象与undefinednull比较,都返回false。只有在对象转为原始值得到undefined时,才会返回true,这种情况是非常罕见的。
 
 
(4)由于相等运算符不限制两个操作数的数据类型变化,各种坑,各种 隐藏的类型转换
'' == '0'           // false  直接比
0 == ''             // true  转数值比
0 == '0'            // true   转数值比
 
2 == true           // false
2 == false          // false
 
false == 'false'    // false
false == '0'        // true
 
false == undefined  // false
false == null       // false
null == undefined   // true
 
' ' == 0     // true
 

(5)不相等运算符

 
相等运算符有一个对应的“不相等运算符”(!=),两者的运算结果正好相反。
 
5.布尔运算符
用于将表达式转为布尔值。
 
  • 取反运算符:!
  • 且运算符:&&
  • 或运算符:||
  • 三元运算符:?:
 
5.1!取反运算符
用于将布尔值变为相反值,即true变成falsefalse变成true
对于非布尔值的数据,取反运算符会自动将其转为布尔值,然后取反。我猜内部用Boolean方法,规则是,以下六个值取反后为true,其他值取反后都为false
 
 
这意味着,取反运算符有转换数据类型的作用。
!undefined // true
!null // true
!0 // true  (包括+0和-0)
!NaN // true
!"" // true
!false//true
 
!54 // false
!'hello' // false
![] // false
!{} // false
 
5.1.1不管什么类型的值,经过取反运算后,都变成了布尔值。
 
5.1.2如果对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数的作用相同。这是一种常用的类型转换的写法。
两次取反就是将一个值转为布尔值的简便写法。
!!x
// 等同于Boolean(x)
5.2且运算符&&
且运算符的运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值,如果是表达式则计算出结果);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值。
 
 
't' && '' // ""
't' && 'f' // "f"
't' && (1 + 2) // 3
'' && 'f' // ""
'' && '' // ""
 
var x = 1;
(1 - 1) && ( x += 1) // 0
x // 1
由于且运算符的第一个运算子的布尔值为false,第一个运算子可能不是布尔值,这里会有一个转布尔值的隐式转换。
 
这种跳过第二个运算子的机制,被称为“短路”。有些程序员喜欢用它取代if结构,比如下面是一段if结构的代码,就可以用且运算符改写。
if (i) {
  doSomething();
}
 
// 等价于
 
i && doSomething();
 
5.3或运算符 ||
且是false短路,或是true短路。
 
或运算符(||)的运算规则是:如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false,还会去算一下第二个运算子的值,不管它false还是true,返回第二个运算子的值。
短路规则对这个运算符也适用。
 
false || 0 || '' || 4 || 'foo' || true
// 4
 

运算符常用于为一个变量设置默认值。下面代码不严谨

function saveText(text) {
 text = text || '';
  // ...
}
// 或者写成

saveText(this.text || '')
 
上面代码表示,如果函数调用时,没有提供参数,则该参数默认设置为空字符串。默认往里传空字符串
 
5.4三元条件运算符
三元条件运算符用问号(?)和冒号(:),分隔三个表达式。如果第一个表达式的布尔值为true,则返回第二个表达式的值,否则返回第三个表达式的值。第一个值不是布尔类型会转换成布尔
 
三元条件表达式与if...else语句具有同样表达效果,前者可以表达的,后者也能表达。但是两者具有一个重大差别,if...else是语句,没有返回值;三元条件表达式是表达式,具有返回值。所以,在需要返回值的场合,只能使用三元条件表达式,而不能使用if..else
console.log(true ? 'T' : 'F');
console.log方法的参数必须是一个表达式,这时就只能使用三元条件表达式。如果要用if...else语句,就必须改变整个代码写法了。
6.位运算符 暂时不看
 
7.其他运算符
7.1void运算符
void运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
void 0 // undefined
void(0) // undefined
 
7.1.1void运算符的两种写法,都正确。建议采用后一种形式,即总是使用括号。因为void运算符的优先性很高,如果不使用括号,容易造成错误的结果。比如,void 4 + 7实际上等同于(void 4) + 7
 
var x = 3;void (x = 5) //undefined
x // 5
 
 
7.1.2这个运算符主要是用于书签工具(bookmarklet),以及用于在超级链接中插入代码,目的是返回undefined可以防止网页跳转。
 
<a href="javascript:void window.open('http://example.com/')">
  点击打开新窗口
</a>
上面代码用于在网页中创建一个链接,点击后会打开一个新窗口。如果没有void,点击后就会在当前窗口打开链接。
 
7.1.3下面是常见的网页中触发鼠标点击事件的写法。
 
<a href="http://example.com" onclick="f();">文字</a>
 
上面代码有一个问题,函数f必须返回false,或者说onclick事件必须返回false,内部机制如何未知,否则会引起浏览器跳转到example.com
function f() {
  // some code
  return false;
}
 
<a href="http://example.com" onclick="f()(给click事件绑定一个函数执行某个功能,但是不让页面跳转);return false;">文字</a>
 
 
7.1.4等价写法:用void运算符<a href="javascript: void(f())">文字</a>
 
下面的代码会提交表单,但是不会产生页面跳转。
<a href="javascript: void(document.form.submit())">
文字</a>
 
7.2逗号运算符:
逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
'a', 'b' // "b"
 
var x = 0;
var y = (x++, 10);
x // 1
y // 10
逗号运算符返回后一个表达式的值。
很难找到实际应用的场景,而且下面代码测试也不正确
alert(1,2)//1
alert((1,2))//2 【原因不详】
8,运算顺序
8.1优先级:
var x = 1;
var arr = [];

var y = arr.length <= 0 || arr[0] === undefined ? x : arr[0];
根据语言规格,这五个运算符的优先级从高到低依次为:小于等于(<=)、严格相等(===)、或(||)、三元(?:)、等号(=)。因此上面的表达式,实际的运算顺序如下。
var y = ((arr.length <= 0) || (arr[0] === undefined)) ? x : arr[0];
 
只要记忆常见优先级就可以
 
8.2圆括号()
优先级最高,可以提高运算的优先级
括号中的表达式第一个运算
由于运算符的优先级别十分繁杂,且都是来自硬性规定,因此建议总是使用圆括号,保证运算顺序清晰可读,这对代码的维护和除错至关重要。
顺便说一下,圆括号不是运算符,不具有求值作用,而是一种语法结构。它一共有两种用法:一种是把表达式放在圆括号之中,提升运算的优先级;另一种是跟在函数的后面,作用是调用函数。
 
var x = 1;
(x) = 2;

如果圆括号具有求值作用,那么就会变成1 = 2,这是会报错了。但是,上面的代码可以运行,这验证了圆括号只改变优先级,不会求值。

 
这也意味着,如果整个表达式都放在圆括号之中,那么不会有任何效果。
(exprssion)// 等同于expression
圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错。
 
(var a = 1)// SyntaxError: Unexpected token var
 
8.3左结合与右结合
对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的“左结合,从左边开始计算
少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的“右结合”(right-to-left associativity)。其中,最主要的是赋值运算符(=)和三元条件运算符(?:
w = x = y = z;
q = a ? b : c ? d : e ? f : g;
都是先计算最右边的那个运算符
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));
原文地址:https://www.cnblogs.com/xsfx/p/7123415.html