# JavaScript基础知识点
# 数据类型
- 原始类型有
null,undefined,string,number,boolean,symbol,bigInt
所以
null基本类型就不是object,虽然typeof null会输出object是因为历史遗留。在JS的最初版本中使用的是32位系统,为了性能考虑使用低位存储变量的类型信息,000开头代表是对象,然而null表示为全零,所以将它错误的判断为object。
- 除了原始类型其他都是对象类型
原始类型存储的是值,对象类型存储的是地址(指针)。当创建一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)
const b = []
TIP
对于常量b来说,假设内存地址为#001,那么在地址#001的位置存放了值[],常量b存放了地址#001。
undefined典型用法- 变量被声明了,但没有赋值时,就等于
undefined。 - 调用函数时,应该提供的参数没有提供,该参数等于
undefined。 - 对象没有赋值的属性,该属性的值为
undefined。 - 函数没有返回值时,默认返回
undefined。
- 变量被声明了,但没有赋值时,就等于
undefined和null的区别:
在 JavaScript 中,null 和 undefined 都表示没有值的情况,但它们具有不同的含义。
undefined表示声明了一个变量,但没有给它赋值。未初始化的变量默认值为undefined。当函数没有返回值时,返回值为undefined。在访问对象上不存在的属性时,返回值也是undefined。null表示一个值被明确地定义为空值。可以将其赋给任何类型的变量,如对象、数组等,表示这个变量不引用任何对象或数组元素。
在使用时,通常情况下:
- 如果想要表示一个变量没有被赋值,就可以将其初始化为
undefined; - 如果想要表示一个变量明确地为空,就可以将其赋值为
null。
需要注意的是,在比较时,null 和 undefined 的比较需要使用严格相等运算符(===),因为它们有不同的数据类型。例如,undefined == null 返回 true,但是 undefined === null 返回 false。
所以把一个对象赋值给一个变量的时候,这个时候赋值的其实是地址,所以当我们进行数据修改的时候,就会修改存放在地址上的值,也就导致两个变量的值都发生改变
# 值传递和引用传递
当给函数传递基本数据类型的参数时,函数内部修改该参数不会导致参数本身发生变化,这就是按值传递的意思。那么当参数类型为引用类型的时候,就是按引用传递参数了吗?并不是!!当函数参数是引用类型时,我们同样将参数复制了一个副本到局部变量,只不过复制的这个副本是指向堆内存中的地址而已,我们在函数内部对对象的属性进行操作,实际上和外部变量指向堆内存中的值相同,但是这并不代表着引用传递。
let obj = {};
function changeValue(obj){
obj.name = 'linjiaheng';
obj = {name:'kobe'};
}
changeValue(obj);
console.log(obj.name); // linjiaheng
2
3
4
5
6
7
所以记住:函数参数传递的并不是变量的引用,而是变量拷贝的副本,当变量是原始类型时,这个副本就是值本身,当变量是引用类型时,这个副本是指向堆内存的地址。
函数传递对象参数其实是相当于接受实参的隐式引用,在函数内部修改传进去的对象属性,会导致传入的对象变化。
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person
}
const p1 = {
name: 'yck',
age: 25
}
const p2 = test(p1)
console.log(p1) // -> ?
console.log(p2) // -> ?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

