随记JS基础知识,加入个人理解。
JS数据类型
- 基本类型:数值、字符串、布尔、null、undefined
- 基本类型按值传递,赋值时开辟新的内存空间存储副本;值改变互不影响。
- 存储在栈内存中、存储的是具体的值;其大小固定的。
- 引用类型:对象(数组、函数等)
- 引用类型,引用内存地址。
- 存储在堆内存中,存储的栈内存地址;其大小不固定。
简单示例:
// 基本数据类型
var a=1;
var b=a;
a=2;
// a=2 ; b=1;
// 引用类型、常规赋值
var a={name:"Joy"};
var b=a;
var c={d:b};
a.name="Helo";
// a.name="Helo" ; b.name="Helo" c.d.name="Helo";
可以看出将引用类型赋值给另一个变量时,引用类型的属性值发生改变则相关的变量的属性都会发生变化。
再看这个示例:
var a={name:"Joy"};
var b=a;
var c={d:b.name};
a.name="Helo"
// a.name="Helo" ; b.name="Helo" c.d.name="Joy";
给对象c
的属性赋值的是基本数据类型b.name
是字符串,所以开辟了新内存地址去存储属性d:"Joy"
; a
/b
对象还是引用的属性name
内存地址。
这样对于深、浅拷贝就有了简单说明:
基本数据类型开辟新内存地址进行存储;引用类型引用内存地址存储
- 浅拷贝把对象中可枚举的属性复制一份给目标对象。 (如果属性还是引用类型,引用其内存地址,拷贝结束)
- 深拷贝把对象中可枚举的属性复制一份给目标对象。 (如果属性还是引用类型,则继续遍历取出其中的属性递归处理:是基本类型则拷贝,引用类型继续递归处理…直到所有的属性按照基本类型数据拷贝给目标对象。而不是引用类型数据)
浅拷贝:
var a={
name:"li",
age:23,
other:{
birthday:"1994009121123",
address:"****"
}
};
Object.assign(target,...source); // 浅拷贝
var b = Object.assign({},a);
b.name="hua";
b.other.birthday="201009123322";
// a.name="li" b.name="hua"
// a.other.birthday="201009123322" b.other.birthday="201009123322"
// 浅拷贝逻辑
for(let prop in a){
b[prop] = a[prop];
}
深拷贝:
// 深拷贝逻辑
function deepCopy(copyObj,target){
for(let prop in copyObj){
let value = Reflect.get(copyObj,prop);
if(value== target){
continue;
}
let isArray = Array.isArray(value); // 先判断值是否为数组,
if(typeof value == "object"&&!isArray){
target[prop] = Object.create({});
deepCopy(copyObj[prop],target[prop]);
}else if(isArray){
target[prop] = [];
deepCopy(value,target[prop]);
}else{
target[prop] = value;
}
}
}
let obj = {
name:"li",
age:32,
hobby:["a","b"],
detail:{
birthday:'2019'
}
}
let obj_b = {};
deepCopy(obj,obj_b);
对数组的处理,主要在于数组也是对象,所以处理数据时先判断值是否为数组。
Call()、Apply()
都是
Function
原型链上的方法;
说明:第一个参数指定函数执行上下文对象this
指向;其他的都是执行参数。
区别:
call(context,arg1,arg2,...)
,参数不固定。apply(context,[arg1,arg2,...])
,两个参数,第二个为参数数组。
返回值:方法无返回值则返回undefined
功能说明:属性混入、继承,就是把这个对象的属性、方法传授给调用者。就不需要重复写了。
若没有第一个参数,则
this
绑定为全局对象 ;如果为严格模式use strict
则为undefined
非严格模式下,指定null
、undefined
会自动指向全局对象。
call(context,arg1,arg2,…)
// 实现继承
function Person(name,age){
this.name=name;
this.age=age;
}
function Young(name,age){
Person.call(this,name,age);
this.feature="naughty";
}
console.log(new Young("大飞",32).name); // output: "大飞"
// 绑定this上下文对象
let obj=[
{
name:"001",
age:"12",
},
{
name:"002",
age:"23",
}
];
for(let i=0;i<obj.length;i++){
(function(index){
this.print = function(){
console.log("obj#"+index,this.name,this.age);
}
this.print();
}).call(obj[i],i);
}
// output:obj#0 001 12
// obj#1 002 23
apply(context,[arg1,arg2,…])
可接收类数组对象,注意兼容性NodeList
。
// 向数组添加成员,一般我们使用for循环添加成员
var arr=[1,2,3];
var item=[4,5];
arr.push.apply(arr,item);
// arr:[1,2,3,4,5]
使用
contact
合并两个数组,返回的是一个新数组。
// 数值比较,多值比较麻烦
var min = Math.min(2,12,1,5); // min:1
var arr = [2,12,1,5];
var min = Math.min.apply(Math,arr);
当比较的数组值很多时,注意越界问题。超过最大参数可能会丢失。
什么叫类数组对象
类数组对象须键值(key)
为数值,否则数组值为undefined、必须有length
属性,决定数组的长度。
let obj={
1:"1",
2:"2",
4:"4",
length:"6",
}
Array.prototype.join.call(obj,"-");
// output: -1-2--4-
//下标0 / 3 / 5 值都为空值
arguments
是一个类数组对象
。函数传参的参数对象,所有函数中可用的布局变量。
function TT(){
console.log(arguments);
}
TT(1,"world",[3,4]);
转换为Array
var args = Array.prototype.slice.call(arguments);
// es5
var args = Array.from(arguments);
var args =[...arguments];
// 使用Array的默认构造函数
var args = Array.apply(null,arguments);
属性
arguments.callee
:指向当前执行的函数。
arguments[@@iterator]
迭代器对象,包含参数索引。
function TT(){
let obj = arguments[Symbol.iterator]();
for(let item of obj){
console.log(item);
}
}
TT(1,"world",[3,4]);
// 1
// "world"
// [3,4]
使用typeof获取类型、返回
object