js基础

381 阅读21分钟

1.数据类型

  • 基本数据类型: Undefined、Null、Boolean、Number、String、Symbol(ES6)、BigInt(ES2020)
  • 引用数据类型: Object(包含了function、Array、Date)
  1. Undefined: 只有一个值,在使用var 声明变量但未对其加初始化时,这个变量就是undefined。
  2. Null: 只有一个值,null是表示一个空对象指针,这也是typeof操作符检测 null 值时会返回 object 的原因。

注意:

  • NaN是Number中的一种,是Number的特殊数值。
  • 用isNaN()检测是否是非数值型。
Number('as') // NaN
Number('as') == NaN // false
isNaN('56') // false
typeof null // 'object'(不存在的对象)
typeof undefined // 'undefined'(没有初始化、定义的值)
typeof NaN // 'number'(Number中的特殊数值)
typeof [] // 'object'
typeof {} // 'object'
typeof Object // 'function'
typeof Function // 'function'
typeof Date // 'function'
typeof console.log // 'function'
typeof Symbol() // 'symbol'
0 == false // true
0 === false // false
1 == 1 // true
1 === 1 // true
1 === Number(1) // true
1 == true // true
1 === true // false
{} == {} // false
{} === {} // false
[] == [] // false
[] === [] // fasle
null == null // true
null === null // true
undefined == undefined // true
undefined === undefined // true
NaN == NaN // false
NaN === NaN // false

2.变量声明

function f(x) { 
    var x; 
    var x; 
    if (true) { 
        var x; 
    } 
} // 所有`x`的声明实际上都引用一个相同的`x`


let x = 10; 
let x = 20; // 错误,不能在1个作用域里多次声明`x`
for (var i = 0; i < 5; i++) { 
    setTimeout(function() { 
        console.log(i); // 5 5 5 5 5
    }, 100 * i); 
}
说明:`setTimeout`在若干毫秒后执行一个函数,并且是在`for`循环结束后。 `for`循环结束后,`i`的值为`5`。 所以当函数被调用的时候,它会打印出 `5`

for (var i = 0; i < 5; i++) { 
    (function(i) {
        setTimeout(function() { 
            console.log(i); // 0 1 2 3 4
        }, 100 * i); 
    })(i)
}

for (let i = 0; i < 5; i++) { 
    setTimeout(function() { 
        console.log(i); // 0 1 2 3 4
    }, 100 * i); 
}
说明:当`let`声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在 `setTimeout`例子里我们仅使用`let`声明就可以了。

3.对象

  • 创建对象的方式
/*
* 对象字面量:
*/
var obj = {
    name: "张三",
    age: 18,
    getAge(){
        return this.age;
    }
}
console.log(obj.name); // 张三
console.log(obj.getAge()); // 18

/*
* 使用 new关键字:
* new操作符用于创建和初始化一个新对象, new关键字后面必须跟一个函数调用。以这种方式使用的函数
* 被称为构造函数,目的是初始化新创建的对象。
*/
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.say = function() { }
}

var obj = new Object(); // 通过js内置的类型构造函数创建;
var obj2 = new Array(); // 通过js内置的类型构造函数创建;
var obj3 = new Person('李四', 20); // 通过自定义的构造函数创建;
console.log(obj); // {}

console.log(obj2); // []
console.log(obj3); // Person {name: '李四', age: 20, say: ƒ}

/*
* Object.create():
* 用于创建一个新对象,使用第一个参数(现有的对象)作为新创建的对象的原型, 第二个参数用于描述新
* 对象的属性(该参数属于高级特性)。
* 用途: 防止对象被第三方库函数意外修改。这种情况,不要直接把对象传给库函数,而要传入一个继承自
* 己的对象,如果函数读取这个对象的属性,可以读到继承的值。而如果它设置这个对象的属性,则修改不会
* 影响原始对象。
*/
Object.create =  function (o) { // Object.create 内部实现
    let F = function () {};
    F.prototype = o;
    let newObj = new F();
    return newObj;
};
-----------------------------------------------------------
var o = { x: '不要修改这个值' };
library.function(Object.create(o)); // 防止意外修改;
-----------------------------------------------------------
var obj = Object.create({ }, {
    "a": { value: 1 },
    "b": { value: 2, congigurable: false, enumerable: true, writable: true },
    "c": { value: 3, congigurable: false, enumerable: true, writable: true }
});
console.log(obj.a) // 1
console.log(obj.b) // 2
-----------------------------------------------------------
let person = {
    age: null,
    sex: '男',
    printIntroduction: function() {
        console.log(`我的名字叫 ${this.name}。我的年龄 ${this.age}`);
    }
};

let obj = Object.create(person);
obj.name = '张三'; 
obj.age = 18; 
obj.printIntroduction(); // 我的名字叫 张三。我的年龄 18 (访问的是obj的原型方法printIntroduction)
console.log(obj); // { name: '张三', age: 18 } // name,age为obj实例上的属性
console.log(obj.sex); // 男 (obj.sex访问的是obj的原型属性sex)
console.log(obj.__proto__); // { age: null, sex: '男', printIntroduction: ƒ }
  • 对象的属性 对象的属性有两种:
  1. 数据属性:value、writable、enumerable、configurable。
  2. 访问器属性: getter、setter、enumerable、configurable。
/*
* 数据属性的四个特性:
* 1.value: 包含这个属性的值; 2.writable: 表示能否改下属性的值,默认是true; 3.enumerable:
* 表示能否被枚举,for-in 或者 Object.keys().默认是true; 4.configurable: 表示能否通过
* delete删除属性,或者能否修改属性的特性,或者能否把属性修改为访问器属性。在直接对象上定义的
* 属性,默认是 true。
* 通常用于定义属性的代码会产生数据属性。
*/
var obj = { a: 1 }; // a 为数据属性
obj.b = 2; // b 为数据属性
console.log(Object.getOwnPropertyDescriptor(obj, 'a')); 
// {value: 1, writable: true, enumerable: true, configurable: true}

