js基础系列(一)——变量和类型

355 阅读9分钟

今天开始我们来总结一下js相关的知识,可能大家对这些都很熟悉,但是从我项目的经验来看,前端虽然是一项好上手的技术,不过进了前端的坑才发现知识点众多而且杂乱。可能很多前端的小伙伴都有这种经验,自己的能力就在于写代码(抄代码),但是很难有大的进步,这个还是因为基础知识体系不够牢固,就导致很多底层的原理不懂,就像空中楼阁。所以把自己学习的东西整理成体系是很重要的。

好啦,废话说了一堆,我们直接进入正题吧!!!


1、JavaScript的语言类型

两种类型:基本类型引用类型

(1)基本类型

String:字符串

Number:数字

Boolean:布尔值(false/true)

Null:null值表示一个空对象指针,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。

   //document.getElementById() 可以返回对拥有指定 ID 的第一个对象的引用

   var $container = document.getElementById("container"); // 注意:container是不存在的

   console.log($container); // null

Undefined:出现undefined的两种情况如下:

a)声明了一个变量,但没有初始化时,这个变量的值就是undefined。

var data;
console.log(data === undefined); //true)

b)对未定义(注意:这里并不是未声明)的变量执行typeof操作符也会返回undefined

var foo;

console.log(typeof data); // "undefined"
console.log(typeof foo); // "undefined"   foo虽然声明了,但是未定义

注:还有其他几种情况也会返回undefined,比如一个函数如果没有使用return语句指定返回值,就会返回一个undefined值,或者调用函数时没有传参数值,参数同样也会被初始化为undefined值。这些都是属于上面两种情况在代码中的体现,这里就不单独解释了。

关于Null和Undefined这里推荐一篇文章,讲的很详细juejin.cn/post/684490…

Symbol:ES6中引入的新类型,从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

(2)引用类型

Object:对象(Array、Date、Function、Error、RegExp、Math···)

2、JavaScript对象的底层数据结构是什么

基本类型是存在栈中的,每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说 ,更加容易管理内存空间。 

引用内存同时保存在栈和堆中,栈中保存的是引用(地址),真正的数据保存在堆中。当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。

3、Symbol的应用及手动实现简单的Symbol

上面我们也提高过,Symbol的用途就是用来做唯一标识的,一个symbol值能作为对象属性的标识符(以此来保证对象属性的唯一性);这是该数据类型仅有的目的。

手动实现Symbol:

这里我的水平有限,咱们还是看一下大神的文章吧

juejin.cn/post/684490…

4、装箱拆箱操作

定义:把基本数据类型转换为对应的引用类型的操作称为装箱,把引用类型转换为基本的数据类型称为拆箱。  

a)装箱

每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。

var s1 = "test";
var s2 = s1.substring(2);

如上所示,变量s1是一个基本类型值,它不是对象,所以它不应该有方法。但是我们知道字符串操作有很多方法,这是因为js内部为我们完成了一系列处理(即我们称之为装箱),使得它能够调用方法,实现的机制如下:

(1)创建String类型的一个实例; (2)在实例上调用指定的方法; (3)销毁这个实例;

这个过程用代码表示如下(这种也叫显示装箱,上面那种叫做隐式装箱):

var s1  = new String("test");
var s2 = s1.substring(2);
s1 = null;

b)拆箱

将引用类型对象转换为对应的值类型对象,它是通过引用类型的valueOf()或者toString()方法来实现的。如果是自定义的对象,你也可以自定义它的valueOf()/tostring()方法,实现对这个对象的拆箱。

var objNum = new Number(123);
var objStr =new String("abc");

console.log( typeof objNum ); //object
console.log( typeof objStr ); //object

console.log( typeof objNum.valueOf() ); //number
console.log( typeof objStr.valueOf() ); //string
console.log( typeof objNum.toString() ); // string
console.log( typeof objStr.toString() ); // string

5、判断JavaScript数据类型的方式

a)typeof()

typeof 返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、object、undefined、function等6种数据类型。

缺点:基本数据类型判断基本没问题(除了null,typeof null会返回object,因为特殊值null被认为是一个空的对象引用),但是对于除了函数之外的其他引用类型都返回的是object,其实返回object也没有错,因为所有对象的原型链最终都指向了Object。 但当我们需要知道某个对象的具体类型时,typeof 就显得有些力不从心了。

b)instanceof()

    var str = 'hello';
    console.log(str instanceof String);//false    var bool = true;
    console.log(bool instanceof Boolean);//false    var num = 123;
    console.log(num instanceof Number);//false    var nul = null;
    console.log(nul instanceof Object);//false    var und = undefined;
    console.log(und instanceof Object);//false    var oDate = new Date();
    console.log(oDate instanceof Date);//true    var json = {};
    console.log(json instanceof Object);//true    var arr = [];
    console.log(arr instanceof Array);//true    var reg = /a/;
    console.log(reg instanceof RegExp);//true    var fun = function(){};
    console.log(fun instanceof Function);//true    var error = new Error();
    console.log(error instanceof Error);//true

