写点什么

JavaScript 学习笔记——数据类型

用户头像
zjlulsum
关注
发布于: 2020 年 05 月 11 日
JavaScript 学习笔记——数据类型

类型分类

基本数据类型

  • Number

  • Boolean

  • String

  • Null

  • Undefined

  • Symbol

引用类型

  • 数组(Array)

  • 函数(Function)

  • 正则(RegExp)

  • 日期(Date)


其中引用类型可变,基本数据类型不可变。

怎么理解可变不可变?

  • 可变是指原始类型存储在栈里面,每个的大小固定,改变时就会重新开一个固定内存,而原地址会被回收。

  • 不可变是指引用类型存储在堆栈上可以动态改变大小。


类型判断

typeof

返回值:类型字符串

typeof 本身是一个运算符,和加减乘除一样

var a = Symbol();var c = function(){};console.log(typeof(1));//numberconsole.log(typeof('sad'));//stringconsole.log(typeof(true));//booleanconsole.log(typeof(a));//symbolconsole.log(typeof(null));//objectconsole.log(typeof(undefined));//undefinedconsole.log(typeof([2]));//objectconsole.log(typeof({b:3}));//objectconsole.log(typeof(c));//function
复制代码

由上可知,typeof 可区分 number,string,boolean,symbol,undefined,object 中的 function。而数组等其他的对象类型不能区分。且 null 类型返回 Object。

instanceof

返回值:布尔值,判断这个对象是否是这个特定类或者是它的子类的一个实例。

[] instanceof Array; //true[] instanceof Object; //true
复制代码

instanceof 只能判断对象,不能判断基本类型,因为基本类型没有原型。

constructor

判断实例对象是由哪个构造函数产生的

var f = new F();f.constructor === F;// true
复制代码

constructor易变,不可信赖,当重写prototype后原有的constractor会丢失

function F(){}F.prototype={    _name:'Eric',};var f = new F();f.constructor === F;//false
复制代码

所以在重写原型时需要给constructor赋值

function F(){}F.prototype={    constructor:F,    _name:'Eric',};var f = new F();f.constructor === F;//true
复制代码


Object.prototype.toString.call()

toStringObject原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲是toString运行时的this指向的对象类型。


当 toString 方法被调用的时候,下面的步骤会被执行:

  1. 如果 this 值是 undefined,就返回 [object Undefined]

  2. 如果 this 的值是 null,就返回 [object Null]

  3. 让 O 成为 ToObject(this) 的结果

  4. 让 class 成为 O 的内部属性 [[Class]] 的值

  5. 最后返回由 "[object " 和 class 和 "]" 三个部分组成的字符串


console.log(Object.prototype.toString.call(undefined)) // [object Undefined]console.log(Object.prototype.toString.call(null)) // [object Null]
var date = new Date();console.log(Object.prototype.toString.call(date)) // [object Date]
复制代码

这个 class 值就是识别对象类型的关键


需要注意的是,必须通过Object.prototype.toString.call() 来获取。因为大部分对象自身都实现了toString方法,这样就可能会导致ObjecttoString被终止查找,因此要用call来强制执行ObjecttoString方法。


实现一个精确判断类型的函数

var class2type = {};
//把每个类型变成对象存在class2type中"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) { class2type["[object " + item + "]"] = item.toLowerCase();})
function type(obj) { if (obj == null) { return obj + ""; } return typeof obj === "object" || typeof obj === "function" ? class2type[Object.prototype.toString.call(obj)] || "object" : typeof obj;}
复制代码

有了 type 函数后,我们可以对常用的判断直接封装,比如 isFunction, isArray:

function isFunction(obj) {    return type(obj) === "function";} function isArray(obj) {    return type(obj) === "array";}
复制代码

复杂判断

plainObject

什么是 plainObject?

plainObject 来自于 jQuery,可以翻译成纯粹的对象,所谓"纯粹的对象",就是该对象是通过 "{}" 或 "new Object" 创建的,该对象含有零个或者多个键值对。

我认为主要是用来区别是自定义构造函数和 Object 构造函数

function F(){    console.log('1');}c = new F();Object.prototype.toString.call(c)//"[object Object]"//自定义构造函数Object.prototype.toString.call()也返回"[object Object]"
复制代码

jQuery isPlainObject 的实现源码

