JS基础知识 —— 变量,运算符,内存

本文详细介绍了JavaScript中的变量类型,包括值类型和引用类型,以及它们的区别。讨论了typeof和instanceof操作符的使用,并探讨了JS引用类型。此外,文章还详述了JS中的隐式类型转换,包括抽象相等比较算法和严格相等比较算法,以及Falsy和Truthy的概念。在运算符部分,讲解了一元加和一元减操作符的规则,还通过几个面试题来加深理解。最后,文章简要阐述了JS内存管理,特别是自动垃圾收集机制。
摘要由CSDN通过智能技术生成

JS中的变量类型


js中的变量分为两种类型,一种是值类型,另外一种是引用类型。值类型包括undefined,null,boolean,string,number;引用类型是object。


这里需要注意下,有两个比较特殊的值 —— null 和 undefined。
null用来表示空对象引用(用以理解)。典型用法:

  1. 作为函数的参数,表示该函数的参数是空对象引用,即没有指向。
  2. 作为对象原型链终点

undefined用来表示缺少值。即此处应该有值,但是还没有定义。典型用法:

  1. 变量已经被声明了,但是还没有被赋值,就等于undefined
    var i;
    console.log(i); // undefined
    
  2. 调用函数时,应该提供的参数没有提供到,该参数的值为undefined
    function foo (x) {
    	console.log(x); // undefined
    }
    foo();
    
  3. 对象没有赋值的属性,该值为undefined
    var o = {};
    
  4. 函数没有返回值时,默认是返回undefined
    function foo () {
    	console.log(1);
    }
    var result = foo();
    console.log(result); // undefined
    

值类型和引用类型区别
  • 存储位置上:值类型存储在栈中,引用类型储存在堆中
  • 存储方式上:值类型存储的是实际的数值,引用类型则是保存在堆内存中对象的引用(因为JS不允许直接操作内存)。
  • 数据类型上:基本类型值指的是简单的数据段,引用类型值指那些可能由多个值构成的对象。

总结:值类型是按值访问的,引用类型的值是按引用访问的(准确地说,应该是当复制保存着内存中对象的引用的变量时,操作的是对象的引用;而为内存中的对象添加属性的时候,操作的是实际的对象)

typeof操作符和instanceof操作符的使用

使用typeof检测数值会出现以下结果:undefined,number,boolean,string,object,function

所以说使用typeof只能检测值类型,检测引用类型是得不到我们想要的结果。所以我们使用instanceof检测引用类型。


JS引用类型

js内置函数
Object,Array,Function,Date,RegExp,Boolean,Number,String,Error

js内置对象(不是函数)
Math,Global,JSON(有两个API —— JSON.parse()和JSON.stringify();也是一种数据格式)



JS变量的隐式类型转换(javascript中的比较)


抽象相等比较算法

比较运算x == y,其中xy是值,产生true或者false。这样的比较按如下的方式进行:

  1. Type(x)Type(y)相同,则
    1. Type(x)Undefined,返回true
    2. Type(x)Null,返回true
    3. Type(x)Number,则
      1. x或者yNaN,返回false
      2. x+0y-0,返回true
      3. x-0y+0,返回true
      4. xy为相等数值,返回true
      5. 返回false
    4. Type(x)String,则当xy为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。否则,返回false
    5. Type(x)Boolean,当xy同为true或者同为false时返回true。否则,返回false
    6. Type(x)Object,当xy为引用同一对象时返回true。否则,返回false
  2. xNullyUndefined,返回true
  3. xUndefinedyNull,返回true
  4. Type(x)NumberType(y)String,返回x == ToNumber(y)(即y先转化为Number类型再比较)。
  5. Type(x)StringType(y)Number,返回ToNumber(x) == y(即x先转化为Number类型再比较)。
  6. Type(x)BooleanType(y)Number,返回比较ToNumber(x) == y的结果(即x先转化为Number类型再比较)。
  7. Type(x)NumberType(y)Boolean,返回比较x == ToNumber(y)的结果(即y先转化为Number类型再比较)。
  8. Type(x)String或者Number,且Type(y)Object,返回比较x == ToPrimitive(y)的结果(即调用Object的valueOf()方法或者toString()方法得到值再比较)。
  9. Type(x)Object,且Type(y)String或者Number,返回比较ToPrimitive(x) == y的结果(即调用Object的valueOf()方法或者toString()方法得到值再比较)。
  10. 其余情况返回false

