JS基础
1.1 JavaScript - 内置对象
了解数据类型前先说说栈(stack)和堆(heap) *** stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放 ***
js中的基本数据类型
Number、String、Boolean、Null、 Undefined、Symbol(ES6)这些类型可以直接操作保存在变量中的实际值。
var a = 10;
var b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20
复制
下图是基本类型的一个直观过程
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAjM2EzLcd3LcJzLcJzdllmVldWYtl2Pn5GcukzY2cjMwczN0ETOhR2MmlzY1QjMwQGN3cDOwEzNzMGMvwFMxQDN3kTMtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
js中的引用类型 Object、Function、Array 、Date ...在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象,正则表达式是对象。引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。
var obj1 = new Object();
var obj2 = obj1;
obj2.name = "Tale";
console.log(obj1.name); // 我
复制
这两个引用数据类型指向了同一个堆内存对象。obj1赋值给obj2,实际上这个堆内存对象在栈内存的引用地址复制了一份给了obj2,但是实际上他们共同指向了同一个堆内存对象,所以修改obj2其实就是修改那个对象,所以通过obj1访问也能访问的到。
var a = [1,2,3,4,5];
var b = a;//传址 ,对象中传给变量的数据是引用类型的,会存储在堆中;
var c = a[0];//传值,把对象中的属性/数组中的数组项赋值给变量,这时变量C是基本数据类型,存储在栈内存中;改变栈中的数据不会影响堆中的数据
console.log(b);//1,2,3,4,5
console.log(c);//1
//改变数值
b[4] = 6;
c = 7;
console.log(a[4]);//6
console.log(a[0]);//1
console.log(c); // 7
复制
从上面我们可以得知,当我改变b中的数据时,a中数据也发生了变化;但是当我改变c的数据值时,a却没有发生改变。
这就是传值与传址的区别。因为a是数组,属于引用类型,所以它赋予给b的时候传的是栈中的地址(相当于新建了一个不同名“指针”),而不是堆内存中的对象。而c仅仅是从a堆内存中获取的一个数据值,并保存在栈中。所以b修改的时候,会根据地址回到a堆中修改,c则直接在栈中修改,并且不能指向a堆内存中。
1.2 判断数据类型
1.2.1 typeof
- 使用方法:
或者typeof operand
typeof(operand)
-
:一个表示对象或原始值的表达式,其类型将被返回。operand
-
- 说明:
typeof
可能的返回值:
类型 | 结果 |
---|---|
Undefined | 'undefined' |
Null | 'object' |
Boolean | 'boolead' |
Number | 'number' |
BigInt | 'bigint' |
String | 'string' |
Symbol | 'symbol' |
Function | 'function' |
其他任何对象 | 'object' |
- 代码:
/**
* @name typeof测试
* @description 通过 typeof 检测各个数据类型的返回
*/
const test = {
testUndefined: undefined,
testNull: null,
testBoolean: true,
testNumber: 123,
testBigInt: BigInt(1234), // 大于 2 的 53 次方算 BigInt
testString: 'test',
testSymbol: Symbol(),
testFunction: function() {
console.log('function');
},
testObject: {
obj: 'yes',
},
testObjectString: new String('String'),
testObjectNumber: new Number(123),
}
console.log(typeof(test.testUndefined)); // undefined
console.log(typeof(test.testNull)); // object
console.log(typeof(test.testBoolean)); // boolean
console.log(typeof(test.testNumber)); // number
console.log(typeof(test.testBigInt)); // bigint
console.log(typeof(test.testString)); // string
console.log(typeof(test.testSymbol)); // symbol
console.log(typeof(test.testFunction)); // function
console.log(typeof(test.testObject)); // object
console.log(typeof(test.testObjectString)); // object
console.log(typeof(test.testObjectNumber)); // object
复制
1.2.2 instanceof
- 说明 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
- 代码:
function Phone(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var auto = new Phone('HTC', 'MI', 2009);
console.log(auto instanceof Phone); // true
console.log(auto instanceof Object); // true
复制
- 方法:
object instanceof constructor
-
:某个实例对象。object
-
:某个构造函数constructor
-
- 说明:
instanceof
运算符用来检测
constructor.prototype
是否存在于参数
Object
的原型链上。
同时,
instanceof
运算符也可以用来判断数据类型,但是它会存在一点 “缺陷”,详细可观看代码。
- 代码:
/**
* @name instanceof示例1
* @description 检测字符串类型
*/
const simpleString = 'this is sample String';
const newString = new String('this New String');
console.log(simpleString instanceof String); // false,检查原型链会返回 undefined
console.log(newString instanceof String); // true
/**
* @name instanceof示例2
* @description 检测数字类型
*/
const simpleNumber = 1;
const newNumber = new Number(1);
console.log(simpleNumber instanceof Number); // false
console.log(newNumber instanceof Number); // true
/**
* @name instanceof示例3
* @description 检测对象类型
*/
const simpleOjbect = {};
const newObject = new Object();
console.log(simpleOjbect instanceof Object); // true
console.log(newObject instanceof Object); // true
复制
1.2.3 constructor判断
constructor
返回创建实例对象的 Object 构造函数的引用。所有对象都会从它的原型上继承一个
constructor
属性。
const arr = [];
console.log(arr.constructor === Array); // true
const obj = {};
console.log(obj.constructor === Object); // true
const num = 1;
console.log(num.constructor === Number); // true
const str = '1';
console.log(str.constructor === String); // true
const bool = true;
console.log(bool.constructor === Boolean); // true
const nul = null;
console.log(nul.constructor); // 报错:Uncaught TypeError: Cannot read property 'constructor' of null at <anonymous>:1:5
const undefin = undefined;
console.log(undefin.constructor); // 报错:Uncaught TypeError: Cannot read property 'constructor' of null at <anonymous>:1:5
复制
1.2.4 toString()方法
toString() 方法返回一个表示该对象的字符串。默认情况下,
toString()
方法被每个
Object
对象继承,如果该方法没有在自定义对象中被覆盖,
toString()
返回
'[object type]'
,其中
type
是类型
const object = new Object();
console.log(object.toString()); // [object Object]
复制
这样我们就可以通过
toString()
来获取每个对象的类型。为了每个对象都能通过
Object.prototype.toString()
来检测,我们需要用
Function.prototype.call()
或者
Function.prototype.apply()
的形式来调用,传递要检查的对象作为第一个参数,称为
thisArg
。
- 代码:
const toString = Object.prototype.toString;
function Person(){}
console.log(toString.call(new Date)); // [object Date]
console.log(toString.call(new String)); // [object String]
console.log(toString.call(Math)); // [object Math]
console.log(toString.call('tale')); // [object String]
console.log(toString.call(123)); // [object Number]
console.log(toString.call([])); // [object Array]
console.log(toString.call({})); // [object Object]
console.log(toString.call(undefined)); // [object Undefined]
console.log(toString.call(null)); // [object Null]
console.log(toString.call(new Person)); // [object Object]
console.log(toString.apply(new Date)); // [object Date]
console.log(toString.apply(new String)); // [object String]
console.log(toString.apply(Math)); // [object Math]
console.log(toString.apply('tale')); // [object String]
console.log(toString.apply(123)); // [object Number]
console.log(toString.apply([])); // [object Array]
console.log(toString.apply({})); // [object Object]
console.log(toString.apply(undefined)); // [object Undefined]
console.log(toString.apply(null)); // [object Null]
console.log(toString.apply(new Person)); // [object Object]
复制
1.3 深拷贝和浅拷贝
1.3.1 浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
var a={key1:"aaa"}
function Copy(p){
var c ={};
for (var i in p){
c[i]=p[i]
}
return c;
}
a.key2 = ["1","2"];
var b = Copy(a);
b.key3 = "bbb"
console.log(b.key1)//11111
console.log(b.key3)//33333
console.log(a.key3);//undefined
b.key2.push("3")
console.log(a.key2);// ["1","2","3"]
复制
原因是key1的值属于基本类型,所以拷贝的时候传递的就是该数据段;但是key2的值是堆内存中的对象,所以key2在拷贝的时候传递的是指向key2对象的地址,无论复制多少个key2,其值始终是指向父对象的key2对象的内存空间。实现拷贝的方法
- 数组拷贝
- for循环或者利用数组自身的方法,slice、concat方法在运行后会返回新的数组
- 对象
- for(循环)
- 使用JSON比较简单,但是JSON的深拷贝方式会忽略函数对象和原型对象
- 扩展运算符(...)
- Object.assign() (ES6方法)
//ES6实现浅拷贝的方法
var a = {name:"tale"}
var b= Object.assign({},a);
b.age = 18;
console.log(a.age);//undefined
----------------------------------
//数组
var a = [1,2,3];
var b = a.slice();
b.push(4);
b//1,2,3,4
a//1,2,4
----------------------------------
var a = [1,2,3];
var b = a.concat();
b.push(4);
b//1,2,3,4
a//1,2,3
----------------------------------
var a = [1,2,3];
var b = [...a]
b//1,2,3,4
a//1,2,3
复制
1.3.2深拷贝
或许以上并不是我们在实际编码中想要的结果,我们不希望父子对象之间产生关联,那么这时候可以用到深拷贝。既然属性值类型是数组和或象时只会传址,那么我们就用递归来解决这个问题,把父对象中所有属于对象的属性类型都遍历赋给子对象即可。
复制
var a={key1:"aaa"}
function Copy(p,c){
var c =c||{};
for (var i in p){
if(typeof p[i]==="object"){
c[i]=(p[i].constructor ===Array)?[]:{}
Copy(p[i],c[i]);
}else{
c[i]=p[i]
}
}
return c;
}
a.key2 = ["1","2"];
var b = Copy(a);
b.key3 = "bbb"
console.log(b.key1)//11111
console.log(b.key3)//bbb
console.log(a.key3);//undefined
b.key2.push("3")
console.log(a.key2);// ["1","2"]
复制
tale