/*
* 访问器(setter/getter)属性的四个特性:
* 1.getter: 函数或undefined,在取属性值时被调用; 2.setter: 函数或undefined,在设置属性值
* 时被调用; 3.enumerable: 表示能否被枚举,for-in 或者 Object.keys().默认是true; 4.
* configurable: 表示能否通过delete删除属性,或者能否修改属性的特性,或者能否把属性修改为访问
* 器属性。在直接对象上定义的属性,默认是 true。
* 如果我们要想改变属性的特性,或者定义访问器属性,我们可以使用 Object.defineProperty()。
*/
var obj = { a: 1 };
Object.defineProperty(obj, 'b', {
    value: 2, 
    writable: false, 
    enumerable: false, 
    configurable: true,
}); // a和 b都是数据属性,但是b的特性值变化了

console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
// {value: 1, writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(obj, 'b'));
// {value: 2, writable: false, enumerable: false, configurable: true}
-----------------------------------------------------------
var o = { 
    get a() {
        console.log('触发了getter'); // 触发了getter (先执行这里, 再打印下面的1)
        return 1;
    }
};
console.log(o.a); // 1 (访问器属性跟数据属性不同,每次访问属性都会执行getter或者setter函数。
                  // 这里我们的getter函数返回了1,所以o.a每次都得到1。)

  • 对象的方法
  1. es5中: Object.create( )、Object.keys( )、Object.getOwnPropertyNames( )、Object.getPrototypeOf( )、Object.setPrototypeOf( )、Object.freeze( )、Object.isFrozen( )、Object.getOwnPropertyDescriptor( )、Object.defineProperty( )、Object.defineProperties( )、Object.isExtensible( )、Object.preventExtensions( )、hasOwnProperty( )
  2. es6中: Object.assign( )、Object.is( )、Object.values( )、Object.entries( )、Object.getPrototypeOf( )、Object.setPrototypeOf( )、Object.getOwnPropertyDescriptors( )、Object.isExtensible( )、Object.preventExtensions( )
/*
* Object.create( ): 见对象的创建方式
*/

/*
* Object.keys(obj): 返回指定对象(obj)的可枚举属性组成的字符串数组。
* 返回的数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
*/
var arr = ['a', 'b', 'c'];
var obj = { 0: 'a', 1: 'b', 2: 'c' };
var obj2 = { 100: 'a', 2: 'b', 7: 'c' };
var obj3 = { name: '张三', getName() { return this.name } };
var f = function() {};
var str = "foo";

console.log(Object.keys(arr)); // ['0', '1', '2']
console.log(Object.keys(obj)); // ['0', '1', '2']
console.log(Object.keys(obj2)); // ['2', '7', '100']
console.log(Object.keys(obj3)); // ['name', 'getName']
console.log(Object.keys(f)); // []
console.log(Object.keys(str)); // TypeError: "foo" is not an object (es5)
                               // ['0', '1', '2'] (es6)

/*
* Object.getOwnPropertyNames(obj): 返回指定对象(obj)的所有自身属性的属性名(包括不可枚举属
* 性但不包括Symbol值作为名称的属性)组成的字符串数组。
*/
var arr = ['a', 'b', 'c'];
var obj = { 0: 'a', 1: 'b', 2: 'c' };
var obj2 = { 100: 'a', 2: 'b', 7: 'c' };
var obj3 = { name: '张三', getName() { return this.name } }
var str = "foo";

console.log(Object.getOwnPropertyNames(arr)); // '0', '1', '2', 'length']
console.log(Object.getOwnPropertyNames(obj)); // ['0', '1', '2']
console.log(Object.getOwnPropertyNames(obj2)); // ['2', '7', '100']
console.log(Object.getOwnPropertyNames(obj3)); // ['name', 'getName']
console.log(Object.getOwnPropertyNames(str)); // TypeError: "foo" is not an object (es5)
                                              // ['0', '1', '2', 'length'] (es6)
                                              
/*
* Object.getPrototypeOf(obj): 返回指定对象(obj)的原型(内部[[Prototype]]属性的值)。
* 返回值: 给定对象的原型。如果没有继承属性,则返回 null。
*/
var proto = {};
var obj = Object.create(proto);
var obj2 = new Object();
var F = function() {}

console.log(Object.getPrototypeOf(obj) === proto) // true
console.log(Object.getPrototypeOf(Object)) // ƒ () { [native code] }
console.log(Object.getPrototypeOf( Function )) // ƒ () { [native code] }
console.log(Object.getPrototypeOf(F)) // ƒ () { [native code] }
console.log(Object.getPrototypeOf(Object) === Function.prototype) // true
console.log(Object.prototype === Object.getPrototypeOf(obj2)) // true
console.log(Object.prototype === Object.getPrototypeOf({})) // true
// 说明: Object.getPrototypeOf(Object)是把 Object这一构造函数看作对象,
// 返回的当然是函数对象的原型,也就是 Function.prototype。

/*
* Object.defineProperty(obj, prop, descriptor): 用于在一个对象(obj)上定义一个新属性或
* 修改现有属性,并返回该对象(obj)。
* obj:要定义属性的对象; prop:要定义或修改的属性的名称或 Symbol; 3.descriptor:要定义或修改
* 的属性描述符。
* 该属性描述符有两种属性: 1.数据属性: value、writable、enumerable、configurable;
* 2.访问器属性: get、set、enumerable、configurable。
* 定义了value或 writable,一定不能有get或set,反之亦然,否则报错(数据属性与访问器属性不能混用)。
*/
var o = { };
Object.defineProperty(o, 'a', {
    value: 20,
    writable: false,
    enumerable: true,
    configurable: true,
});
o.a = 21;
console.log(o); // {a: 20} (writable为false,属性a的值不可写,故仍为 20)
-----------------------------------------------------------
var o = {};
var bValue = 30;
Object.defineProperty(o, 'b', {
  get() { return bValue; },
  set(newValue) { bValue = newValue; },
  enumerable : true,
  configurable : true
});
console.log(o.b); // 30
o.b = 32;
console.log(o.b); // 32
console.log(o); // { }

4.函数

