js笔记(阮一峰JavaScript)
复习一下js es5部分
后面预计看 你不知道的js & 红宝书 然后补充一部分吧
分了两类,坑和细节,坑是我认为不合理的部分,而细节则是相对合理的但是需要记忆的部分
坑
提升
变量提升
eg1
console.log(a);//undefined
//要注意这里既然是undefined,至少说明在执行这句之前声明过了a,也就是提升
//否则应该报错 a is not defined
var a = 1;
eg2
function
命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。
f();
function f() {
console.log("成功调用f");
}
console.log(f);
//成功调用f
//[Function:f]
采用赋值语法,那就报错
f();
var f = function () {
console.log("成功调用f");
};
console.log(f);
//TypeError: f is not a function
//f现在只是undefined
//等价于
var f;
f();
f = function () {};
函数提升
var f = function () {
console.log('1');
}
function f() {
console.log('2');
}
f()
想想调用f输出的是什么呢?
答案是1,这就是函数提升
js执行起来实际是这样的:
var f;
function f() {
console.log("2");
}
f = function () {
console.log("1");
};
就算在函数的重复声明中,也有函数提升现象,具体体现为前一次的声明总是无效的
function f() {
console.log(1);
}
f() // 2
function f() {
console.log(2);
}
f() // 2
还不是很理解的话参考这篇博客吧
http://www.noobyard.com/article/p-zboayved-dx.html
奇怪的注释
x = 1; <!-- x = 2;
--> x = 3;
-->
只有在行首,才会被当成单行注释,否则会当作正常的运算
进制 & parseInt
需要注意的是现在使用前导零的写法已经会报错辣,所以这里如果觉得不考的话其实不重要
进制
前导0表示八进制,处理时很容易造成混乱。ES5 的严格模式和 ES6,已经废除了这种表示法(好似),但是浏览器为了兼容以前的代码,目前还继续支持这种表示法。(确实能跑)
parseInt
坑点
(如果字符串以0x
或0X
开头,parseInt
会将其按照十六进制数解析。)
如果字符串以0
开头,将其按照10进制解析。是的,不是八进制
如果传一个以0
开头的数字,会先把该数字转为字符串再解析,而该转化过程是八进制
详情见下:
第二个参数
2~36之间是预期接收的参数
但是:
//超出范围的算错
parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
//0算没传,为默认的10进制
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10
这会导致一些令人意外的结果
前置知识:合理
如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN
。
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)
//String这里是用8进制把11转成了9
// 等同于
parseInt(String(9), 2)
对于那些会自动转为科学计数法的数字,parseInt
会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。
parseInt(1000000000000000000000.5) // 1
// 等同于
parseInt('1e+21') // 1
parseInt(0.0000008) // 8
// 等同于
parseInt('8e-7') // 8
合理的一些细节:
正常遇到小数会(先转成string)返回小数点前的数字
遇到±也是认识的,正数转了之后不带+号
isNaN
不用它,用NaN不等于自身的特性判NaN会更好
function myIsNaN(value) {
return value !== value;
}
isNaN的坑
对不是数值的值,会先调Number(),然后再调isNaN(),导致一些预期之外的结果
isNaN('Hello') // true
// 相当于
isNaN(Number('Hello')) // true
//因此
isNaN({}) // true
isNaN(['xzy']) // true
//不过 由于这些Number()可以转成数字,这里就为false
isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false
with
with区块没有改变作用域引出的坑
var obj = {};
with (obj) {
p1 = 4;//没有对p1赋值,而是创建了全局变量p1
p2 = 5;
}
obj.p1 // undefined
p1 // 4
建议不使用with,使用临时变量代替with的功能
eval
不用 / 少用eval
eval
的问题
安全性问题:你的字符串可能会被注入其他的命令或者第三方脚本。
可调试问题:很难去调试错误信息。
压缩问题:程序不会对字符串进行最小化压缩。
通常情况下,eval
最常见的场合是解析 JSON
数据的字符串,不过正确的做法应该是使用原生的JSON.parse
方法。
var str = '{"name":"sayoriqwq"}';//待解析字符串
// eval写法
// eval("(" + str + ")");
//Function替代
function myEval(fn){
var Fn = Function;//让Fn指向函数
return new Fn('return ' + fn)
}
//myEval("(" + str + ")")
//正解
JSON.parse(str)
console.log(str);//{"name":"sayoriqwq"}
使用Function
代替eval
:(可以看完eval
的用法之后对比这个代替的办法的优点)
执行语句:
var code = 'console.log("Hello")';
Function(`"use strict";${code}`)();
//hello
console.log(Function('return 1+2*3')());//7
声明变量(无论是否严格,都只在Function域生效)
var code = 'var a = 123;console.log(a)';
Function(`"use strict"; ${code}`)();
// console.log(a);这里不加注释的话,会报错 a is not defined,证明我们通过code创建的变量a并没有影响到外部作用域
// 123 最后会输出123,内部的语句能正常输出a的值
修改外部变量(无论是否严格,都无法影响到外部变量):
var b = 1234
var code = 'b = 1';
Function(`"use strict"; ${code}`)();
//Function里等号处报错,b is not defined
关闭严格模式:
var b = 1234
var code = 'b = 1;console.log(b)';
Function(`${code}`)();
console.log(b);
//1 内部使用了在Function作用域的b变量,相当于省略了var语法而已
//1234 但是外界的b并未受到影响
这才是一个安全的方法
基础
eval
命令接受一个字符串作为参数,并将这个字符串当作语句执行。
但是它没有自己的作用域,因此可能会修改当前作用域的变量的值,造成安全问题。
JavaScript 规定,如果使用严格模式,eval
内部声明的变量,不会影响到外部作用域。
(function f() {
'use strict';
eval('var foo = 123');
console.log(foo); // ReferenceError: foo is not defined
//如果不使用严格模式,则可以正常输出123
})()
但是,即使在严格模式下,eval
依然可以读写当前作用域的变量,甚至改写外部变量
var qwq = 1234;
(function f() {
"use strict";
var foo = 1;
eval("foo = 2;qwq = 1");
console.log(foo); // 2
})();
console.log(qwq);//1
别名调用
var m = eval;
m('var x = 1');
x // 1
上面代码中,变量m是eval
的别名。静态代码分析阶段,引擎分辨不出m('var x = 1')
执行的是eval
命令。
为了保证eval
的别名不影响代码优化,JavaScript 的标准规定,凡是使用别名执行eval
,eval
内部一律是全局作用域。
var a = 1;
function f() {
var a = 2;
var e = eval;
e('console.log(a)');
}
f() // 1
上面代码中,eval
是别名调用,所以即使它是在函数中,它的作用域还是全局作用域,因此输出的a
为全局变量。这样的话,引擎就能确认e()不会对当前的函数作用域产生影响,优化的时候就可以把这一行排除掉。
eval
的别名调用的形式五花八门,只要不是直接调用,都属于别名调用,因为引擎只能分辨eval()
这一种形式是直接调用。
eval.call(null, '...')
window.eval('...')
(1, eval)('...')
(eval, eval)('...')
上面这些形式都是eval
的别名调用,作用域都是全局作用域。
细节
typeof & instanceof
typeof不能判断null,object,array,能识别函数
console.log(typeof [1,2])//object
console.log(typeof null)//object
类型转换
空数组([]
)和空对象({}
)对应的布尔值,都是true
标识符
合法:
arg0
_tmp
$elem
π
不合法:
1a // 第一个字符不能是数字
23 // 同上
*** // 标识符不能包含星号
a+b // 标识符不能包含加号
-d // 标识符不能包含减号或连词线
在对象中不符合标识符的键名也是可以取的,不过需要加上''
,取值的时候使用[]
标签
top:
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (i === 1 && j === 1) continue top;
console.log('i=' + i + ', j=' + j);
}
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// 如果不加标签,单continue会多出这一句
// i=1, j=2
// i=2, j=0
// i=2, j=1
// i=2, j=2
undefined & null & NaN
转为数值时
null:空对象 => 0
undefined:此处无定义 => NaN
NaN:表示非数字,但是类型为number
转为布尔值时
都是false
undefined
null
false
0
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
NaN
//NaN为数字类型
console.log(typeof NaN);//number
//NaN不等于任何值
NaN === NaN // false
Infinity
溢出是无穷,未定式/错误的运算 是NaN
其他没有列举的计算也都是符合常识的
Infinity - Infinity // NaN
Infinity / Infinity // NaN
0 * Infinity // NaN
0 / 0 //NaN
null
作运算时,会转为0
Infinity
与undefined
计算,返回的都是NaN
±0
唯一的区别:
(1 / +0) === (1 / -0) // false (+Infinity !== -Infinity)
精度
-2^53 ~ 2^53
即:
JavaScript 对15位的十进制数都可以精确处理
parseFloat & Number
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
string
-
字符串内部的单个字符无法改变和增删,这些操作会默默地失败。
-
javascript对UTF-16的支持不完整,因此,JavaScript 返回的字符串长度可能是不正确的
例如:对于四字节字符
𝌆
,浏览器会正确识别这是一个字符,但是 JavaScript 无法识别,会认为这是两个字符。
console.log('𝌆'.length)//2
object
表达式还是语句?
{}
里面的一律解释为代码块
如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
delete
-
删除一个不存在的属性,
delete
不报错,而且返回true
(所以不能用delete返回true确定该属性存在) -
delete
命令只能删除对象本身的属性,无法删除继承的属性对于无法删除的属性,虽然
delete
命令返回true
,但该属性并没有被删除var obj = {}; delete obj.toString // true obj.toString // function toString() { [native code] }
function & 作用域 & 闭包
name
功能:用于获取参数函数的名字
var f3 = function myName() {};
f3.name // 'myName'
//当然真正的函数名还是f3
//myName这个名字只在函数体内部可用(作用域)
作用域
注意函数作为参数传的时候的作用域问题
不过也仅限考题了,还是多用let
比较好
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;//在开发时会提示,已声明a,却从未读取a的值
//debug方案:1、去掉var,就改全局变量a 2、把这个a作为参数传给x,让x处理参数
x();
}
f() // 1
参数
-
如果一定要省略靠前的参数,只有显式传入
undefined
-
同名取最后值
-
值传递和引用传递(地址)
在函数内部修改
obj
的属性p
,会影响到原始值。在函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。(重新对地址赋值,自然不会影响到保存在原地址上的值)
-
f.length
是预期参数,arguments.length
是实际调用传了多少参数
arguments
-
伪数组
转化:
//silce Array.prototype.slice.call(arguments) //for逐一push 略
-
严格模式下,
arguments
对象与函数参数不具有联动关系。也就是说,修改arguments
对象不会影响到实际的函数参数。
var f = function(a, b) {
'use strict'; // 开启严格模式
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
console.log(f(1,1));//严格:2 正常:5
arguments.callee
,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。
闭包
经典问题,不再赘述
闭包是指有权访问另一个函数作用域中变量的函数
闭包中的变量存储的位置是堆内存
这篇博客写的很清楚
https://juejin.cn/post/6937469222251560990?searchId=20230721172710B2C7563449355BDAAECF