注意:false == ""也是true

1. false -> Number(false) -> 0
2. "" -> Number("") -> 0
3. 0 == 0 -> false == ""
false == 0 // true
false == undefined // false
false == null // false
null == 0 // false
null == "" // false
undefined == 0 // false
undefined == "" // false 

注:相等运算符不总是传递的。 例如两个不同的String对象,都表示相同的字符串;==运算符认为每个String对象都与字符串的值相等,但是两个字符串对象互不相等。

new String("a") == "a"; // true
"a" == new String("a"); // true
new String("a") == new String("a"); // false
严格相等比较算法

比较运算x === y,产生true或者false。这样的比较按如下的方式进行:

  1. 如果Type(x)Type(y)结果不一致,返回false
  2. Type(x)Type(y)相同,则
    1. Type(x)Undefined,返回true
    2. Type(x)Null,返回true
    3. Type(x)Number,则
      1. x或者yNaN,返回false
      2. x+0y-0,返回true
      3. x-0y+0,返回true
      4. xy为相等数值,返回true
      5. 返回false
    4. Type(x)String,则当xy为完全相同的字符序列(长度相等且相同字符在相同位置)时返回true。否则,返回false
    5. Type(x)Boolean,当xy同为true或者同为false时返回true。否则,返回false
    6. Type(x)Object,当xy为引用同一对象时返回true。否则,返回false
Falsy 和 Truthy
  • Falsy值:当进行逻辑判断时均为 false。包括 false, null, undefined, +0, -0, NaN, "", ''
  • Truthy值:其余的值均为 Truthy,当进行逻辑判断时均为 trueinfinity, [], "0"都是 Truthy 值。
两个很经典的面试题

1. [] == ![] // true

  1. 等号右边有 !,优先级比 ==更高,优先计算右边的结果。[]为非假值,所以右边的运算结果为 false

    ![] => false
    
  2. ==的任意一遍有 boolean类型的值先把这个值转换成 number类型,右边转换成了 0

    Number(false) => 0
    
  3. ==分别是 numberobject类型的值时,需要把 object转换成了 number类型。对 object进行 ToNumber操作

    Number([].valueOf()) => 0
    

2. ++[[]][+[]]+[+[]] // “10”

  1. ++[[]][+[]]+[+[]] =>

    ++[[]][+[]]
    +
    [+[]]
    
  2. 因为 +[] === 0,所以原式 =>

    ++[[]][0]
    +
    [0]
    
  3. 因为 [[]][0] 的意思是获取 [[]] 的第一个元素,故返回 [[]] 的第一个数组 [],原式 =>

    1
    +
    [0]
    
  4. 当数组只有一个元素时,返回结果就是它自身的第一个元素,原式 =>

    1 + "0" => "10"
    



JS运算符


图片可能不是很清晰,这个是MDN的传送门


一元加

  • 一元操作符以一个加号(+)表示,放在数值前面,对数值不会产生任何影响
  • 在对非数值应用一元操作符时,该操作符会像Number()转型函数一样对这个值执行转换。(布尔值false和true将转为0和1,字符串值会被按照一组特殊的规则进行解析,而对象是先调用它们的valueOf()和toString(),再转换得到的值)
  • 它可以将字符串转换成整数和浮点数形式,也可以转换非字符串值 true,false 和 null。小数和十六进制格式字符串也可以转换成数值。负数形式字符串也可以转换成数值(对于十六进制不适用)。
+3     // 3
+"3"   // 3
+true  // 1
+false // 0
+null  // 0
+undefined // NaN
+function(val){ return val;} // NaN
true + 5 // 6
false + 5 // 5
null + 5 // 5
undefined + 5 // NaN
'' + 5 // '5'
[] + 5 // '5'

Number(), parseInt(), +一样,都可以用来进行数字的转换。区别在于,当转换的内容包含非数字的时候,Number()会返回 NaN(Not a Number)+Number()一样会返回 NaNparseInt()要看情况,如果以数字开头,就会返回开头的合法数字部分,如果以非数字开头,则返回 NaN