函数就是可被重复调用执行的代码块,每个js中的函数实际上都是一个Function对象。

定义函数的方式:

  • 函数声明,function ff( ){ };
  • 函数表达式,var ff = function( ){ };
  • 使用Function对象来构造函数,var ff = new Function(arg1,arg2,...,function body); 调用方式:ff(arg1,arg2,...);
  • 箭头函数(es6),const ff = (x, y)=> x+y;  调用方式:ff(1,2)。 实例属性:
  • Function.prototype.length:函数期望的参数数量。
  • Function.prototype.name:函数的名称。 实例方法:
  • Function.prototype.apply(thisArg, argsArray):调用一个函数并将其 this 的值设置为提供的 thisArg。参数可用以通过数组对象传递。
  • Function.prototype.bind(thisArg, arg1, arg2, ...argN):创建一个新的函数,该函数在调用时,会将 this 设置为提供的 thisArg。在调用新绑定的函数时,可选的参数序列(, arg1, arg2, ...argN)会被提前添加到参数序列中(即调用绑定创建的函数时,传递的参数会接续在可选参数序列后)。
  • Function.prototype.call(thisArg, arg1, arg2, ...argN):调用一个函数,并将其 this 值设置为提供的值。也可以选择传输新参数。
  • Function.prototype.toString():返回表示函数源码的字符串。覆盖了Object.prototype.toString方法。 函数的分类:
  • 普通函数:用 function 关键字定义的函数。
  • 箭头函数(es6):用=> 运算符定义的函数。
  • 即时函数:表示可以在定义后立即调用。
  • 闭包函数:闭包就是能够读取其他函数内部变量的函数。
  • 嵌套函数:一个函数嵌套在另外一个函数中。
  • 回调函数:一个被当做参数的函数,即为回调函数
  • 递归函数:能够在函数结束后,将函数调用。
  • class函数(es6):
  • 生成器函数(generator)(es6):
  • 异步函数(async)(es6):
  • 高阶函数:
/*
* Function.prototype.apply(thisArg, argsArray): 调用一个具有给定this值的函数,以及以一个
* 数组(或类数组对象)的形式提供的参数。
* 语法: func.apply(thisArg, argsArray);
* 参数: 1.func: 函数; 2.thisArg: 必需,在 func函数运行时使用的 this值; 3.argsArray: 可选
* ,一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func函数。
* 返回值: 调用有指定 this值和参数的函数的结果。
* 注意: 1.如果 thisArg的值不传或为 null、undefined,非严格模式下,func中 this指向 window对
* 象;严格模式下,传什么指向什么,不传指向 undefined。
*/
var num = 1;
function logNum() {
    console.log(this.num);
}
logNum(); // 1 (logNum中的this指向 window)
logNum.apply({ num: 20 }); // 20 (logNum中的 this指向参数 { num: 20 })
console.log(this); // window (this指向 window)
-----------------------------------------------------------
function a(x, y) {
    var val = this(x, y);
    console.log(val); // 30
}
function b(m, n) {
    return m + n;
}
a.apply(b, [10, 20]);
-----------------------------------------------------------
var num0 = 1;
function add(num1, num2) {
    var sum = this.num0 + num1 + num2;
    console.log(sum);
}
add(2,3); // 6 (add中的 this指向 window)
add.apply({ num0: 10 }, [20, 30]); // 60 (add 中的 this指向参数 { num0: 10 })
-----------------------------------------------------------
var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ['a', 'b', 0, 1, 2]

/*
* Function.prototype.bind(thisArg, arg1, arg2, ...argN): 创建一个新的函数,在 bind()被调
* 用时,这个新函数的 this被指定为 bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使
* 用。
* 语法: func.bind(thisArg, arg1, arg2, ...argN);
* 参数: 1.func: 函数; 2.thisArg: 必需,在 以func函数拷贝的新函数运行时使用的 this值; 3.
* arg1, arg2, ...argN: 新创建函数被调用时,被预置入新创建函数的参数列表中的参数。
* 返回值: 返回一个 func函数的拷贝,并拥有指定的 this值和初始参数。
* 注意: 1.如果 thisArg的值不传或为 null、undefined,非严格模式下,func的拷贝新函数中 this指
* 向 window对象;严格模式下,传什么指向什么,不传指向 undefined。
*/
function a(m, n) {
    var sum = this(m, n);
    console.log(sum);
}
function b(x, y) {
    return x + y;
}
var copyA = a.bind(b, 10, 20);
console.log(copyA); 
// ƒ a(m, n) {
//    var sum = this(m, n);
//    console.log(sum);
// }
copyA(); // 30
-----------------------------------------------------------
var num0 = 1;
function add(num1, num2) {
    var sum = this.num0 + num1 + num2;
    console.log(sum);
}
var copyAdd = add.bind({ num0: 10 }, 20, 30);
copyAdd(); // 60

/*
* Function.prototype.call(thisArg, arg1, arg2, ...argN): 调用一个具有给定 this值的函数,
* 以及单独给出的一个或多个参数。
* 语法: func.call(thisArg, arg1, arg2, ...);
* 参数: 1.func: 函数; 2.thisArg: 可选,在 func函数运行时使用的 this值; 3.arg1, arg2, ...:
* 指定的参数列表。
* 返回值: 使用调用者提供的 this值和参数调用该函数的返回值。若该方法没有返回值,则返回 
* undefined。
* 注意: 1.该方法的语法和作用与 apply()方法类似,只有一个区别,就是 call()方法接受的是一个参数
* 列表,而 apply()方法接受的是一个包含多个参数的数组; 2.如果 thisArg的值不传或为 null、
* undefined,非严格模式下,func中 this指向 window对象;严格模式下,传什么指向什么,不传指向
* undefined。
*/
var num0 = 1;
function add(num1, num2) {
    var sum = this.num0 + num1 + num2;
    console.log(sum);
}
add(2,3); // 6 (add中的 this指向 window)
add.call({ num0: 10 }, 20, 30); // 60 (add 中的 this指向参数 { num0: 10 })
-----------------------------------------------------------
function fruits(){ };
fruits.prototype = {
    color: 'red',
    say() {
        console.log('my color is ' + this.color);
    }
}
var apple = new fruits();
apple.say(); // my color is red
apple.say.call({ color: 'yellow' }); // my color is yellow
-----------------------------------------------------------
function add(a, b) {
    console.log(a + b);
}
function sub(a, b) {
    console.log(a - b);
}
add.call(sub, 30, 20); // 50
add.apply(sub, [30, 20]); // 50
add.bind(sub, 30, 20)(); // 50