var class2type = {};
// 相当于 Object.prototype.toStringvar toString = class2type.toString;
// 相当于 Object.prototype.hasOwnPropertyvar hasOwn = class2type.hasOwnProperty;
function isPlainObject(obj) { var proto, Ctor;
// 排除掉明显不是obj的以及一些宿主对象如Window if (!obj || toString.call(obj) !== "[object Object]") { return false; }
/** * getPrototypeOf es5 方法,获取 obj 的原型 * 以 new Object 创建的对象为例的话 * obj.__proto__ === Object.prototype */ proto = Object.getPrototypeOf(obj);
// 没有原型的对象是纯粹的,Object.create(null) 就在这里返回 true if (!proto) { return true; }
/** * 以下判断通过 new Object 方式创建的对象 * 判断 proto 是否有 constructor 属性,如果有就让 Ctor 的值为 proto.constructor * 如果是 Object 函数创建的对象,Ctor 在这里就等于 Object 构造函数 */ Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
// 在这里判断 Ctor 构造函数是不是 Object 构造函数,用于区分自定义构造函数和 Object 构造函数 return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);}
复制代码

这里需要区分一下:

Object.hasOwnProperty.toString.call(Ctor)和 Object.prototype.toString.call(Ctor)

console.log(Object.hasOwnProperty.toString.call(Ctor)); // function Object() { [native code] }console.log(Object.prototype.toString.call(Ctor)); // [object Function]
复制代码

因为hasOwnProperty是一个函数,所以Object.hasOwnProperty.toString调用的Function.prototype.toString


而且 Function 对象覆盖了从 Object 继承来的 Object.prototype.toString 方法。函数的 toString 方法会返回一个表示函数源代码的字符串。具体来说,包括 function 关键字,形参列表,大括号,以及函数体中的内容。

所以源码最后一行代码中的 hasOwn.toString.call(Ctor) 返回的是构造函数代码。

例如上面的 Function c :

function F(){    console.log('1');}c = new F();Ctor = Object.getPrototypeOf(c).constructorObject.hasOwnProperty.toString.call(Ctor)
/*"function F(){ console.log('1');}"*/
复制代码

EmptyObject

jQuery isEmptyObject 源码

function isEmptyObject( obj ) {
var name;
for ( name in obj ) { return false; }
return true;}
复制代码

但是根据这个源码我们可以看出 isEmptyObject 实际上判断的并不仅仅是空对象。

举个栗子:

console.log(isEmptyObject({})); // trueconsole.log(isEmptyObject([])); // trueconsole.log(isEmptyObject(null)); // trueconsole.log(isEmptyObject(undefined)); // trueconsole.log(isEmptyObject(1)); // trueconsole.log(isEmptyObject('')); // trueconsole.log(isEmptyObject(true)); // true
复制代码


isArrayLike

源码

function isArrayLike(obj) {
// obj 必须有 length属性 var length = !!obj && "length" in obj && obj.length; var typeRes = type(obj);
// 排除掉函数和 Window 对象 if (typeRes === "function" || isWindow(obj)) { return false; }
return typeRes === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj;}
复制代码

所以如果 isArrayLike 返回 true,至少要满足三个条件之一:

  1. 是数组

  2. 长度为 0

  3. lengths 属性是大于 0 的数字类型,并且 obj[length - 1]必须存在


第二个条件存在争议

var obj = {a: 1, b: 2, length: 0}//这个对象用isArray返回true
复制代码

之所以保留 length === 0 是因为 arguments 是一个类数组对象

function a(){    console.log(isArrayLike(arguments))}a();
复制代码

这里应该返回 true。


第三个条件为什么要 obj[length - 1] 必须存在?

因为数组 length 的长度是最后一个元素的 key 值加 1

数组 2 种写法:

var arr1 = [,,3]
//当我们写一个对应的类数组对象就是:var arrLike = { 2: 3, length: 3}
var arr2 = [1,,];console.log(arr.length) // 2
复制代码

关于为什么 arr2 长度是 2 而不是 3,可以看尾后逗号,JavaScript 忽略数组中的尾后逗号


var arrLike = {    0: 1}console.log(arrLike.length)//undefined
var arrLike = { 0: 1, length:1}console.log(arrLike.length)//1
复制代码

总结

通过 isArrayLike 的第二种判断条件存在的问题,可以总结出:一些方法的实现也并不是非常完美和严密的,但是最后为什么这么做,其实也是一种权衡,权衡所失与所得。所有这些点,都必须脚踏实地在具体应用场景下去分析、去选择,要让场景说话。


类型转换

原始值转数字

方法:Number()

如果 Number 函数不传参数,返回 +0,如果有参数,调用 ToNumber(value)

ToNumber 对应的结果表


让我们写几个例子验证一下:

console.log(Number()) // +0console.log(Number(undefined)) // NaNconsole.log(Number(null)) // +0console.log(Number(false)) // +0console.log(Number(true)) // 1console.log(Number("123")) // 123console.log(Number("-123")) // -123console.log(Number("1.2")) // 1.2console.log(Number("000123")) // 123console.log(Number("-000123")) // -123console.log(Number("0x11")) // 17console.log(Number("")) // 0console.log(Number(" ")) // 0console.log(Number("123 123")) // NaNconsole.log(Number("foo")) // NaNconsole.log(Number("100a")) // NaN
复制代码

如果通过 Number 转换函数传入一个字符串,它会试图将其转换成一个整数或浮点数,而且会忽略所有前导的 0,如果有一个字符不是数字,结果都会返回 NaN,鉴于这种严格的判断,我们一般还会使用更加灵活的 parseInt 和 parseFloat 进行转换。

parseInt 只解析整数,parseFloat 则可以解析整数和浮点数,如果字符串前缀是 "0x" 或者"0X",parseInt 将其解释为十六进制数,parseInt 和 parseFloat 都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回 NaN:

console.log(parseInt("3 abc")) // 3console.log(parseFloat("3.14 abc")) // 3.14console.log(parseInt("-12.34")) // -12console.log(parseInt("0xFF")) // 255console.log(parseFloat(".1")) // 0.1console.log(parseInt("0.1")) // 0
复制代码

原始值转字符

方法:String()

如果 String 函数不传参数,返回空字符串,如果有参数,调用 ToString(value)

对象转字符串和数字

toString
Object.prototype.toString.call({a: 1}) // "[object Object]"({a: 1}).toString() // "[object Object]"({a: 1}).toString === Object.prototype.toString // true
复制代码

我们可以看出当调用对象的 toString 方法时,其实调用的是 Object.prototype 上的 toString 方法。

然而 JavaScript 下的很多类根据各自的特点,定义了更多版本的 toString 方法。例如:

  • 数组的 toString 方法将每个数组元素转换成一个字符串,并在元素之间添加逗号后合并成结果字符串。

  • 函数的 toString 方法返回源代码字符串。

  • 日期的 toString 方法返回一个可读的日期和时间字符串。

  • RegExp 的 toString 方法返回一个表示正则表达式直接量的字符串。

console.log(({}).toString()) // [object Object]
console.log([].toString()) // ""console.log([0].toString()) // 0console.log([1, 2, 3].toString()) // 1,2,3console.log((function(){var a = 1;}).toString()) // function (){var a = 1;}console.log((/\d+/g).toString()) // /\d+/gconsole.log((new Date(2010, 0, 1)).toString()) // Fri Jan 01 2010 00:00:00 GMT+0800 (CST)
复制代码

转换规则

参数类型结果 Object1. primValue = ToPrimitive(input, String)2. 返回 ToString(primValue).


先调用一个 ToPrimitive 方法,将其转为基本类型,然后再参照“原始值转字符” 的对应表进行转换。


valueOf

表示对象的原始值。默认的 valueOf 方法返回这个对象本身,数组、函数、正则简单的继承了这个默认方法,也会返回对象本身。日期是一个例外,它会返回它的一个内容表示: 1970 年 1 月 1 日以来的毫秒数。

var date = new Date(2017, 4, 21);console.log(date.valueOf()) // 1495296000000
复制代码
转换规则:

参数类型结果 Object1. primValue = ToPrimitive(input, Number)2. 返回 ToNumber(primValue)。

先调用一个 ToPrimitive 方法,将其转为基本类型,然后再参照“原始值转数字”的对应表进行转换。


总结

对象转字符:

String():

1.如果对象有 toString 方法,就调用。如果返回一个原始值,则将这个原始值变成字符串并返回。

2.如果对象没有 toString 方法,或者这个方法并不返回一个原始值(比如重写了 toString()),那么 JavaScript 会调用 valueOf 方法。如果存在这个方法,则 JavaScript 调用它。如果返回值是原始值,JavaScript 将这个值转换为字符串,并返回这个字符串的结果。

3.否则,JavaScript 无法从 toString 或者 valueOf 获得一个原始值,这时它将抛出一个类型错误异常。

对象转数字:

Number():

对象转数字的过程中,JavaScript 做了同样的事情,只是它会首先尝试 valueOf 方法

  1. 如果对象具有 valueOf 方法,且返回一个原始值,则 JavaScript 将这个原始值转换为数字并返回这个数字

  2. 否则,如果对象具有 toString 方法,且返回一个原始值,则 JavaScript 将其转换并返回。

  3. 否则,JavaScript 抛出一个类型错误异常。

举个例子:

console.log(Number({})) // NaNconsole.log(Number({a : 1})) // NaNconsole.log(Number([])) // 0console.log(Number([0])) // 0console.log(Number([1, 2, 3])) // NaNconsole.log(Number(function(){var a = 1;})) // NaNconsole.log(Number(/\d+/g)) // NaNconsole.log(Number(new Date(2010, 0, 1))) // 1262275200000console.log(Number(new Error('a'))) // NaN
复制代码

注意,在这个例子中,[][0] 都返回了 0,而 [1, 2, 3] 却返回了一个 NaN。我们分析一下原因:

当我们 Number([]) 的时候,先调用 []valueOf 方法,此时返回 [],因为返回了一个对象而不是原始值,所以又调用了 toString 方法,此时返回一个空字符串,接下来调用 ToNumber 这个规范上的方法,参照对应表,转换为 0, 所以最后的结果为 0

而当我们 Number([1, 2, 3]) 的时候,先调用 [1, 2, 3]valueOf 方法,此时返回 [1, 2, 3],再调用 toString 方法,此时返回 1,2,3,接下来调用 ToNumber,参照对应表,因为无法转换为数字,所以最后的结果为 NaN


JSON.stringify

值得一提的是:JSON.stringify() 方法可以将一个 JavaScript 值转换为一个 JSON 字符串,实现上也是调用了 toString 方法,也算是一种类型转换的方法。下面讲一讲 JSON.stringify 的注意要点:

1.处理基本类型时,与使用 toString 基本相同,结果都是字符串,除了 undefined

console.log(JSON.stringify(null)) // nullconsole.log(JSON.stringify(undefined)) // undefined,注意这个undefined不是字符串的undefinedconsole.log(JSON.stringify(true)) // trueconsole.log(JSON.stringify(42)) // 42console.log(JSON.stringify("42")) // "42"
复制代码

2.布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。

JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]"
复制代码