从上面的运行结果我们可以看到,基本数据类型时没有检测出他们的类型,但是我们使用下面的方式创建num、str、boolean,是可以检测出类型的:

var num = new Number(123);
var str = new String('abcdef');
var boolean = new Boolean(true);

c)constructor

    var str = 'hello';
    console.log(str.constructor == String);//true
    var bool = true;
    console.log(bool.constructor == Boolean);//true    var num = 123;
    console.log(num.constructor ==Number);//true   // var nul = null;
   // console.log(nul.constructor == Object);//报错    //var und = undefined;
    //console.log(und.constructor == Object);//报错    var oDate = new Date();
    console.log(oDate.constructor == Date);//true    var json = {};
    console.log(json.constructor == Object);//true    var arr = [];
    console.log(arr.constructor == Array);//true    var reg = /a/;
    console.log(reg.constructor == RegExp);//true    var fun = function(){};
    console.log(fun.constructor ==Function);//true    var error = new Error();
    console.log(error.constructor == Error);//true

从上面的测试中我们可以看到,undefined和null是不能够判断出类型的,并且会报错。因为null和undefined是无效的对象,因此是不会有constructor存在的 

而且,因为constructor属性是可以被修改的,会导致检测出的结果不正确。

d)Object.prototype.toString(基本是万能的)

toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型(配合call或者apply使用)

    var str = 'hello';
    console.log(Object.prototype.toString.call(str));//[object String]
    var bool = true;
    console.log(Object.prototype.toString.call(bool))//[object Boolean]
    var num = 123;
    console.log(Object.prototype.toString.call(num));//[object Number]
    var nul = null;
    console.log(Object.prototype.toString.call(nul));//[object Null]
    var und = undefined;
    console.log(Object.prototype.toString.call(und));//[object Undefined]
    var oDate = new Date();
    console.log(Object.prototype.toString.call(oDate));//[object Date]
    var json = {};
    console.log(Object.prototype.toString.call(json));//[object Object]
    var arr = [];
    console.log(Object.prototype.toString.call(arr));//[object Array]
    var reg = /a/;
    console.log(Object.prototype.toString.call(reg));//[object RegExp]
    var fun = function(){};
    console.log(Object.prototype.toString.call(fun));//[object Function]
    var error = new Error();
    console.log(Object.prototype.toString.call(error));//[object Error]

值得注意的是:如果你非要改变一下Object原型链上的toString方法,那你还是自求多福吧...

除上述四种方法之外,这里再补充一个专门判断数组的方法Array.isArray(),如果只是想判断数组的话,那这个是目前最靠谱的判断数组的方法了,当参数为数组的时候,isArray方法返回true,当参数不为数组的时候,isArray方法返回false。

6、隐式类型转换

自动转换 Boolean:例如 if 语句 或者其他需要Boolean的地方

if (表达式){}

运算符:在非 Numeber 类型进行数学运算符 - * / 时,会先将非 Number 转换成 Number 类型。 + 运算符要考虑字符串的情况,在操作数中存在字符串时,优先转换成字符串,然后进行连接字符串的操作。

+ 操作符的执行顺序是:

  • 当一侧操作数为 String 类型,会优先将另一侧转换为字符串类型。
  • 当一侧操作数为 Number 类型,另一侧为原始类型,则将原始类型转换为 Number 类型。
  • 当一侧操作数为 Number 类型,另一侧为引用类型,将引用类型和 Number 类型转换成字符串后拼接。
对象:只有在 JavaScript 表达式或语句中需要用到数字或字符串时,对象才被隐式转换(会在内部调用valueOf()和toString()方法)。

7、数字精度问题

JavaScript处理大数字的方法、避免精度丢失的方法

精度丢失的原因:计算机的最底层是通过1和0的机器码来对具体的数据和操作进行具体的实现。由于底层实现机制的原因,浮点数在转换为二进制表示的时候,无法精确表示这种包含小数点的数据,其本质是将浮点数转换成了用二进制表示的最接近的近似值。可以知道看似有穷的数字, 在计算机的二进制表示里却是无穷的,由于存储位数限制因此存在“舍去”,精度丢失就发生了。

因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。

处理大数字的方法:

a)将Number转为String,然后将String转为Array,并且注意补齐较短的数组,将他们的长度标称一样再一一相加得到一个新数组,再讲和组成的新数组转为数字就可以了。

b)使用BigInt,BigInt是JavaScript中的一个新的原始类型,可以用任意精度表示整数。使用BigInt,即使超出JavaScript Number 的安全整数限制,也可以安全地存储和操作大整数。BigInt支持最常见的运算符,二元运算符+、-、*、**、/、%都正常工作,按位操作|,&, <<,>>和Number是一样的。但是不能表示小数,也不允许在BigInt和Number 之间混合运算。

避免精度丢失的方法:对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。 对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)。

结语

这一部分就到此为止啦,后面会持续更新,如有不对的地方也欢迎大家指正。

本文一部分内容是查阅资料而来,如有雷同望各位海涵。