console.log

225 阅读5分钟

JS 一些基本概念

  • 数据类型
  1. 基本类型:numberbooleanstringundefinednull
  2. 引用类型:arrayobjectfunction
  • typeof 返回 六 种值 numberbooleanstringobjectfunctionundefined

    注:返回的值均为 string 类型
  • 六种情况下返回值为 false false0undefinednull""NaN
  • 其他
null < 0null > 0null == 0 // 均为 false
undefined < 0undefined > 0undefined == 0 // 均为 false
null == undefined // true

Number({}) // NaN
Number([]) // 0

[] == false // true
[] == ![] // true
// 因为引用值对比的是地址,所以以下均为 false
[] == true
{} == true
{} == {}
[] == []
[] == {}

运行逻辑

JS 运行三部曲:语法分析、预编译、解释执行

  • 语法分析:通篇执行一遍,看有木有问题,有问题直接报错 语法问题报错:Uncaught SyntaxError
    逻辑问题报错:Uncaught ReferenceError
  • 预编译:( 预编译发生在函数执行的前一刻 )
  1. 创建 AO 对象;(AO:Activation Object 执行期上下文)
  2. 找到形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined
  3. 将实参值和形参统一
  4. 在函数体里面找函数声明,值赋予函数体

变量与函数

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属性,所以为空。