3.undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时(以保证单元位置不变))。


JSON.stringify({x: undefined, y: Object, z: Symbol("")}); // "{}"JSON.stringify([undefined, Object, Symbol("")]);          // "[null,null,null]"
复制代码

4.JSON.stringify 有第二个参数 replacer,它可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除。

function replacer(key, value) {  if (typeof value === "string") {    return undefined;  }  return value;}var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};var jsonString = JSON.stringify(foo, replacer);console.log(jsonString)// {"week":45,"month":7}var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};console.log(JSON.stringify(foo, ['week', 'month']));// {"week":45,"month":7}
复制代码

5.如果一个被序列化的对象拥有 toJSON 方法,那么该 toJSON 方法就会覆盖该对象默认的序列化行为:不是那个对象被序列化,而是调用 toJSON 方法后的返回值会被序列化,例如:

var obj = {  foo: 'foo',  toJSON: function () {    return 'bar';  }};JSON.stringify(obj);      // '"bar"'JSON.stringify({x: obj}); // '{"x":"bar"}'
复制代码

隐式转换

一元操作符+
console.log(+'1');
复制代码

当 + 运算符作为一元操作符的时候,查看 ES5规范1.4.6,会调用 ToNumber 处理该值,相当于 Number('1'),最终结果返回数字 1