/*
* Function.prototype.toString(): 返回表示当前函数源码的字符串。
* 语法: func.toString();
* 参数: 1.func: 当前函数。
* 返回值: 表示函数源代码的一个字符串。
*/
function sum(a, b) {
    return a + b;
}
console.log(sum.toString());
// function sum(a, b) {
//     return a + b;
// }

① 递归函数

重新组合树形数据:

// demo/index.html:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>demo</title>
    </head>
    <body>
        <script type="text/javascript" src="./demo.js"></script>
    </body>
</html>

// demo/js:
var treeData = [
    {
        departmentName: "信息部",
        departmentId: 0,
        subset: [
            {
                departmentName: "数据中心",
                departmentId: 0,
                subset: [
                    {
                        departmentName: "ETL组",
                        departmentId: 0,
                        subset: null,
                    },
                    {
                        departmentName: "数据组",
                        departmentId: 1,
                        subset: null,
                    },
                    {
                        departmentName: "分析组",
                        departmentId: 2,
                        subset: null,
                    },
                ],
            },
        ],
    },
    {
        departmentName: "全域事业部",
        departmentId: 1,
        subset: [
            {
                departmentName: "战略发展中心",
                departmentId: 0,
                subset: [
                    {
                        departmentName: "华东大区",
                        departmentId: 0,
                        subset: null,
                    },
                    {
                        departmentName: "华南大区",
                        departmentId: 1,
                        subset: null,
                    },
                    {
                        departmentName: "华西大区",
                        departmentId: 2,
                        subset: null,
                    },
                    {
                        departmentName: "华北大区",
                        departmentId: 3,
                        subset: null,
                    },
                    {
                        departmentName: "华中大区",
                        departmentId: 4,
                        subset: null,
                    },
                ],
            },
            {
                departmentName: "业务拓展中心",
                departmentId: 1,
                subset: [
                    {
                        departmentName: "拓展一组",
                        departmentId: 0,
                        subset: null,
                    },
                    {
                        departmentName: "拓展二组",
                        departmentId: 1,
                        subset: null,
                    },
                    {
                        departmentName: "拓展三组",
                        departmentId: 2,
                        subset: null,
                    },
                ],
            },
        ],
    },
];

const handleTree = (treeList) => {
    return treeList.map((item) => {
        const { departmentName, departmentId, subset } = item;
        return {
            label: departmentName,
            value: departmentId,
            children: (() => {
                if (subset && subset.length > 0) {
                    return handleTree(subset);
                } else {
                    return null;
                }
            })(),
        };
    });
};

console.log("treeData:", handleTree(treeData));

打印的数据:

1664187681263.png

拓展
js通过递归实现树形数据操作

5.原型原型链

理解js中原型、原型链这个概念,绝对是帮助我们更深入学习js的必要一步,比如,如果js开发者想理解js继承,new关键字原理,甚至封装组件、优化代码,弄明白js中原型、原型链更是前提条件。本篇文章,用最简洁的文字,清楚明白讲解原型链相等关系和原型、原型链存在的意义,看完这篇文章,你会发现,原型、原型链原来如此简单!

1651819638(1).png

上面经典的原型链相等图,根据下文的学习,你会轻易掌握。

讲原型的时候,我们应该先要记住以下几个要点,这几个要点是理解原型的关键

  • 所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。
  • 所有的引用类型都有一个__proto__ 属性(也叫隐式原型,它是一个普通的对象)。
  • 所有的函数都有一个'prototype'属性(也叫显式原型,它是一个普通的对象)。
  • 所有的引用类型,它的__proto__属性指向它的构造函数的'prototype'属性。
  • 当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的__ proto__属性(也就是它的构造函数的'prototype'属性)中去寻找。

1651822087(1).png

原型(prototype):只有函数对象才有prototype属性,这个属性是一个指针,指向一个对象(通过该构造函数创建实例对象的原型对象),该属性的指向我们就可以叫做“原型”或者“原型对象”。

原型链(prototype chain):在JavaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接(__ proto__)。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链。

原型对象的作用:是用来存放实例中共有的那部份属性、方法,可以大大减少内存消耗。所以 js内置的函数对象,Object、Array的原型存在着很多属性和方法,Function是个例外,它的原型就是一个function空函数。

原型链的作用:js主要通过原型链实现继承,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。

js之父在设计js原型、原型链的时候遵从以下两个准则:

  1. Person.prototype.constructor == Person; 准则1:原型对象(即Person.prototype)的constructor指向构造函数本身;
  2. person01. __ proto __ == Person.prototype;准则2:实例(即person01)的__proto__和原型对象指向同一个地方。
/*
* 注意: 1.默认情况下,所有的原型(即原型对象)都会自动获得一个 constructor(构造函数)属性,该属
* 性(是一个指针)指向该函数的构造函数; 2.js对象均有一个__proto__属性,但__proto__并非是一个
* 规范属性,只是部分浏览器实现了此属性,对应的标准属性是 [[Prototype]]; 3.所有函数的
* __proto__ 都和 Function.prototype指向同一个地方; 4.除了Object的原型对象(Object.
* prototype)的__proto__指向 null,其他内置函数对象的原型对象(例如: Array.prototype)和自定
* 义构造函数的 __proto__都指向 Object.prototype,因为原型对象本身是普通对象。
*/
function Person() {};
console.log(Person.prototype);
//constructor:ƒ Person()
//__proto__:Object
console.log(Person.prototype.constructor == Person); // true
console.log(new Person().__proto__); 
//constructor:ƒ Person()
//__proto__:Object
console.log(new Person().__proto__ == Person.prototype); // true
-----------------------------------------------------------
var obj = {};
console.log(obj.__proto__.constructor); // Object() { [native code] }
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
-----------------------------------------------------------
function A() {};
console.log(A.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Array.__proto__ === Function.prototype); // true
-----------------------------------------------------------
var o1 = new Object();
var o2 = new Object();
o1.__proto__ === Object.prototype; // 准则2
o2.__proto__ === Object.prototype; // 准则2
Object.prototype.__proto__ === null; // 原型链到此停止
-----------------------------------------------------------
function Foo(){};
let f1 = new Foo();
let f2 = new Foo();