TIP
实参不是对象时就是按值传递,函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。
- 函数参数为基本数据类型时,函数体内复制了一份参数值,任何操作都不会影响原参数的实际值。
- 函数参数是引用类型时,当在函数体内修改这个值的某个属性值时,将会对原来的参数进行修改。
- 函数参数是引用类型时,如果直接修改这个值的引用地址。比如
person={...}新的属性值赋予给对象。则相对于在函数体内新建了一个引用,任何操作都不会影响到原来参数的实际值。
# 判断数据类型
typeof:null、函数、数组都可以判断为对象object,所以不能准确判断变量到底是什么类型。
var str1 = 123
console.log(typeof str1 === 'number') // true
console.log(typeof Array) // 'function'
console.log(typeof {}) // 'object'
console.log(typeof function name(){console.log(222)}) // 'function'
// function /object
function fun1(){};
const fun2 = function(){};
const fun3 = new Function('name','console.log(name)');
const obj1 = {};
const obj2 = new Object();
const obj3 = new fun1();
const obj4 = new new Function();
console.log(typeof Object);//function
console.log(typeof Function);//function
console.log(typeof fun1);//function
console.log(typeof fun2);//function
console.log(typeof fun3);//function
console.log(typeof obj1);//object
console.log(typeof obj2);//object
console.log(typeof obj3);//object
console.log(typeof obj4);//object
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