那么下面的这些结果呢?

console.log(+[]);//0console.log(+['1']);//1console.log(+['1', '2', '3']);//NaNconsole.log(+{});//NaN
复制代码
二元操作符+

当计算 value1 + value2 时:

  1. lprim = ToPrimitive(value1)

  2. rprim = ToPrimitive(value2)

  3. 如果 lprim 是字符串或者 rprim 是字符串,那么返回 ToString(lprim) 和 ToString(rprim)的拼接结果

  4. 返回 ToNumber(lprim) 和 ToNumber(rprim)的运算结果


  • Null 与数组:

console.log(null + 1);
复制代码

按照规范的步骤进行分析:

  1. lprim = ToPrimitive(null) 因为 null 是基本类型,直接返回,所以 lprim = null

  2. rprim = ToPrimitive(1) 因为 1 是基本类型,直接返回,所以 rprim = 1

  3. lprim 和 rprim 都不是字符串

  4. 返回 ToNumber(null) 和 ToNumber(1) 的运算结果

接下来:

ToNumber(null) 的结果为 0,(回想上篇 Number(null)),ToNumber(1) 的结果为 1

所以,null + 1 相当于 0 + 1,最终的结果为数字 1


  • 数组与数组: 

console.log([] + []);
复制代码

依然按照规范:

  1. lprim = ToPrimitive([]),[]是数组,相当于 ToPrimitive([], Number),先调用 valueOf 方法,返回对象本身,因为不是原始值,调用 toString 方法,返回空字符串""

  2. rprim 类似。

  3. lprim 和 rprim 都是字符串,执行拼接操作

