Fork me on GitHub

JavaScript基础之变量

最近感觉自己步入了新的阶段,想把以前的知识都捡起来,复习一下,温故而知新嘛,这一篇主要是关于数据和变量的存储

一、数据类型

基本数据类型(栈):Number、null、undefined、String、Boolean、Symbol(Symbol为ES6新增)

引用数据类型(堆):object(其中包括array、function、Date)

二、浏览器执行JS代码

1、从电脑内存中分配出一块内存,用来执行代码(栈内存——>stack)

2、分配一个主线程来自上而下执行JS代码

三、赋值

1、简单赋值

例如:let a = 12

步骤一:创建变量a,将a放到栈内存的变量存储区

步骤二:创建值12,将12放到栈内存的值存储区

步骤三:“=”为赋值,将值与变量关联起来

 

2、复杂赋值

步骤一:内存中分配出一块新内存,存引用类型值(堆-heap)=====> 内存有个十六进制地址

步骤二:对象中的键值对依次存储到堆内存中

步骤三:把堆内存地址和变量通过“=”关联起来

 例子如图:

 

注意:JS的赋值特点是关联,不是拷贝

 四、关于typeof

typeof返回值有六种:number、string、boolean、undefined、object、function

JS有两种特殊数据类型:null 和 undefined

五、var、let、const的区别

JavaScript中一旦被定义就无法再被修改的变量,称之为常量,常量在ES6后使用const来定义,通常常量用大写字母定义,且多个单词之间用_分隔

在ES6之前定义变量,不管是静态还是动态都用var

1、var声明的变量会挂在在window上,而const和let的声明不会

var a = 100;
console.log(a,window.a);    // 100 100

let b = 10;
console.log(b,window.b);    // 10 undefined

const c = 1;
console.log(c,window.c);    // 1 undefined

2、var声明变量存在变量提升,let和const不存在变量提升

console.log(a); // undefined  ===>  a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined  ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined  ===> 找不到c这个变量
const c = 10;

3、let和const声明形成块作用域

因此,可以尽可能的使用 let 而不是 var ,这样执行效率更高,内存占用更少,可以及时释放

如:

if(1){
    var a = 100;
    let b = 10;
}

console.log(a); // 100
console.log(b)  // 报错:b is not defined  ===> 找不到b这个变量
if(1){

    var a = 100;
        
    const c = 1;
}
 console.log(a); // 100
 console.log(c)  // 报错:c is not defined  ===> 找不到c这个变量

4、同一作用域下 let 和 const 不能声明同名变量,而 var 可以

var a = 100;
console.log(a); // 100

var a = 10;
console.log(a); // 10
let a = 100;
let a = 10;

//  控制台报错:Identifier 'a' has already been declared  ===> 标识符a已经被声明了。

5、暂存死区

var a = 100;

if(1){
    a = 10;
    //在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
    // 而这时,还未到声明时候,所以控制台Error:a is not defined
    let a = 1;
}

6、const

/*
*   1、一旦声明必须赋值,不能使用null占位。
*
*   2、声明后不能再修改
*
*   3、如果声明的是复合类型数据(比如:数组、对象),可以修改其属性,但是不能修改数据的地址
*
* */

const a = 100; 

const list = [];
list[0] = 10;
console.log(list);  // [10]

const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj);  // {a:10000,name:'apple'}

六、隐式转换和显式转换

首先我们要知道,在 JS 中类型转换只有三种情况,分别是:

1、转换为布尔值

2、转换为数字

3、转换为字符串

 

 

 

七、NaN

在JavaScript中,NaN是一个特殊的数字值(typeof NaN的结果是number),是not a number的缩写,表示不是一个合法的数字。

1、NaN的产生

  一个不能被解析的数字

Number('abc') // NaN
Number(undefined) // NaN

  失败的操作

Math.log(-1) // NaN
Math.sqrt(-1) // NaN
Math.acos(2)  // NaN

  一个运算符为NaN

NaN + 1 // NaN
10 / NaN  // NaN

2、注意点

NaN是唯一一个和自身不相等的值

NaN === NaN  // false

3、如何辨别NaN

使用全局函数isNaN()来判断一个数值是不是一个非数字(并不是用来判断是不是NaN这个值):

isNaN(NaN)  // true
isNaN(10)  // false

为什么说isNaN()不是用来判断是不是NaN这个值的呢?因为isNaN对非数字不起作用,它首先做的就是把这些值转换成数字,转换的结果可能为NaN,然后函数会错误的返回true

isNaN('abc')  // true

  所以我们想确定这个值是否为NaN,可以使用以下两种方法:

1、将isNaN()和typeof结合来判断

function isValueNaN(value) {
	return typeof value === 'number' && isNaN(value)
}

2、值是否与本身不相等(NaN是唯一有这样特征的值)

function isValueNaN(value) {
	return value !== value
}

八、深拷贝和浅拷贝

深拷贝和浅拷贝只针对Object和Array这样的引用数据类型的

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

 

 

 1、赋值和浅拷贝的区别

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。

也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此两个对象是联动的。

 