f1.__proto__ === Foo.prototype; // 准则2
f2.__proto__ === Foo.prototype; // 准则2
Foo.prototype.__proto__ === Object.prototype; // 准则2 (Foo.prototype本质也是普通对象,
                                              // 可适用准则2)。
Object.prototype.__proto__ === null; // 原型链到此停止

Foo.prototype.constructor === Foo; // 准则1
Foo.__proto__ === Function.prototype; // 准则2
Function.prototype.__proto__  === Object.prototype; // 准则2 (Function.prototype本质也
                                                    // 是普通对象,可适用准则2)。
Object.prototype.__proto__ === null; // 原型链到此停止
-----------------------------------------------------------
var arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null (原型链到此为止)

6.new关键字的实现

  • new关键字的实现原理(即new关键字所做的操作):
  1. 创建一个空对象(即{});
  2. 新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
  3. 将构造函数的作用域赋值给新对象(也就是this对象指向新对象);
  4. 返回新对象。
  • 自己封装一个 new关键字:
function myNew(fn, ...args) {
    const obj = {};
    obj.__proto__ = fn.prototype;
    let result = fn.apply(obj, args); // 改变this指向
    return typeof result === "object" ? result : obj;
}

function Person(name, age) {
    this.name = name;
    this.age = age;
}
var p = myNew(Person, '张三', 18);
console.log(p); // Person {name: '张三', age: 18}
  • Object.create其实就是利用空对象实现原型链继承:
// Object.create 源码实现:
Object.create = function(o){
    function F(){ };
    F.prototype = o;
    return new F();
}

7.数组

  • 数组的属性 数组属性有:length
  • 数组的方法
  1. es5中: pop( )、push( )、shift( )、splice( )、slice( )、unshift( )、concat( )、join( )、sort( )、reverse( )、reduce( )、forEach( )、map( )、every( )、some( )、filter( )、indexOf( )、lastIndexOf( )。
  2. es6中: Array.from( )、of( )、copyWithin( )、fill( )、find( )、findIndex( )、includes( )、entries( )、keys( )、values( )。
  3. es2019: flat( )。
/*
* 高阶函数 Array.prototype.filter(): 
* 定义: 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
* 语法: var newArray = arr.filter(callback(currentValue,index,array), thisArg)。
* 参数: 1.arr: 表示指定的原始数组; 2.currentValue: 必须,当前元素的值; 3.index: 可选,当前
* 元素的索引; 4.array: 可选,当前元素属于的数组对象(arr); 5.thisArg: 可选,执行 callback时,
* 用于this的值(如果省略 thisArg或其值为 null或 undefined, 非严格模式this则指向全局对象; 严
* 格模式 this指向所传参数值,省略 thisArg,则指向 undefined)。
* 返回值: 新的数组。
* 注意: 1.filter() 不会对空数组进行检测; 2.filter() 不会改变原始数组; 3.不对未初始化的值
* 进行任何操作。
*/
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var newArr = arr.filter((item) => item%2 !== 0); // 筛选数组中奇数
console.log(newArr) // [1, 5, 9, 15]
console.log(arr) // [1, 2, 4, 5, 6, 9, 10, 15] (原始数组不变)
-----------------------------------------------------------
var arr = ['apple', 'banana', 'pear', 'apple', 'orange', 'orange'];
var newArr = arr.filter((item, index, arrs) => {
    console.log(arrs); // ['apple', 'banana', 'pear', 'apple', 'orange', 'orange']
    return arrs.indexOf(item) === index; // 数组去重 (indexOf总是返回第一个元素的位置,
});                                      // 后续的重复元素位置与indexOf返回的位置不相等,
                                         // 因此被filter滤掉了)。
console.log(newArr) // ['apple', 'banana', 'pear', 'orange']
console.log(newArr.toString()) // apple,banana,pear,orange
-----------------------------------------------------------
var arr = ['1', '', null, '3.png', ' ',  null, 'a', undefined];
var newArr = arr.filter(item => item); // 去除数组中的 null,undefined,''
console.log(newArr) // ['1', '3.png', ' ', 'a']
-----------------------------------------------------------
var arr = ['1', '', null, '3.png', ' ',  null, 'a', undefined];
var newArr = arr.filter(item => item && item.trim()); // 去除空字符串(包含空格)
console.log(newArr) // ['1', '3.png', 'a']
-----------------------------------------------------------
var arrayA = [
    { name: 'a', type: 'letter' },
    { name: '1', type: 'digital' },
    { name: 'c', type: 'letter' },
    { name: '2', type: 'digital' },
];
var arrayB = arrayA.filter(function(item){ 
    return item.type == 'letter'; // 把对象数组 a中的某个属性值取出来存到数组 b中
})
console.log(arrayB); // [{ name: 'a', type: 'letter' }, { name: 'c', type: 'letter' }]
-----------------------------------------------------------
var arr = [10, 20];
var newArr = arr.filter(function() {
    console.log(this) // {name: '张三'}
}, { name: '张三' })
var newArr2 = arr.filter(function() {
    console.log(this) // [50]
}, [50])
var newArr3 = arr.filter(function() {
    console.log(this) // Number {30}
}, 30)
var newArr4 = arr.filter(function() {
    console.log(this) // String {''}
}, '')
var newArr5 = arr.filter(function() {
    console.log(this) // ƒ (){}
}, function(){})