所以,[] + []相当于 "" + "",最终的结果是空字符串""


  • 数组与对象:

console.log([] + {});console.log({} + []);
复制代码

按照规范:

  1. lprim = ToPrimitive([]),lprim = ""

  2. rprim = ToPrimitive({}),相当于调用 ToPrimitive({}, Number),先调用 valueOf 方法,返回对象本身,因为不是原始值,调用 toString 方法,返回 "[object Object]"

  3. lprim 和 rprim 都是字符串,执行拼接操作

所以,[] + {} 相当于 "" + "[object Object]",最终的结果是 "[object Object]"。

下面的例子,可以按照示例类推出结果:

console.log(1 + true); // 2console.log({} + {}); // "[object Object][object Object]"console.log(new Date(2017, 04, 21) + 1) // "Sun May 21 2017 00:00:00 GMT+0800 (CST)1"
复制代码
  • ==相等:


image.png


分类:

1.左右类型相同:

a.有一个 type 为 Undefined 或 Null 返回 true

b.如果有一个 type 为 Number:

i.另一个为 NaN,则返回 false

NaN == NaN //false NaN与任何值都不相等,包括自己 

ii.2 个数值相等,返回 true

iv.分别为+0 和-0,返回 true

c.如果有一个是 String 则当两个完全相同才会 true

d.如果有一个是 Boolean 则都为 ture 或 false,才返回 true

f.两个引用同一对象时返回 true

2.左右 type 不同时:

a.一个为 null 另一个为 undefined,返回 true

b.一个为 Number 另一个为 String,将 String 用 ToNumber()转换成 Number 后,再与 Number 类型比较

c.有一个是 Boolean,将 Boolean 用 ToNumber()转换成 Number 类型,再比较。

/*true == '2' 就相当于 1 == '2' 就相当于 1 == 2,结果自然是 false。
所以当一方是布尔值的时候,会对布尔值进行转换,因为这种特性,所以尽量少使用 xx == true 和 xx == false 的写法。
比如:*/
// 不建议if (a == true) {}
// 建议if (a) {}// 更好if (!!a) {}
复制代码

d.一个为 String Number,另一个为 Object,则把 Object 用 ToPrimitive()转换成基本类型,再比较

e.返回 false


深浅拷贝

为什么会出现深浅拷贝?

因为有些数组嵌套了数组或对象,于是该数组的深度不是 1,拷贝的时候如果只是在第一层拷贝,那么就会出现一个属性引用一个对象地址,而不是真正拷贝,所以当第二层对象的值改变,引用同一对象的属性也会改变。


数组的浅拷贝

如果是数组,我们可以利用数组的一些方法比如:slice、concat 返回一个新数组的特性来实现拷贝。

比如:

var arr = ['old', 1, true, null, undefined];var new_arr = arr.concat();new_arr[0] = 'new';console.log(arr) // ["old", 1, true, null, undefined]console.log(new_arr) // ["new", 1, true, null, undefined]
复制代码

