JS 一些基本概念
- 数据类型
- 基本类型:
number、boolean、string、undefined、null - 引用类型:
array、object、function、
- typeof 返回 六 种值
number、boolean、string、object、function、undefined
注:返回的值均为string类型 - 六种情况下返回值为 false
false、0、undefined、null、""、NaN - 其他
null < 0、 null > 0、null == 0 // 均为 false
undefined < 0、undefined > 0、undefined == 0 // 均为 false
null == undefined // true
Number({}) // NaN
Number([]) // 0
[] == false // true
[] == ![] // true
// 因为引用值对比的是地址,所以以下均为 false
[] == true
{} == true
{} == {}
[] == []
[] == {}
运行逻辑
JS 运行三部曲:语法分析、预编译、解释执行
- 语法分析:通篇执行一遍,看有木有问题,有问题直接报错
语法问题报错:
Uncaught SyntaxError
逻辑问题报错:Uncaught ReferenceError - 预编译:( 预编译发生在函数执行的前一刻 )
- 创建 AO 对象;(AO:Activation Object 执行期上下文)
- 找到形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined
- 将实参值和形参统一
- 在函数体里面找函数声明,值赋予函数体
变量与函数
console.log(a);
var a = 123; // 将 var 变成 let 将如何?
结果:undefined、 报错 b is not defined
因为:变量是 声明提升;let 有自己单独的作用域,不存在声明提升,即 作用域死区
test()
function test() {
console.log(123)
}
结果:123
因为:函数声明整体提升
global = 100;
function fn() {
console.log(global);
global = 200;
console.log(global);
var global = 300; // 将 var 去掉将如何?
}
fn();
结果:undefined 和 200、100 和 200
因为:当函数 fn 中有 var 声明的 global 时,此时该函数可理解为已经是有 global 的一个小的作用域,全局变量的 global 不会影响函数内的值;如果将 var 去掉,那函数内外的 global 均属于全局,所以可被赋值和修改
function test(a) {
console.log(a)
}('1')
结果:1
因为:只有函数表达式加()才能执行,当为函数声明时加('1'),可当做执行 ('1'),函数声明无用。
+function test() {
console.log('a')
}()
var test = function() {
console.log('b')
}()
结果:a、b
因为:此两种均为函数表达式写法,所以加 () 可执行。
function fn(a) {
console.log(a);
var a = 123;
console.log(a);
function a(){};
console.log(a);
var b = function(){};
console.log(b);
function d(){};
};
fn(1);
结果:ƒ a(){}、123、123、ƒ (){}
因为:根据 JS 预编译过程可推得
预编译执行逻辑
// 第一步、第二步:
AO: {
a: undefined,
b: undefined,
}
// 第三步:
AO: {
a: 1,
b: undefined
}
// 第四步:
AO: {
a: function a(){},
b: function(){},
d: function d(){}
}
变量声明与函数声明冲突时
var c = 1;
function c(c) {
console.log(c);
var c = 3;
}
c(2);
结果:报错 c is not a function
因为:根据预编译执行逻辑,此时c已经赋值为1,所以不是一个函数。
计算问题和隐式转换
var a = 10, b = 20, c = 30;
++a;
a++;
e = ++a + (++b) + (c++) + a++;
结果:77
因为:++a = 11
a++ = 11
++a + (++b) + (c++) + a++ = 13 + 21 + 30 + 13
var foo = '11' + 2 - '1';
console.log(foo,typeof foo);
结果:111、number
var f = (
function f() {
return '1'
},
function g() {
return 2;
}
)();
console.log(typeof f);
结果:number
因为:逗号运算符,执行逗号后面的,所以变成了立即执行函数。
1 + true
1 + null
1 + undefined
'abc' > 'b'
'abc' > 'aab'
结果:2、1、NaN、false、true
var x = 1;
if (function f(){}) {
x += typeof f;
}
console.log(x);
结果:1undefined
因为:if 当中判断为true,typeof(f) 为字符串undefined,所以结果为 1 + 'undefined' = '1undefined'
typeof(a) && -true + (+undefined) + ''
结果:'NaN'
因为:'undefined' && -1 + 'NaN' + ''
'undefined' && 'NaN'
'NaN'
JS精度不准问题
0.1 + 0.2 == 0.3
0.14 * 100 == 14
0.3 - 0.2 == 0.1
结果:false、false、false
因为:javascript 精度不准,js可正常计数范围:前16位,后16位。
数组
如何判断一个变量类型时数组
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
typeof arr // object,typeof 是无法判断是否是数组的
// 方法一:
var arr = []
arr instanceof Array // true
// 方法二:
Object.prototype.toString.call([]) // [object Array]
// 方法三:
Object.prototype.constructor([]) // []
其他
let arr = [];
arr[0] = 0;
arr[1] = 1;
arr.foo = 'c';
console.log(arr.length);
结果:2
new Array(10)
+new Array(10)
结果:[empty × 10]、NaN
var obj = {
'2': 3,
'3': 4,
'length': 2, // 如果将 length变为 3则结果怎样?
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj)
结果:
{ 2: 1,3: 2,length: 4,
push: Array.prototype.push,
splice: Array.prototype.splice
} 、
{ 2: 3,3: 1,4: 2, length: 5,
push: Array.prototype.push,
splice: Array.prototype.splice
}
因为: push的时候,length的值对应key值。如果相同则覆盖,如果不同则累加。
JS 基本类型和引用类型赋值问题
var a = 1;
function foo(x) {
x = 2;
}
foo(a);
console.log(a);
结果:1
因为:JS 的基本类型,是按值传递的
var obj = {x : 1};
function foo(o) {
o.x = 3;
}
foo(obj);
console.log(obj.x);
结果:3
因为:JS 的引用类型,如果改变引用的值,则会改变其结果
var obj = {x : 1};
function foo(o) {
o = 100;
}
foo(obj);
console.log(obj.x);
结果:1
因为:JS 的引用类型,如果重新赋值,则不会改变其结果
let obj = {a: 0};
function fun(obj) {
obj.a = 1;
obj = {a: 2};
obj.b = 2;
}
fun(obj);
console.log(obj);
结果:{a: 1}
因为:JS 的引用类型,如果改变引用的值,则会改变其结果;如果重新赋值,则不会改变其结果( 此时 obj={a:2} 已经重新赋值相当于一个新的变量,和之前的入参没有关系 )
this
function test() {
console.log(this)
}
test();
new test()
结果:window 、test{}
因为:函数预编译过程 this 指向 window;
new 改变 this 指向,指向本身。
var name = '222';
var a = {
name: '111',
say() {
console.log(this.name)
}
}
var fun = a.say;
a.say();
fun();
var b = {
name: '333',
say(fun) {
fun()
},
}
b.say(a.say);
b.say = a.say;
b.say();
结果:111、222、222、333
因为:对象调用,则 this 指向调用它的对象;
赋值之后,this 指向丢失,重新指向 window;
函数作为参数传递,造成隐式绑定失效,重新指向 window;
赋值操作,相当于 this 指向调用它的对象b;
var foo = 123;
function print() {
this.foo = 234;
console.log(foo)
};
print();
new print()
结果:234、123
因为:print里面没有foo,foo在全局,this.foo改的是全局;
此时this指的是print,print里面的this.foo=234,但是打印的是foo,所以是全局的foo;
let name = 'the window';
let obj = {
name: 'my obj',
getName: function() {
return function() {
return this.name;
}
}
}
console.log(obj.getName()())
结果:空
因为:由this指向规律可知道该this指向window,而let有自己的作用域,不属于window。一般情况下输出 undefined。但是window又默认有自己的name属性,所以为空。