/*
* 高阶函数 Array.prototype.forEach():
* 定义: 方法对数组的每个元素执行一次提供的函数。
* 语法: arr.forEach(callback(currentValue,index,array), thisArg)。
* 参数: 1.arr: 表示指定的原始数组; 2.currentValue: 必须,当前元素的值; 3.index: 可选,当前
* 元素的索引; 4.array: 可选,当前元素属于的数组对象(arr); 5.thisArg: 可选,执行 callback时,
* 用于this的值(如果省略 thisArg或其值为 null或 undefined, 非严格模式this则指向全局对象; 严
* 格模式 this指向所传参数值,省略 thisArg,则指向 undefined)。
* 返回值: undefined。
* 注意: 1.forEach()以升序遍历数组项,而不会改变原始数组; 2.除了抛出异常外,无法终止或跳出
* forEach()循环; 3.不对未初始化的值进行任何操作。
*/
var arr = [1, 3, , 7]; // 下标(索引)为 2的位置未初始化值
var numCallbackRuns = 0;
arr.forEach(function(item, index) { // 不对未初始化的值进行任何操作,直接跳过该位置
    console.log(item); // 1 3 7 (先依次打印 1 3 7,最后打印下面的 3)
    console.log(index) // 0 1 3
    numCallbackRuns++;
});
console.log(numCallbackRuns); // 3
console.log(arr[2]); // undefined
-----------------------------------------------------------
var arr2 = [1, 3, undefined, 7]; // 数组中的元素只要有值占位就算已初始化(undefined, NaN)
var numCallbackRuns2 = 0;
arr2.forEach(function(item) {
    console.log(item); // 1 3 undefined 7 (先依次打印 1 3 undefined 7,最后打印下面的 4)
    numCallbackRuns2++;
});
console.log(numCallbackRuns2); // 4
-----------------------------------------------------------
var arr = [1, 3, NaN, 7];
var a = arr.forEach(function(item, index) {
    console.log(item); // 1 3 NaN 7
    return 9;
});
console.log(a); // undefined [forEach()返回值为 undefined]
-----------------------------------------------------------
var arr = [10, 30];
arr.forEach(function() {
    console.log(this); // window (严格模式为 null)
}, null);
var arr = [10, 30];
arr.forEach(function() {
    console.log(this); // String {'hello word'}
}, 'hello word');

/*
* 高阶函数 Array.prototype.map():
* 定义: 方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
* 语法: var newArray = arr.map(callback(currentValue,index,array), thisArg)。
* 参数: 1.arr: 表示指定的原始数组; 2.currentValue: 必须,当前元素的值; 3.index: 可选,当前 
* 元素的索引; 4.array: 可选,当前元素属于的数组对象(arr); 5.thisArg: 可选,执行 callback时, 
* 用于this的值(如果省略 thisArg或其值为 null或 undefined, 非严格模式this则指向全局对象; 严
* 格模式 this指向所传参数值,省略 thisArg,则指向 undefined)。
* 返回值: 新的数组。
* 注意: 1.map()不会改变原始数组(当然可以在callback执行时改变原始数组)。
*/
var numbers = [1, 3, 5];
var newNumbers = numbers.map(function(item) {
    return item * 2;
});
console.log(newNumbers); // [2, 6, 10]
console.log(numbers); // [1, 3, 5] (原始数组不变)
-----------------------------------------------------------
var numbers = [1, 3, 5];
var newNumbers = numbers.map(function(item, index) {
    if(index < 2) {
        return item;
    }
});
console.log(newNumbers); // [1, 3, undefined]
console.log(numbers); // [1, 3, 5]
-----------------------------------------------------------
var numbers = [1, 3, 5];
var newNumbers = numbers.map(function(item, index) { });
console.log(newNumbers); // [undefined, undefined, undefined]
console.log(numbers); // [1, 3, 5]

/*
* 高阶函数 Array.prototype.some():
* 定义: 方法用来检测数组中是否存在符合指定条件的元素,存在就返回 true,不存在就返回 false。
* 语法: var booleanVal = arr.some(callback(currentValue,index,array), thisArg)。
* 参数: 1.arr: 表示指定的原始数组; 2.currentValue: 必须,当前元素的值; 3.index: 可选,当前
* 元素的索引; 4.array: 可选,当前元素属于的数组对象(arr); 5.thisArg: 可选,执行 callback时,
* 用于this的值(如果省略 thisArg或其值为 null或 undefined, 非严格模式this则指向全局对象; 严
* 格模式 this指向所传参数值,省略 thisArg,则指向 undefined)。
* 返回值: 布尔值。
* 注意: 1.some()不会对空数组进行检测; 2.如果有一个元素满足条件,则表达式返回 true,剩余的元素
* 不会再执行检测; 3.如果没有满足条件的元素,则返回false; 4.不会改变原始数组。
*/
var arr = [];
var booleanVal = arr.some(function() {
    console.log('hello word'); // arr为空数组,故不会执行,因此没有打印值
});
console.log(booleanVal); // false
-----------------------------------------------------------
var arr = [1, 3, 5];
var booleanVal = arr.some(function(item) {
    console.log(item); // 依次打印 1 3 5 (然后打印下面的false)
});
console.log(booleanVal); // false
-----------------------------------------------------------
const users = [
    { name: 'Bob', age: 60 },
    { name: 'Sarah', age: 20 },
    { name: 'Billy', age: 18 },
];  
const booleanVal = users.some((user, index) => {
    console.log(index); // 依次打印 0 1 2
    return user.age <= 18;
});
console.log(booleanVal); // true
-----------------------------------------------------------
const users = [
    { name: 'Billy', age: 18 },
    { name: 'Bob', age: 60 },
    { name: 'Sarah', age: 20 },
];  
const booleanVal = users.some((user, index) => {
    console.log(index); // 0 (只要有一个元素满足条件,则表达式返回 true,剩余的元素不会再执行
                        // 检测)。
    return user.age <= 18;  
});
console.log(booleanVal); // true

