JS+Vue初步学习
本文是自己学习中的随笔,如果有错误或者改进,欢迎指出。本文是学习https://wangdoc.com/javascript/types/null-undefined-boolean.html后的随笔,如果感兴趣,可以自己看一看
1.变量
- var 声明变量 如果声明(单纯声明,不赋值,赋值会覆盖前面声明的变量)一个已经存在的变量 是无效的(不会报错,也不会起作用)
- 变量提升机制 所有变量的声明都会被提前,然后再一行一行的执行代码(所以如果在声明前使用了变量不会报错,会将其认为undefined)
- 变量类型:Undefined、Null、Boolean、Number、String、Object
- null与undefined的区别:
null
是一个表示“空”的对象,转为数值时为0
;undefined
是一个表示"此处无定义"的原始值,转为数值时为NaN
。Number(null) // 0 5 + null // 5 Number(undefined) //NaN 5 + undefined // NaN
// 容易查出错误
- undefined表示“未定义”,它的主要应用场景如下:
// 变量声明了,但没有赋值 var i; i // undefined
// 调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
return x;
}
f() // undefined// 对象没有赋值的属性
var o = new Object();
o.p // undefined// 函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined
2.运算符
- 相等运算符:
===
,!==
,==
,!=
- undefined、""或者''(空字符串)、null、false、0、NaN (转换为boolean是false,其余为true)
- 空数组[] 与 空对象{} 转换为布尔值为true
3.数值
- JS中所有的数值都是以64位浮点数存储的,所以1与1.0是相同的 (某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算)
1 === 1.0 // true
- 由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
0.1 + 0.2 === 0.3 // false
0.3 / 0.1
// 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1)
// false0.1 === 0.1
// true
- 超过-2^53 ~ 2^53 部分的数值,都无法保证精度,运算时结果会出错 (由于2的53次方是一个16位的十进制数值,所以简单的法则就是,JavaScript 对15位的十进制数都可以精确处理)
Math.pow(2, 53) // 9007199254740992
// 多出的三个有效数字,将无法保存
9007199254740992111
// 9007199254740992000
- 根据标准,64位浮点数的指数部分的长度是11个二进制位。JavaScript 能够表示的数值范围为21024到2-1023(开区间),超出这个范围的数无法表示。
- 如果一个数大于等于2的1024次方,那么就会发生“正向溢出”,返回
Infinity
。 - 如果一个数小于等于2的-1075次方(指数部分最小值-1023,再加上小数部分的52位),那么就会发生为“负向溢出”,会直接返回0。
Math.pow(2, 1024) // Infinity Math.pow(2, -1075) // 0 Number.MAX_VALUE // 1.7976931348623157e+308 Number.MIN_VALUE // 5e-324
- 数值的表示方法:
123e3 // 123000 123e-3 // 0.123 -3.1E+12 // -3100000000000 .1e-23 // 1e-24
- JavaScript 会自动将数值转为科学计数法表示:
-
- 小数点前的零多于21位
- 小数点后的零多于5位
- 进制:通常来说,有前导0的数值会被视为八进制,但是如果前导0后面有数字8和9,则该数值被视为十进制(不会报错,严格的0o才会报错)(ES5严格模式和ES6已经废除0开头表示表示8进制,因为使用时很容易造成混淆)
0888 // 888 0777 // 511 0o88 // 报错
- JavaScript 内部实际上存在2个0:一个是+0,一个是-0,区别就是64位浮点数表示的符号位不同,它们是等价的,在使用过程中几乎都是当作0来使用
-0 === +0 // true 0 === -0 // true 0 === +0 // true
+0 // 0
-0 // 0
(-0).toString() // '0'
(+0).toString() // '0'// 唯一有区别的场合是,+0或-0当作分母,返回的值是不相等的。
(1 / +0) === (1 / -0) // fasle
(1 / +0) // Infinity
(1 / -0) // -Infinity - NaN是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于
Number
5 - 'x' // NaN // 将x转为数字出错
// 一些运算函数的结果也为NaN
Math.acos(2) // NaN 求取反余弦值。返回的值是 0 到 PI 之间的弧度值。
Math.log(-1) // NaN 返回参数的自然数底数的对数值。
Math.sqrt(-1) // NaN // 求根0 / 0 // NaN
typeof NaN // 'number' - NaN不等于任何数,包括它本身。数组的indexOf方法内部使用的是严格相等运算符,所以该方法对NaN不成立。NaN与任何数(包括它自己)的运算,得到的都是NaN。
NaN === NaN // false [NaN].indexOf(NaN) // -1 Boolean(NaN) // false NaN + 32 // NaN NaN - 32 // NaN NaN * 32 // NaN NaN / 32 // NaN
- Infinity表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0得到
// 场景一 Math.pow(2, 1024) // Infinity
// 场景二
0 / 0 // NaN
1 / 0 // Infinity - Infinity大于任何数(除了NaN),-infinity小于任何数(除了NaN)。当与NaN比较时,总返回false
- Infinity的运算:
5 * Infinity // Infinity 5 - Infinity // -Infinity Infinity / 5 // Infinity 5 / Infinity // 0
0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity
Infinity + Infinity // Infinity
Infinity * Infinity // Infinity
Infinity - Infinity // NaN
Infinity / Infinity // NaN// Infinity与null计算时,null会转成0,等同于与0的计算。
null * Infinity // NaN
null / Infinity // 0
Infinity / null // Infinity
undefined + Infinity // NaN
undefined - Infinity // NaN
undefined * Infinity // NaN
undefined / Infinity // NaN
Infinity / undefined // NaN
- 与数值相关的方法:parseInt() 将字符串转为整数 (字符串头部有空格,会自动去掉;参数不是字符串,会转换为字符串再转为整数)
parseInt('123') // 123 parseInt(' 81') // 81 parseInt(1.23) // 1 // 等同于 parseInt('1.23') // 1
// 字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分
parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15// 如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1// 如果字符串以0x或0X开头,parseInt会将其按照十六进制数解析。
parseInt('0x10') // 16// 如果字符串以0开头,将其按照10进制解析。
parseInt('011') // 11// 对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。
parseInt(1000000000000000000000.5) // 1
// 等同于
parseInt('1e+21') // 1
parseInt(0.0000008) // 8
// 等同于
parseInt('8e-7') // 8 - parseInt()的进制转换:
// parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数 // 默认情况下,parseInt的第二个参数为10,即默认是十进制转十进制。 parseInt('1000') // 1000 // 等同于 parseInt('1000', 10) // 1000 parseInt('1000', 2) // 8 parseInt('1000', 6) // 216 parseInt('1000', 8) // 512
// 如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN。
// 如果第二个参数是0、undefined和null,则直接忽略。 Number则不会忽略,直接转换失败返回NaN
parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10
parseInt('1546', 2) // 1
parseInt('546', 2) // NaN// 如果parseInt的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。
parseInt(0x11, 36) // 43
parseInt(0x11, 2) // 1// 等同于
parseInt(String(0x11), 36)
parseInt(String(0x11), 2)// 等同于
parseInt('17', 36)
parseInt('17', 2)// 对于八进制的前缀0,尤其需要注意
parseInt(011, 2) // NaN// 等同于
parseInt(String(011), 2)// 等同于
parseInt(String(9), 2)// JavaScript 不再允许将带有前缀0的数字视为八进制数,而是要求忽略这个0。
// 但是,为了保证兼容性,大部分浏览器并没有部署这一条规定。 - parseFloat()方法,将字符串转为浮点数 parseFloat方法会自动过滤字符串前导的空格。
// parseFloat会将空字符串转为NaN。 // 这些特点使得parseFloat的转换结果不同于Number函数 parseFloat([]) // NaN parseFloat('FF2') // NaN parseFloat('') // NaN parseFloat(true) // NaN Number(true) // 1 parseFloat(null) // NaN Number(null) // 0 parseFloat('') // NaN Number('') // 0 parseFloat('123.45#') // 123.45 Number('123.45#') // NaN Number(undefined) // NaN parseFloat(undefined) // NaN
Number([]) // 0
Number({}) // NaN - isNaN()方法:
// isNaN只对数值有效,如果传入其他值,会被先转成数值。 // 比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回true, // isNaN为true的值,有可能不是NaN,而是一个字符串。 isNaN('Hello') // true // 相当于 isNaN(Number('Hello')) // true
// 对象和数组
isNaN({}) // true
// 等同于
isNaN(Number({})) // true
isNaN(['xzy']) // true
// 等同于
isNaN(Number(['xzy'])) // true// 对于空数组和只有一个数值成员的数组,isNaN返回false
isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false// 自定义判断是否为NaN
function myIsNaN(value) {
return typeof value === 'number' && isNaN(value);
}// 判断NaN更可靠的方法是,利用NaN为唯一不等于自身的值的这个特点,进行判断。
function myIsNaN(value) {
return value !== value;
} - isFinite()方法:返回一个布尔值,表示某个值是否为正常的数值
isFinite(Infinity) // false isFinite(-Infinity) // false isFinite(NaN) // false isFinite(undefined) // false isFinite(null) // true isFinite(-1) // true
//除了Infinity、-Infinity、NaN和undefined这几个值会返回false,
// isFinite对于其他的数值都会返回true。 - JavaScript 返回的字符串长度可能是不正确的,因为JavaScript 对 UTF-16 的支持是不完整的,由于历史原因,只支持两字节的字符,不支持四字节的字符。对于码点在
U+10000
到U+10FFFF
之间的字符,JavaScript 总是认为它们是两个字符(length
属性为2)。 - Base64编码
btoa()
:任意值转为 Base64 编码atob()
:Base64 编码转为原来的值- 注意,这两个方法不适合非 ASCII 码的字符,会报错
-
var string = 'Hello World!'; btoa(string) // "SGVsbG8gV29ybGQh" atob('SGVsbG8gV29ybGQh') // "Hello World!" btoa('你好') // 报错
// 要将非 ASCII 码字符转为 Base64 编码,必须中间插入一个转码环节,再使用这两个方法。
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}function b64Decode(str) {
return decodeURIComponent(atob(str));
}b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好" - 对象-键名 (如果是字符串,可以任意命名,包括空格;如果不是,则必须符合标识符规范或者为数值才会自动转换为字符串,否则报错)
var o1 = {}; var o2 = { bar: 'hello' };
o1.foo = o2;
o1.foo.bar // "hello"// 属性可以动态创建,不必在对象声明时就指定 如,没有在o1中声明foo属性,直接定义赋值使用
// 对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加。
// 对象中的值可以为任意数值或者函数。和Java中的对象意义上差不多,属性+方法{ foo: 123 } // JavaScript 引擎读到上面这行代码,会发现可能有两种含义。 // 第一种可能是,这是一个表达式,表示一个包含foo属性的对象; // 第二种可能是,这是一个语句,表示一个代码区块,里面有一个标签foo,指向表达式123。 // 如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块
// 这种差异在eval语句(作用是对字符串求值)中反映得最明显。
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
// 上面代码中,如果没有圆括号,eval将其理解为一个代码块;加上圆括号以后,就理解成一个对象。属性的读取有两种,一种是obj.p,另一种是obj['p'];后面一种必须用引号括起,否则会当作变量处理(方括号内部还能使用表达式) 数值键名不能使用点式读取,会和小数点混淆
- 查看一个对象本身的所有属性 Object.keys(obj)方法
- 属性的删除 delete obj.p 或者 delete obj['p'] (删除一个对象中不存在的属性,不会报错,并且返回true;所以不能使用删除来判断对象是否存在;delete只有某个属性存在,并且不能删除的时候才返回为false)
var obj = Object.defineProperty({}, 'p', { value: 123, configurable: false });
obj.p // 123
delete obj.p // false// delete命令只能删除对象本身的属性,无法删除继承的属性
var obj = {}; delete obj.toString // true obj.toString // function toString() { [native code] }
//
toString
是对象obj
继承的属性,虽然delete
命令返回true
,但该属性并没有被删除,依然存在。
// 这个例子还说明,即使delete
返回true
,该属性依然可能读取到值 - 属性是否存在:in运算符
// in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值) // 如果包含就返回true,否则返回false // 它的左边是一个字符串,表示属性名,右边是一个对象。 var obj = { p: 1 }; 'p' in obj // true 'toString' in obj // true // 但是in不能识别哪些属性是对象自身的,哪些属性是继承的 // 可以使用对象的hasOwnProperty方法判断一下,是否为对象自身的属性 var obj = {}; if ('toString' in obj) { console.log(obj.hasOwnProperty('toString')) // false }
- 属性的遍历:for...in循环
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('键名:', i);
console.log('键值:', obj[i]);
}
// 键名: a
// 键值: 1
// 键名: b
// 键值: 2
// 键名: c
// 键值: 3// 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
// 它不仅遍历对象自身的属性,还遍历继承的属性。
var obj = {};// toString 属性是存在的
obj.toString // toString() { [native code] } 默认是不可遍历的for (var p in obj) {
console.log(p);
} // 没有任何输出// 如果想遍历自身的属性,可以使用hasOwnProperty来辅助完成
var person = { name: '老张' };for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name - with语句,作用是操作同一个对象的多个属性时,提供一些书写的方便。
// 格式 with (对象) { 语句; }
// 举例
// 例一
var obj = {
p1: 1,
p2: 2,
};
with (obj) {
p1 = 4;
p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;// 例二
with (document.links[0]){
console.log(href);
console.log(title);
console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);// 注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。
var obj = {};
with (obj) {
p1 = 4;
p2 = 5;
}obj.p1 // undefined
p1 // 4
// 应该先定义对象obj的属性p1,然后在with区块内操作它。
// with没有改变作用域,它的内部依然是当前作用域,这造成了with绑定对象不明确// 建议不要使用with,使用临时变量代替
with(obj1.obj2.obj3) {
console.log(p1 + p2);
}// 可以写成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);
4.函数
- 声明函数的方法有三种:
- function命令
function print(s) { console.log(s); }
- 函数表达式(赋值语句右侧只能是表达式)
var print = function(s) { console.log(s); };
// 后面的函数可以有函数名,但是只能在函数内部使用,函数外部无效
var print = function x(){
console.log(typeof x);
};x
// ReferenceError: x is not defined
print()
// function// 这种写法的用处有两个:
// 一是可以在函数体内部调用自身,
// 二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)
var f = function f() {}; // 也非常常见
// {}结束后加分号,函数的声明,不需要加 - Function构造函数
var add = new Function( 'x', 'y', 'return x + y' );
// 等同于
function add(x, y) {
return x + y;
}// 上面代码中,Function构造函数接受三个参数,除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数。
var foo = new Function(
'return "hello world";'
);// 等同于
function foo() {
return 'hello world';
}// Function构造函数可以不使用new命令,返回结果完全一样。总的来说,这种声明函数的方式非常不直观,几乎无人使用。
- function命令
- 函数重复声明会覆盖前面的声明,而且函数名的提升机制(将函数视作变量,所以和变量相同的提升机制,将其放置到代码头部),致使一旦后面有覆盖的声明,前面在任何使用的时候都是被覆盖的,前面的声明完全无效;
// 不会报错 f();
function f() {}
// 但是,如果采用赋值语句定义函数,JavaScript 就会报错。
f();
var f = function (){};
// TypeError: undefined is not a function// 等同于
var f;
f();
f = function () {};// ~~~~~
var f = function () {
console.log('1');
}function f() {
console.log('2');
}
// 表面上后面声明的函数f,应该覆盖前面的var赋值语句,但是由于存在函数提升,实际上正好反过来。
f() // 1 - 函数继承的属性:
- name,获取函数名称
- length,返回函数预期传入的参数个数,即函数定义之中的参数个数。
- toString()方法,返回一个字符串,内容为函数的源代码;对于那些原生的函数,toString()方法返回function(){[native code]}。
- slice()方法,切割想要的部分;左闭右开。
- join()方法,用于将数组中的所有元素放入一个字符串中;有参数,参数即表示数组中的所有元素的间隔样式
- 函数内部也有变量的提升(局部变量):不管变量声明在哪里,都会被提升到函数体的头部
- 函数的参数不是必须的,JS允许省略参数;需要注意的是,函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数。但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。
function f(a, b) { return a; }
f( , 1) // SyntaxError: Unexpected token ,(…)
f(undefined, 1) // undefined - 如果有同名的参数,则取后面出现的那个值
function f(a, a) { console.log(a); }
f(1, 2) // 2
// 取值的时候,以后面的a为准,即使后面的a没有值或被省略,也是以其为准
function f(a, a) {
console.log(a);
}f(1) // undefined
// 如果要获得第一个a的值,可以使用arguments对象。
function f(a, a) {
console.log(arguments[0]);
}f(1) // 1
-
arguments对象:包含了函数运行时的所有参数,arguments[0]表示第一个参数,一次arguments[1]...这个对象只能在函数体内部才可以使用
var f = function (one) { console.log(arguments[0]); console.log(arguments[1]); console.log(arguments[2]); }
f(1, 2, 3)
// 1
// 2
// 3
// 由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。// 正常模式下,arguments对象可以在运行的时候修改
var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}f(1, 1) // 5
// 严格模式下,arguments对象与函数参数不具有联动关系。也就是说,修改arguments对象不会影响到实际的函数参数。
var f = function(a, b) {
'use strict'; // 开启严格模式
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}f(1, 1) // 2
// 通过arguments对象的length属性,可以判断函数调用时到底带几个参数。
function f() {
return arguments.length;
}f(1, 2, 3) // 3
f(1) // 1
f() // 0// 让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。
var args = Array.prototype.slice.call(arguments);// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
} - callee属性:arguments对象带有一个的属性,返回它所对应的原函数
var f = function () { console.log(arguments.callee === f); }
f() // true
// 可以通过arguments.callee,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。
- 闭包:闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。
function createIncrementor(start) { return function () { return start++; }; }
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7 - 闭包的另一个用处,是封装对象的私有属性和私有方法。
function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; }
return {
name: name,
getAge: getAge,
setAge: setAge
};
}var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25
// 注意 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。 - 立即调用的函数表达式(IIFE):
(function(){ /* code */ }()); // 或者 (function(){ /* code */ })();
只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
- eval()方法:命令接受一个字符串作为参数,并将这个字符串当作语句执行。如果参数字符串无法当作语句运行,那么就会报错。
eval('var a = 1;'); a // 1 eval('3x') // Uncaught SyntaxError: Invalid or unexpected token
// 放在eval中的字符串,应该有独自存在的意义,不能用来与eval以外的命令配合使用。举例来说,下面的代码将会报错。
eval('return;');
// Uncaught SyntaxError: Illegal return statement
// return语句必须在函数中才能使用// 如果eval的参数不是字符串,那么会原样返回。
eval(123) // 123// eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。
var a = 1;
eval('a = 2');a // 2
// JavaScript 规定,如果使用严格模式,eval内部声明的变量,不会影响到外部作用域。
(function f() {
'use strict';
eval('var foo = 123');
console.log(foo); // ReferenceError: foo is not defined
})()// 不过,即使在严格模式下,eval依然可以读写当前作用域的变量。
(function f() {
'use strict';
var foo = 1;
eval('foo = 2');
console.log(foo); // 2
})()
但是,如果采用赋值语句定义函数,JavaScript 就会报错