所有
Function的实例都是函数对象,其他的均为普通对象,其中包括Function实例的实例。
instanceof:判断的原理是根据原型链
function Person() {
}
let p1 = new Person()
console.log(p1 instanceof Person) // true
console.log(p1 instanceof Object) // true
console.log(Person instanceof Object) // true
console.log(Person instanceof Function) // true
console.log(Object instanceof Function) // true
2
3
4
5
6
7
8
9
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
2
3
4
5
对于原始数据类型要直接使用
instanceof是行不通的,为啥第二个例子就可以呢?是因为这个时候已经使用new String强制转换成String对象类型。instanceof后面跟着是构造函数,typeof和基本类型比较
class PrimitiveString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('hello world' instanceof PrimitiveString) // true
2
3
4
5
6
toString:最完美的方案
function checkType(payload) {
switch(Object.prototype.toString.call(payload)) {
case '[object Null]': console.log('null')
break;
case '[object Number]': console.log('number')
break;
case '[object String]': console.log('string')
break;
case '[object Undefined]': console.log('undefined')
break;
case '[object Boolean]': console.log('boolean')
break;
case '[object Array]': console.log('Array')
break;
case '[object Object]': console.log('Object')
break;
case '[object Number]': console.log('NaN')
break;
// 还有其他类型Map Symbol等不一一列举
}
}
checkType({a:2}) // Object
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
TIP
同样是检测对象 obj 调用 toString 方法,obj.toString() 的结果和 Object.prototype.toString.call(obj) 的结果不一样,这是为什么?
这是因为 toString 是 Object 的原型方法,而 Array 、function 等类型作为 Object 的实例,都重写了 toString 方法。。不同的对象类型调用toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法( function 类型返回内容为函数体的字符串,Array 类型返回元素组成的字符串…),而不会去调用 Object 上原型 toString 方法(返回对象的具体类型),所以采用 obj.toString() 不能得到其对象类型,只能将 obj 转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 原型上的 toString 方法。
constructor:原型prototype的一个属性,null和undefined没有这个属性所以无法判断类型。
有两个作用,一是判断数据的类型,二是对象实例通过对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,就不能判断数据类型了。
var o = ''
console.log(o.constructor === String) // true
console.log(o.constructor.name) // String
// 改变constructor
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor === Fn); // false
console.log(f.constructor === Array); // true;
2
3
4
5
6
7
8
9
10
11
12
# 类型转换
- 转换的情况:
- 转换为布尔值
- 转换为数字
- 转换为字符串
- 转换为布尔值
在条件判断时,除了
undefined,null,false,NaN,'',0,-0,0n(bigint类型)其他都转为true在利用Boolean()方法强类型转换时,只有false,空字符串,0/NaN,null,undefined会转换为false。其余都转为true
Boolean([]) // true
Boolean({}) // true
2
Number类型
- 八进制:前面为
0,如070(八进制56)。如果其余数值超过7,则忽略前面的0直接解析为十进制数值
console.log(079) // 79
- 十六进制:前面跟着
0x其余为(0~9及A~F) - 可以使用
isFinite()函数判断参数位于最小Number.MIN_VALUE和最大值MAX_VALUE之间,是则返回true
数值转换
Number()函数
Boolean-->true转为1,false转为0- 数值则简单传入传出
null-->0undefined-->NaN
- 字符串
- 字符串只包含数字(包括正负,八进制)全部转为十进制的
- 字符串包含浮点,则转为对应的浮点值,同样忽略八进制前导
0 - 字符串包含有效的十六进制则转为相对应的十进制数值
- 空字符串则转为
0 - 其他情况则转为
NaN
- 对象: 调用对象的
valueOf()(返回对象的原始值),然后依照规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,然后再次依照前面的规则转换返回的字符串值。
let obj={ value:'你好啊', num:2, toString:function(){ return this.value }, valueOf:function(){ return this.num }, } console.log(obj+'明天') //2明天 console.log(obj+1) // 3 console.log(String(obj)) // 你好啊1
2
3
4
5
6
7
8
9
10
11
12
13当对象进行类型转换时:1.首先调用
valueOf,如果执行结果是原始值,返回,如果不是下一步。2.其次调用toString,如果执行结果是原始值,返回,如果不是,报错。特殊情况:当使用显示类型转换成String时,执行顺序则是先调用toString,其次调用valueOfTIP
Number(['1'])转为1。先调用valueOf转成['1']还不是原始值,则调用toString转成'1'。'true' == true //false Number('true')->NaN Number(true)->1toFixed(num):toFixed()方法可把Number四舍五入为指定小数位数的数字; 参数num: 代表小数位数。返回的是字符串格式了'' == null为false。{}+10返回10。10+{}返回10[object Object]
parseInt()函数,解析字符串,并返回一个整数
- 忽略字符串前面的空格,直到找到第一个非空格字符。如果第一个字符非数值或者负号,就会返回
NaN。也就是说空字符串转为NaN,而不是0 - 如果第一个字符为数值,则继续解析后面的字符,直到遇到非数值字符。
parseInt('123blue') // 123 parseInt('1231.11') // 1231 小数点为非数值字符 parseInt('abc123') // NaN1
2
3- 能够有效识别前导,
0x识别为十六进制然后转为十进制。八进制的时候则忽略前面的0
parseInt('0x11') // 17 parseInt('011') // 11 而不是9 IE下才是9,十进制 parseInt(011) // 这才是9,011解析成字符串'011'识别为八进制 parseInt('011',2) // 3 parseInt(011,2) // 011->9,二进制只有0/1,所以NaN parseInt(111,2) // 7 parseInt(8,3) // 表示在基数3中“解析"8"。但是,在基体3中,1位数字只是0,1和2。由于没有有效字符,返回NaN 。 parseInt(16, 3) //要求它在基数3中解析"16"。因为它可以解析1,它会解析,然后它在6处停止,因为它无法解析它。 所以它返回1 。 parseInt(020,10) // 16 即parseInt('020',8)1
2
3
4
5
6
7
8
9没有忽视
0真正转为八进制是ES3下,忽略0的是ES5。ES5的引擎下该函数不具解析八进制的能力了。所以该函数才提供了第二个参数转换时使用的基数var num = parseInt('010',8) console.log(num) // 81
2TIP
- 如果指定基数为
16,则字符串不用带0x。如果参数radix小于2或者大于36,并且第一个参数数值不能大于第二个基数,这样才能有效进行进制转换,否则parseInt()将返回NaN。
parseInt('111', 0) //111 返回第一个参数的number类型1- 如果忽略或者为
0,那么数字使用十进制表示的。
parseInt('111') // 1111- 如果数字是以
0x或0X开头的那么数字使用十六进制表示。 - 待考察
parseInt(0.5); // -> 0 parseInt(0.05); // -> 0 parseInt(0.005); // -> 0 parseInt(0.0005); // -> 0 parseInt(0.00005); // -> 0 parseInt(0.000005); // -> 0 parseInt(0.0000005); // -> 51
2
3
4
5
6
7parseFloat()解析一个字符串并返回一个浮点数
- 从第一个字符解析,遇到无效的浮点数字字符就终止解析。如第二个小数点。
- 始终忽略前导
0 - 只解析十进制,因此没有第二个参数作为基数。
- 十六进制格式的字符串始终解析成
0
parseFloat('0x11') // 01- 字符串如果为可以解析成整数的,则会返回整数
parseFloat('123blue') // 123 parseFloat('qwar4s2') // NaN1
2
3
String类型
转为字符串
toString()(null和undefined没有这个方法)
该方法属于对象
Object方法,由于所有对象都继承了Object的对象实例。所以Array/Boolean/Date/Error/Function/Number/String都能调用该方法。调用数值的该方法时,传入一个参数作为基数,可以返回规定进制的字符串值。没有参数就默认十进制呗
var num = 10
num.toString(16); // 'a' a的十六进制为10
num.toString(8); // '12'
// 引擎中这样执行,因为num基本数据类型没有toString方法
var num = new Number(10);
num.toString(); // '10'
num = null;
2
3
4
5
6
7
8
上述代码是JS引擎自动执行的,你无法访问
num对象,它只存在于代码的执行瞬间,然后立即销毁。
null和undefined因为没有相应对构造函数,所以没有toString()方法,则用String()方法转为字符串
console.log(String(null)) // 'null'
- 在js中数字后面的
'.'操作符意义不确定。因为可能是一个浮点数的标志也可能是取一个对象的属性的运算符。但是js的解释器把它当成了浮点数的标志。
console.log(10.toString())// Uncaught SyntaxErro: Invalid or unexpected token
console.log(10..toString()) // '10'
console.log((10.).toString) // '10'
2
3
# 运算符
++和--
递增和递减操作符,放在数值前面后面结果是截然不同的。放前面是:先进行递增或递减再进行运算;放后面是先进行运算,再进行递增或递减。
var age = 22
var herAge = 23
// var all = ++age + herAge
// console.log(all) // 46
var twoAll = age-- + herAge
console.log(twoAll) // 45
console.log(age) // 21
2
3
4
5
6
7
- 递增递减运算符用在布尔值,字符串,浮点数值和对象的情况:
- 包含有效数值的字符串-->转为数字值-->再递增或递减
- 不包含有效数值-->
NaN - 布尔值-->
1/0-->再递增或递减 - 浮点-->执行加减1操作
- 对象--> 调用对象
valueOf()方法-->进行转换,若为NaN;则利用toString()方法继续。解析对象原始值转换
+特别情况
- 两方都为字符串,则形成字符串拼接
- 运算中其中一方为字符串,那么就会把另外一方也转换为字符串
- 如果一方是对象则调用
valueOf/toString方法取得值,将其转换为基本数据类型再进行字符串拼接 - 其他情况都会转为
number类型,undefined转为NaN - 如果是
Infinity+Infinity则结果为Infinity - 如果是
-Infinity + (-Infinity)则结果是-Infinity - 如果是
Infinity + (-Infinity)则为NaN
4+[1,2,3] // 41,2,3
4+{} // 4[object Object]
2
- 还得注意这种情况:
'a'+ +'c' // aNaN
undefined+10 //NaN undefined转为NaN
console.log({}+true) // [object Object]true
{}+true // 1 浏览器控制台
" \t \n" - 2 // -2
'1'+1n // '11' 比较特殊字符串和BigInt相加,BigInt转换为字符串
1+1n // 错误,不能把数值和BigInt相加
2
3
4
5
6
7
8
9
10
11
字符串转换为数字时,会忽略字符串的开头和结尾处的空格字符。在这里,整个字符串由空格字符组成,包括
\t、\n以及它们之间的“常规”空格。因此,类似于空字符串,所以会变为0。
- 一元操作符在数值前相当于正负,若在对象,字符串,布尔值,浮点数前就会进行数值转换操作,规则和上述一样。
- 除了加法运算符,其他运算符只要其中一方为数字,另外一方则转为数字。
- 乘性运算符:乘法,除法,求模。
参与乘性计算的某个操作符不是数值,则后台利用
Number()转型函数转换为数值。
0被0除结果为NaN,7/0为Infinity,Math.pow(10, 1000)也为Infinity
求模中被除数为
0,则结果为0
- 比较运算符
- 如果一个操作数为数值,则将另外一个转换为数值
- 如果一个操作数为布尔值,则也转换为数值
- 相等操作符
null==undefined。这两个基本类型数据不能转换为其他值,即如果其中一个操作值是null/undefined,那么另一个操作符必须为null/undefined,才会返回true,否则都是false。- 两个操作数都为对象,就要看两个对象是否同一个。(地址是否指向同一个)
- 判断两者类型是否为
string/number,是的话就会将字符串转换为number - 判断其中一方是否为
boolean,是的话就会把boolean转为number再进行判断。 - 判断其中一方是否为
object且另一方为string/number/symbol,是的话就会把object转为原始类型再进行判断。 - 如果其中一个操作数为
Symbol类,那么返回false
[] == ![] // true 都转为0。后者![]是先布尔类型转换,转换为false,然后数值转换为0
'' == !'' // false 前者转为0后者为1
var a = [0];
if (a) {
console.log(a == true); // false
} else {
console.log(a);
}
2
3
4
5
6
7
8
9
- 逗号操作符
- 用于声明多个变量
- 用于赋值, 逗号操作符只返回最后一个操作符的值
var num = (1,2,3,4) // num值为4 最后一个
- 尾逗号
[,] + [,]; // -> ""
[] + [] === [,] + [,]; // -> true
[,,,] + [,,,]; // -> ",,,,"
([,,,] + [,,,]).length === [,,,,].length; // -> true
2
3
4
TIP
不合法的尾后逗号
- 仅仅包含逗号的函数参数定义或者函数调用会
- 当使用剩余参数的时候,并不支持尾后逗号
- 解构时使用剩余参数也会抛出错误
- JSON格式
function f(,) {} // SyntaxError: missing formal parameter
(,) => {}; // SyntaxError: expected expression, got ','
f(,) // SyntaxError: expected expression, got ','
function f(...p,) {} // SyntaxError: parameter after rest parameter
(...p,) => {} // SyntaxError: expected closing parenthesis, got ','
var [a, ...b,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma
//JSON不允许尾后逗号
JSON.parse('[1, 2, 3, 4, ]');
JSON.parse('{"foo" : 1, }');
// SyntaxError JSON.parse: unexpected character
// at line 1 column 14 of the JSON data
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 布尔操作符
- 如果操作数为一个对象(空数组也是),则逻辑非结果为
false
- 逻辑与在有一个操作数不是布尔值的情况下
- 第一个操作数是对象,则返回第二个操作数;
- 第二个操作数为对象,只有第一个操作数为
true时,才会返回该操作数 - 如果两个都为对象,则返回第二个
- 如果有一个操作数为
null,NaN,undefined,则返回null,NaN,undefined
- 逻辑或在有一个操作数不是布尔值的情况下
- 如果第一个操作数为对象,则返回第一个操作数
- 如果第一个操作数为
false,返回第二个操作数 - 如果两个操作数都为对象,返回第一个操作数
- 如果两个操作数都为
null,NaN,undefined,则返回null,NaN,undefined
??操作符(零合并操作符)
如果第一个参数不是
null/undefined,这个运算符返回第一个参数,否则返回第二个。
null??5 // 5
3??5 //3
2
??=(逻辑空值赋值运算符)
var x = null
var y = 5
console.log(x ??= y)
// => 5
console.log(x = (x ?? y))
// => 5
2
3
4
5
6
7
?.可选链操作符
可选的链式操作符
?.允许开发人员读取深嵌在对象链中的属性值,而不必显式验证每个引用。当一个引用为空时,表达式停止计算并返回一个未定义的值。
var travelPlans = {
destination: 'DC',
monday: {
location: 'National Mall',
budget: 200
}
};
const tuesdayPlans = travelPlans.tuesday?.location;
console.log(tuesdayPlans) // => undefined
2
3
4
5
6
7
8
9
TIP
现在就可以利用可选链操作符解决cannot read property of undefined的一个常见错误。
传统解决方法就是:
- 通过
&&短路运算符来进行嗅探 - 通过
||设置默认保底值 - 利用
try...catch
var result
try {
result = obj.user.posts[0].comments
} catch {
result = null
}
2
3
4
5
6
# 运算符优先级
关联性决定了拥有相同优先级的运算符的执行顺序。
a OP b OP c;
左关联(左到右)相当于把左边的子表达式加上小括号
(a OP b) OP c,右关联(右到左)相当于a OP (b OP c)。赋值运算符是右关联的。
a = b =5
结果
a和b的值都会成为5。这是因为赋值运算符的返回结果就是赋值运算符右边的那个值,具体过程是:b被赋值为5,然后a也被赋值为b=5的返回值,也就是5。
下表将所有运算符优先级从高(21)到低0 





# 基本包装类型
实际上每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。
var s1 = 'some text'
var s2 = s1.substring(2)
2
TIP
引用类型与基本包装类型主要区别是对象生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一代代码的执行瞬间,然后立即销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。
- 对基本包装类型的实例调用
typeof会返回object,而且所有基本包装类型的对象在转换布尔值类型都为true Object构造函数会像工厂函数一样,创造基本包装类型的实例。也可以使用new调用基本包装类型(不过不推荐这种方法)。
var obj = new Object('test')
console.log(obj instanceof String) // true
var value = '25'
var obj2 = new Number(value)
console.log(typeof obj2) // object
2
3
4
5
6
# String类型
基于子字符串创建新字符串的方法:第一个参数指定子字符串开始位置;第二个参数表示哪里截至(可选)。
substr()方法第二个参数表示返回字符串字符的个数。slicesubstringsubstr传递负值参数时。slice会将负值和长度相加,substr会将第一个负值和长度相加,第二个转为0;substring会将所有负值参数都转为0
trim():删除前后空格符,返回一个字符串副本。trimLeft/trimRightcharCodeAt():获取字符编码。
# 位操作符
位操作符用于最基本的层次上,即按内存中表示数值的位来操作数值。对于有符号的整数,32位中的前31位用于表示整数的值。第32位用于表示数值的符号:0表示正数,1表示负数。负数同样用二进制存储,但使用的格式是二进制补码。
- 计算一个数的二进制补码:
- 求这个数值绝对值的二进制码
- 求二进制反码,即
0替换为1,1替换为0 - 得到的二进制反码加
1
~:按位非(本质:操作数的负值减1)
&:按位与
| 第一个数值位 | 第二个数值位 | 结果 |
|---|---|---|
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
|:按位或:
| 第一个数值位 | 第二个数值位 | 结果 |
|---|---|---|
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
^:按位异或:
| 第一个数值位 | 第二个数值位 | 结果 |
|---|---|---|
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
和按位或的区别是两位操作数对应的数值位的数值要不相同才为
1,相同则结果为0
<<:左移
左移时右边会出现空位就用
0来填充,以便得到的结果是完整的32位二进制数。左移不会影响符号位
>>:有符号的右移
这个操作符将数值向右移动,但保留符号位。有符号位的右移操作和左移操作恰好相反。右移左边出现的空位会用符号位上的值来填充
>>>:无符号右移
将
32位都向右移动。对正数来说该操作和有符号右移相同。对负数来说无符号右移是用0来填充空位了。其次,无符号右移操作符会把负数的二进制码当成正数的二进制码。而且,由于负数以其绝对值的二进制补码形式表示,因此就会导致无符号右移后结果非常之大。