用 slice 可以这样做:


var new_arr = arr.slice();
复制代码

但是如果数组嵌套了对象或者数组的话,比如:

var arr = [{old: 'old'}, ['old']];
var new_arr = arr.concat();
arr[0].old = 'new';arr[1][0] = 'new';
console.log(arr) // [{old: 'new'}, ['new']]console.log(new_arr) // [{old: 'new'}, ['new']]
复制代码

我们会发现,无论是新数组还是旧数组都发生了变化,也就是说使用 concat 方法,克隆的并不彻底。

如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。

我们把这种复制引用的拷贝方法称之为浅拷贝,与之对应的就是深拷贝,深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。

所以我们可以看出使用 concat 和 slice 是一种浅拷贝。

数组的深拷贝

那如何深拷贝一个数组呢?这里介绍一个技巧,不仅适用于数组还适用于对象!那就是:

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]var new_arr = JSON.parse( JSON.stringify(arr) );console.log(new_arr);
复制代码

是一个简单粗暴的好方法,就是有一个问题,不能拷贝函数,我们做个试验:

var arr = [function(){    console.log(a)}, {    b: function(){        console.log(b)    }}]var new_arr = JSON.parse(JSON.stringify(arr));console.log(new_arr);// [null, {…}]//0: null//1: {}//length: 2//__proto__: Array(0)
复制代码

浅拷贝的实现

var shallowCopy = function(obj){    if(typeof obj !== 'object') return;  var newObj = obj instanceof Array ? [] : {};  for(var key in obj){     if (obj.hasOwnProperty(key)){       newObj[key] = obj[key];     }  }}
复制代码

深拷贝实现

想要深拷贝,就要避免只在一层上面赋值,所以递归到最里面的一层值

var deepCopy = function(obj) {    if (typeof obj !== 'object') return;    var newObj = obj instanceof Array ? [] : {};    for (var key in obj) {        if (obj.hasOwnProperty(key)) {            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];        }    }    return newObj;}
复制代码

因为使用了递归,所以性能不如浅拷贝,还需在开发时考虑实际需求。

基于深拷贝思想实现合并两个或者更多的对象的内容到第一个对象中(extend)

var class2type = {};var toString = class2type.toString;var hasOwn = class2type.hasOwnProperty;
//isPlainObject()用来判断target是否是plainObjectfunction isPlainObject(obj) { var proto, Ctor; if (!obj || toString.call(obj) !== "[object Object]") { return false; } proto = Object.getPrototypeOf(obj); if (!proto) { return true; } Ctor = hasOwn.call(proto, "constructor") && proto.constructor; return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);}

function extend() { // 默认不进行深拷贝 var deep = false; var name, options, src, copy, clone, copyIsArray; var length = arguments.length; // 记录要复制的对象的下标 var i = 1; // 第一个参数不传布尔值的情况下,target 默认是第一个参数 var target = arguments[0] || {}; // 如果第一个参数是布尔值,第二个参数是 target if (typeof target == 'boolean') { deep = target; target = arguments[i] || {}; i++; } // 如果target不是对象,我们是无法进行复制的,所以设为 {} if (typeof target !== "object" && !isFunction(target)) { target = {}; }
// 循环遍历要复制的对象们 for (; i < length; i++) { // 获取当前对象 options = arguments[i]; // 要求不能为空 避免 extend(a,,b) 这种情况 if (options != null) { for (name in options) { // 目标属性值 src = target[name]; // 要复制的对象的属性值 copy = options[name];
// 解决循环引用 if (target === copy) { continue; }
// 要递归的对象必须是 plainObject 或者数组 if (deep && copy && (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { // 要复制的对象属性值类型需要与目标属性值相同 if (copyIsArray) { copyIsArray = false; clone = src && Array.isArray(src) ? src : [];
} else { clone = src && isPlainObject(src) ? src : {}; }
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) { target[name] = copy; } } } }
return target;};
复制代码


文章参考:[冴羽的博客](https://github.com/mqyqingfeng/Blog)

用户头像

zjlulsum

关注

还未添加个人签名 2018.09.17 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
文章内容很丰富,建议修改下标题,多些描述,阅读量会更高哦~
2020 年 05 月 11 日 18:27
回复
没有更多了
JavaScript 学习笔记——数据类型