js隐式转换

一元减

  • 一元减操作符主要用于表示负数
  • 将一元减操作符应用于数值时,该值会变成负数
  • 当应用于非数值时,一元减操作符遵循与一元加操作符相同的规则,最后再将得到的数值转换为负数

一道很有趣的面试题

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
Foo.getName();				// 2
getName();					// 4
Foo().getName();			// 1
getName();					// 1
new Foo.getName();			// 2
new Foo().getName();		// 3
new new Foo().getName();	// 3
第一问

首先此题先声明定义了一个 ·Foo函数,之后 Foo创建了一个 getName静态属性,是一个匿名函数。再之后 Foo 的原型对象创建了一个 getName的属性,也是一个匿名函数。通过函数变量表达式创建了一个 getName函数,最后声明定义 一个 getName函数。

第一问中的 Foo.getName访问的是 Foo 函数上存储的静态属性,所以是 2

第二问

第二问中直接调用 getName函数。访问的是当前上下文作用域中的 getName函数。在这个说一下,这里涉及了两个坑,一个是变量声明提升,二是函数表达式。变量声明提升就不说了,说一下函数表达式。

函数表达式
var getName = function () {}function getName () {}都是声明语句,区别就是 var getName = function () {}函数表达式;而 function getName () {}函数声明

所以这一问中 var getName = function () { alert (4); }; function getName() { alert (5); };先声明 function getName,此时要 var getName声明,因存在同名则跳过,然后执行代码时, var getName等号右边的值 function () { alert(4); };赋给了 getName变量。所以最终输出 4

第三问

第三问中, Foo().getName();先执行了 Foo 函数,然后调用 Foo 函数返回的对象的 getName属性指向的函数。

Foo 函数的第一句修改了外层作用域中的变量 getName的值。 之后 Foo 函数的返回值是 this。此处的直接调用方式,this指向 window对象。所以相当于执行 window.getName(),此时 getName已经被修改了,所以最终输出为 1

第四问

同第三问,相当于执行 window.getName(),最终输出为 1

第五问

第五问考察的是 js 的运算符优先级问题,成员访问(.)的优先级高于 new,相当于 new (Foo.getName)();。所以实际上将 getName函数作为了构造函数来执行,最终结果为 2

第六问

第六问 new Foo().getName(),首先带参new操作符和成员访问(.)优先级一样,所以先碰到谁就先执行谁,这里是 new。实际执行为 (new Foo()).getName();,执行完 new 操作后有返回值,在这里顺便讲讲 js 中的构造函返回值问题。

构造函数的返回值
1.没有返回值则按照其他语言一样返回实例化对象
2.若有返回值则检查其返回值是否为引用类型。如果是非引用类型,如基本类型(string, number, boolean, null, undefined)则与无返回值相同,实际返回其实例化对象
3.若返回值是引用类型,则实际返回值是这个引用类型

原题中,返回的是 this,而 this 在构造函数中本来就代表着当前实例化对象,所以 Foo 函数返回实例化对象。之后调用实例化对象的 getName函数,因为实例化对象中没该函数,所以向 Foo 函数的原型对象中询找,找到后并执行,最终结果为 3

第七问

第七问中,new new Foo().getName();同样是运算符优先级的问题。实际执行为:new ((new Foo()).getName)();。先初始化 Foo 的实例化对象,然后取得函数 Foo 原型对象上的 getName函数作为构造函数,使用 new 操作,最终输出为 3



JS内存管理


js中内存的生命周期

// 1.分配需要使用的内存
// 2.使用分配的内存进行读写
// 3.不需要时释放,归还内存

var obj = { age: 22 };
obj.age = 20;
obj = null;

而第三步释放内存需要了解一下,JS中是有着自动垃圾收集机制的。自动垃圾收集机制的原理是找到那些不再使用的值,释放掉它们所占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。

JS中最常用的垃圾收集方式标记清除。前面的代码的obj = null只是做了一个释放引用的操作,让obj原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集时找到并且释放掉。

在局部作用域中,函数执行完后,局部变量没有存在的必要,所以垃圾收集器很容易做出判断并回收。而全局变量垃圾收集器很难判断它什么时候需要释放掉内存,所以要尽量避免使用全局变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值