/*
* 高阶函数 Array.prototype.every():
* 定义: 检测一个数组内的所有元素是否都能通过某个指定函数的检测,如果都通过则返回true,否则返
* 回 false。
* 语法: var booleanVal = arr.every(callback(currentValue,index,array), thisArg)。
* 参数: 1.arr: 表示指定的原始数组; 2.currentValue: 必须,当前元素的值; 3.index: 可选,当前
* 元素的索引; 4.array: 可选,当前元素属于的数组对象(arr); 5.thisArg: 可选,执行 callback时,
* 用于this的值(如果省略 thisArg或其值为 null或 undefined, 非严格模式this则指向全局对象; 严
* 格模式 this指向所传参数值,省略 thisArg,则指向 undefined)。
* 返回值: 布尔值。
* 注意: 1.如果 arr是一个空数组,every()方法在一切情况下都会返回 true; 2.every()不会对空数组
* 进行检测; 3.every()方法会遍历数组的每一项,如果有有一项不满足条件,则表达式返回false,剩余的
* 项将不会再执行检测; 4.如果遍历完数组后,每一项都符合条件,则返回 true; 5.every()不会改变原
* 始数组。
*/
var arr = [];
var booleanVal = arr.every(function() {
    console.log('hello word'); // arr为空数组,故不会执行,因此没有打印值
});
console.log(booleanVal); // true
-----------------------------------------------------------
var arr = [1000, 2000, 3000]
var booleanVal = arr.every(function(item) {
    return item > 2000; // arr中的每个元素的值都要大于2000的情况,最后才返回 true
})
console.log(booleanVal)  // false

/*
* Array.prototype.indexOf():
* 定义: 返回指定元素在数组内的位置,找到第一个值时返回其索引,没找到返回-1。
* 语法: var searchIndex = arr.indexOf(searchElement,fromIndex)。
* 参数: 1.searchElement: 必须,要查找的元素; 2.fromIndex: 可选,开始查找的位置(如果该索引值
* 大于或等于数组长度,意味着不会在数组里查找,返回-1。如果参数中提供的索引值是一个负值,则将其
* 作为数组末尾的一个抵消,即-1表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找,以此
* 类推)。
* 返回值: 首个被找到的元素在 arr中的索引位置,若没有找到则返回 -1。
* 注意:1.如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。
* 如果抵消后的索引值仍小于 0,则整个数组都将会被查询。其默认值为 0。
*/
var array = [2, 5, 9];
console.log(array.indexOf(2)); // 0   
console.log(array.indexOf(7)); // -1   
console.log(array.indexOf(9, 2)); // 2 (在索引为 2的位置查找到数组元素 9)
console.log(array.indexOf(2, -1)); // -1 (从最后一个元素开始按顺序查找,没有查找到2返回 -1)
console.log(array.indexOf(2, -3)); // 0
console.log(array.indexOf(5, 2)); // -1 (从索引为 2的位置开始按顺序查找,没有查找到 5 返回
                                  // -1)。
                                  
/*
* 高阶函数: Array.prototype.lastIndexOf():
* 定义: 返回指定元素在数组中最后一次出现的位置(下标值),如果没有则返回 -1。
* 语法: var lastIndex = arr.lastIndexOf(searchElement,fromIndex)。
* 参数: 1.arr: 表示指定的原始数组; 2.searchElement: 必须,被查找的元素; 3.fromIndex: 可选,
* 从此位置(索引)开始逆向查找 [默认为数组的长度减 1(arr.length - 1),即整个数组都被查找。如果
* 该值大于或等于数组的长度,则整个数组会被查找。如果为负值,将其视为从数组末尾向前的偏移。即使
* 该值为负,数组仍然会被从后向前查找。如果该值为负时,其绝对值大于数组长度,则方法返回 -1,即数组
* 不会被查找]。
* 返回值: 数组中该元素最后一次出现的索引,如未找到返回-1。
*/
var array = [2, 5, 9, 2];
console.log(array.lastIndexOf(2)); // 3   
console.log(array.lastIndexOf(7)); // -1   
console.log(array.lastIndexOf(2,3)); // 3
console.log(array.lastIndexOf(2,2)); // 0
console.log(array.lastIndexOf(2,-5)); // -1
console.log(array.lastIndexOf(2,-4)); // 0
console.log(array.lastIndexOf(2,-2)); // 0 (从倒数第二个位置开始逆向查找)
console.log(array.lastIndexOf(2,-1)); // 3 (从最后一个位置开始逆向查找)

/*
* 高阶函数: Array.prototype.splice():
* 定义: 用于插入、删除或替换数组的元素,并以数组形式返回被修改的内容。
* 语法: var modifyArr = arr.splice(startIndex,deleteCount,item1,item2, ...)。
* 参数: 1.arr: 表示原始数组; 2.startIndex: 必需,指定修改(添加/删除)的开始位置(索引) [如果
* 超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从-1计数,
* 这意味着-n是倒数第n个元素并且等价于array.length-n);如果负数的绝对值大于数组的长度,则表示
* 开始位置为第 0位]; 3.deleteCount: 可选,表示要移除的数组元素的个数 [如果deleteCount大于
* start之后的元素的总数,则从start后面的元素都将被删除(含第start位)。如果deleteCount被省略
* 了,或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素
* 的数量),那么start之后数组的所有元素都会被删除。如果deleteCount是 0 或者负数,则不移除元素。
* 这种情况下,至少应添加一个新元素]; 4.item1,item2,...: 可选,要添加到数组的新元素。
* 返回值: 由被删除的元素组成的一个数组(如果没有删除元素,则返回空数组)。
* 注意: 1.splice()会改变原始数组。
*/
var arr = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removedArr = arr.splice(2, 0, 'drum');
console.log(removedArr); // []
console.log(arr); // ['angel', 'clown', 'drum', 'mandarin', 'sturgeon']
-----------------------------------------------------------
var arr2 = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removedArr2 = arr2.splice(2, 0, 'drum', 'guitar');
console.log(removedArr); // []
console.log(arr2); // ['angel', 'clown', 'drum', 'guitar', 'mandarin', 'sturgeon']
-----------------------------------------------------------
var arr3 = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon'];
var removedArr3 = arr3.splice(3, 1);
console.log(removedArr3); // ['mandarin']
console.log(arr3); // ['angel', 'clown', 'drum', 'sturgeon']
-----------------------------------------------------------
var arr4 = ['angel', 'clown', 'drum', 'sturgeon'];
var removedArr4 = arr4.splice(2, 1, 'trumpet'); // 相当于下标为 2的元素的值被替换
console.log(removedArr4); // ['drum']
console.log(arr4); // ['angel', 'clown', 'trumpet', 'sturgeon']
-----------------------------------------------------------
var arr5 = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removedArr5 = arr5.splice(2);
console.log(removedArr5); // ['mandarin', 'sturgeon']
console.log(arr5); // ['angel', 'clown']