浅拷贝是按位拷贝对象,它会创建一个新对象。这个对象有着x原始对象属性值的一份精确拷贝。

如果属性是基本类型,拷贝的就是基本类型的值;

如果属性是内存地址(引用类型),拷贝的就是内存地址,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间不复制对象资源。

 

例子:

1、赋值前后的比较

// 对象赋值
var obj1 = {
   'name' : 'zhangsan',
   'age' :  '18',
   'language' : [1,[2,3],[4,5]],
};
var obj2 = obj1;
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1)
console.log('obj2',obj2)

 

2、浅拷贝前后的比较

// 浅拷贝
var obj1 = {
   'name' : 'zhangsan',
   'age' :  '18',
   'language' : [1,[2,3],[4,5]],
};
var obj3 = shallowCopy(obj1);
obj3.name = "lisi";
obj3.language[1] = ["二","三"];
function shallowCopy(src) {
   var dst = {};
   for (var prop in src) {
       if (src.hasOwnProperty(prop)) {
           dst[prop] = src[prop];
       }
   }
   return dst;
}
console.log('obj1',obj1)
console.log('obj3',obj3)

 

 

 在上面的例子中,obj1是原始属性,obj2是赋值操作得到,而obj3浅拷贝得到。我们可以很清晰看到对原始数据的影响,具体请看下表:

  和原数据是否指向同一地址 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变

2、浅拷贝的实现方式

1、Object.assign()

Object.assign()方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象,但是Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); // wade

  注意:当object只有一层的时候,是深拷贝

let obj = {
   username: 'kobe'
};
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"}

2、Array.prototype.concat()

let arr = [1, 3, {
   username: 'kobe'
}];
let arr2=arr.concat();    
arr2[2].username = 'wade';
console.log(arr);

  修改新对象会改到原对象

 

 3、Array.prototype.slice()

let arr = [1, 3, {
   username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);

  同样的,修改新对象会改到原对象

 

 关于Array的slice()和concat()方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

原数组的元素会按照下述规则拷贝:

1、如果该元素是个对象引用(不是实际的对象),

slice会拷贝这个对象引用到新的数组里。

两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

2、对于字符串、数字及布尔值来说(不是string、number或者boolean对象),

slice会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或布尔值,将不会影响另一个数组。

一个小例子:

let arr = [1, 3, {
   username: ' kobe'
}];
let arr3 = arr.slice();
arr3[1] = 2
console.log(arr,arr3);

 

 3、深拷贝的实现方式

1、JSON.parse(JSON.stringify())

let arr = [1, 3, {
   username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)

 

 这是因为JSON.stringify()方法是一个将JavaScript值(对象或数组)转换为一个JSON字符串,不能接收函数。

2、手写递归方法

递归方法实现深度克隆原理:

遍历对象、数组,直到里面都是基本数据类型,然后再去复制,就是深度拷贝。

// 定义检测数据类型的功能函数
   function checkedType(target) {
     return Object.prototype.toString.call(target).slice(8, -1)
   }
   // 实现深度克隆---对象/数组
   function clone(target) {
     // 判断拷贝的数据类型
     // 初始化变量result 成为最终克隆的数据
     let result, targetType = checkedType(target)
     if (targetType === 'object') {
       result = {}
     } else if (targetType === 'Array') {
       result = []
     } else {
       return target
     }
     // 遍历目标数据
     for (let i in target) {
       // 获取遍历数据结构的每一项值。
       let value = target[i]
       // 判断目标结构里的每一值是否存在对象/数组
       if (checkedType(value) === 'Object' ||
         checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
         // 继续遍历获取到value值
         result[i] = clone(value)
       } else { 
        // 获取到value值是基本的数据类型或者是函数。
         result[i] = value;
       }
     }
     return result
   }

   // 定义检测数据类型的功能函数
   function checkedType(target) {
     return Object.prototype.toString.call(target).slice(8, -1)
   }
   // 实现深度克隆---对象/数组
   function clone(target) {
     // 判断拷贝的数据类型
     // 初始化变量result 成为最终克隆的数据
     let result, targetType = checkedType(target)
     if (targetType === 'object') {
       result = {}
     } else if (targetType === 'Array') {
       result = []
     } else {
       return target
     }
     // 遍历目标数据
     for (let i in target) {
       // 获取遍历数据结构的每一项值。
       let value = target[i]
       // 判断目标结构里的每一值是否存在对象/数组
       if (checkedType(value) === 'Object' ||
         checkedType(value) === 'Array') { 
          // 对象/数组里嵌套了对象/数组
          // 继续遍历获取到value值
         result[i] = clone(value)
       } else { 
         // 获取到value值是基本的数据类型或者是函数。
         result[i] = value;
       }
     }
     return result
   }

3、lodash

该函数库也有提供_.cloneDeep用来做Deep Copy。

var _ = require('lodash');
var obj1 = {
   a: 1,
   b: { f: { g: 1 } },
   c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

 

 

posted @ 2021-04-17 20:05  spikezz  阅读(107)  评论(0)    收藏  举报
1