/*
* 高阶函数 Array.prototype.slice():
* 定义: 从已有的数组中返回选定的元素。
* 语法: var newArr = arr.slice(begin,end)。
* 参数: 1.arr: 表示原始数组; 2.begin: 可选,开始索引,从该索引开始提取原数组元素 [如果该参数为
* 负数,则表示从原数组中的倒数第几个元素开始提取。如果省略 begin,则 slice从索引 0开始。如果
* begin超出原数组的索引范围,则会返回空数组]; 3.end: 可选,结束索引 [slice会提取原数组中索引从
* begin到 end的所有元素(包含begin,但不包含end)。如果该参数为负数,则它表示在原数组中的倒数第
* 几个元素结束抽取。如果end被省略,则 slice会一直提取到原数组末尾。如果 end大于数组的长度,
* slice也会一直提取到原数组末尾]。
* 返回值: 一个含有被提取元素的新数组 [由 begin和 end决定的原始数组的浅拷贝(包括 begin,不包括
* end)]。
* 注意: 1.slice()不会改变原始数组。
*/
var arr = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(arr.slice()); // ['ant', 'bison', 'camel', 'duck', 'elephant']
console.log(arr.slice(2)); // ['camel', 'duck', 'elephant']
console.log(arr.slice(2, 4)); // ['camel', 'duck']
console.log(arr.slice(1, 5)); // ['bison', 'camel', 'duck', 'elephant']
console.log(arr.slice(-2)); // ['duck', 'elephant']
console.log(arr.slice(2, -1)); // ['camel', 'duck']
console.log(arr); // ['ant', 'bison', 'camel', 'duck', 'elephant']

8.立即执行函数

立即执行函数只有一个作用:创建一个独立的作用域。这个作用域里面的变量,外面访问不到(即避免了「变量污染」)。

var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
    liList[i].onclick = function(){
        alert(i) // 为什么 alert 出来的总是 6,而不是 0、1、2、3、4、5
    }
}

var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
    (function(j){
        liList[j].onclick = function(){
            alert(j) // 0、1、2、3、4、5
        }
    })(i)
}

9.数据拷贝

浅拷贝、深拷贝
① 浅拷贝:对对象而言,它的第一层属性值如果是基本数据类型则完全拷贝一份数据,如果是引用类型就拷贝内存地址;
② 深拷贝:深拷贝就是不管是基本数据类型还是引用数据类型都重新拷贝一份, 不存在共用数据的现象。

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

let obj1 = {
    name: 'yang',
    res: {
        value: 123
    }
}

let obj2 = Object.assign({}, obj1)
obj2.res.value = 456
console.log(obj2) // {name: "yang", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
obj2.name = 'haha'
console.log(obj2) // {name: "haha", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}

展开语法 Spread

let obj1 = {
    name: 'yang',
    res: {
        value: 123
    }
}

let {...obj2} = obj1
obj2.res.value = 456
console.log(obj2) // {name: "yang", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}
obj2.name = 'haha'
console.log(obj2) // {name: "haha", res: {value: 456}}
console.log(obj1) // {name: "yang", res: {value: 456}}

② 数组
实际上对于数组来说, 只要不修改原数组, 重新返回一个新数组就可以实现浅拷贝,比如说map、filter、reduce等方法。

Array.prototype.slice

const arr1 = [
     'yang',
     {
         value: 123
     }
];

const arr2 = arr1.slice(0);
arr2[1].value = 456;
console.log(arr2); // ["yang", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
arr2[0] = 'haha';
console.log(arr2); // ["haha", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]

Array.prototype.concat

const arr1 = [
    'yang',
    {
        value: 123
    }
];

const arr2 = [].concat(arr1);
arr2[1].value = 456;
console.log(arr2); // ["yang", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]
arr2[0] = 'haha';
console.log(arr2); // ["haha", {value: 456}]
console.log(arr1); // ["yang", {value: 456}]

深拷贝的方法
① 对象
JSON.parse(JSON.stringify(object))

let obj = JSON.parse(JSON.stringify($obj))
console.log(obj) 			// 不能解决循环引用
/*
	VM348:1 Uncaught TypeError: Converting circular structure to JSON
    at JSON.stringify (<anonymous>)
    at <anonymous>:1:17
*/
delete $obj.c.d
let obj = JSON.parse(JSON.stringify($obj))
console.log(obj) 			// 丢失了大部分属性
/*
	{
        a: null
        c: {a: 1}
        date: "2020-04-05T09:51:32.610Z"
        e: {}
        f: {}
  }
*/

存在的问题:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
  • 不能正确处理new Date()
  • 不能处理正则
  • 不能处理new Error()

② 数组
原理:我们在拷贝的时候判断一下属性值的类型,如果是对象,我们递归调用深拷贝函数就好了

let deepCopy = function (obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    let newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 如果obj的子属性是对象,则进行递归操作,否则直接赋值
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

拓展
“深拷贝” 与 “浅拷贝” 的区别,JS实现深浅拷贝的几种方法
JavaScript系列: 手撕JS中的深浅拷贝
js 对象拷贝的三种方法,深拷贝函数
千万不要用JSON.stringify()去实现深拷贝!有巨坑!!

把对象添加进数组后,改变一个对象的值,数组内对象也跟变化的解决办法!