Object.assign({}, conf)
只能拷贝一级,深层的源改变,目标也会跟着改变。
function deepCopy(des, src) {
for (var key in src) {
if(typeof src[key] !== 'object') {
des[key] = src[key];
} else {
des[key] = des[key] || {};
deepCopy(des[key], src[key]);
}
}
return des;
}
首先了解下Object.create的实现方式
Object.create = function (o) {
var F = function () {};
F.prototype = o;
return new F();
};
new操作符会做一下几个事情:
其中被new的函数就叫构造函数。
注意:
function C() {
this.z = 3;
this.func = function() {console.log(1)}
}
var c1 = new C();
var c2 = new C();
c1.func === c2.func // false
执行c1.func === c2.func
会返回false,这是因为每次实例化的时候都会创造一个新的函数对象,实际上没有必要这么做,首先能想到的是,把要创建的函数放到构造函数外面:
function Person(name, age, job){this.sayName = sayName}
function sayName() {console.log(1)}
var p1 = new Person(1,2,3);
var p2 = new Person(1,2,3);
p1.sayName === p2.sayName // true
但是放在全局中就是去了封装的意义,所以引入了原型的模式。
每个函数都有一个prototype(原型)的属性,这个属性指向一个对象,可以存储有特定类型的所有实例共享的属性和方法。
注:箭头函数不包含这个属性。。。
此时,下式依旧成立
function Person() {}
Person.prototype.name = 'aaa';
Person.prototype.sayName = ()=>{console.log(2)};
var p1 = new Person(1,2,3);
var p2 = new Person(1,2,3);
p1.sayName === p2.sayName // true
Person.prototype就是Person的原型对象,默认情况下,每个函数都存在prototype属性,这个属性存的对象里都默认有一个constructor属性指向该函数(当然还有一个_ proto_继承自Object)。
Person.prototype.constructor 指向 Person。
// _ proto_:显示在具体实例上的一个属性
// prototype:构造函数上的一个属性
A.prototype.isPrototypeOf(B):A是不是B的原型对象
B instanceof A:B是不是A的实例
判断某个对象的[[Prototype]](实例,拥有[[Prototype]]属性的实例对象,通常浏览器的实现是_ proto_)是否指向调用isPrototypeOf()这个方法的对象(原型对象Person.prototype)。
读取某个对象的属性时,会按照实例对象、_ proto_ 对象、 _ proto_ 的 _ proto_等等顺序依次查找,比如上面person的例子,可以找到p1和p2的sayName和name属性。
但是如果直接给p1.name赋值,则无法改变 _ proto_对象中的name值,会在对象实例本身添加这个属性,以后读取p1.name值时,因先从实例找起,所以不会再读到原型上的值。
Person
name: "bbb"
__proto__:{name:"aaa", sayName: ......}
但是通过修改_ proto_ 的name属性,就会同时改变p1和p2的原型的name值 以及 Person的原型对象的name值,因为Person.prototype === p1. _ proto_ ,Person.prototype === p2. _ proto_ 。
使用delete操作符删掉p1.name,则又能访问到原型上的name属性,注意 delete p1.name只能删掉实例上的属性,可以通过delete p1.proto.name删掉原型上的属性,同样的操作原型的话,上述三个都会改变。
可以通过这个方法来判断某属性是来自实例还是来自原型。
p1.hasOwnProperty('name') // false
可以判断是否对象实例或原型中有该属性。可以访问到不可枚举的属性,如constructor和_ proto_、prototype。也可以通过in和hasOwnProperty来判断属性是否在原型中。
只能访问到可枚举的实例和原型属性,constructor和_ proto_、prototype等不可枚举的访问不到。
对象上所有可枚举的实例属性。返回的是字符串数组 。
对象上所有实例属性。返回的是字符串数组 。
一般的,想之前提到的一样,每次修改原型对象是,能立即在所有对象实例中反应出来,因为他们之间的连接是一个指针,而给一个副本。
但是,重写原型对象的话,把原型修改为另一个对象,就切断构造函数与最初原型之间的联系。
重写之前的实例中的[[Prototype]]指针仍指向最初的原型。
在重写原型对象之后创建的实例[[Prototype]]指针指向重写的原型。z
// 重写原型对象
function Person() {}
var friend = new Person();
Person.prototype = {
constructor: Person, // 如果不加这个的话,重写这里会没有constructor,prototype的prototype里会有Object的constructor。但是这种写法也有一个问题,constructor会变成可枚举的,所以也可以向下面那种方式写。
name: 'aaa'
}
// Object.defineProperty(Person.prototype, 'constructor', {
// enumerable: false,
// value: Person
// });
friend.sayName(); // error
原生对象比如Object,Array,String等,比如Array.prototype中有sort()方法。
根据动态性,可以给原生对象的原型上添加方法,比如Array,这样当前环境的所有数组都可以调用到新添加的方法。
PS: 不推荐在产品化的程序中修改原生对象的原型,这样可能会导致明明冲突,而且,也可能会意外的重写原生方法。
如果往数组上添加新方法,因为默认可枚举,所以for-in操作数组时,会把往数组上添加的新特性一起for-in出来。
解决这个问题,可以使用Object.defineProperty来给原型对象添加方法。使用此方法时,默认不可枚举。
可以让所有对象实例共享它所包含的属性和方法(直接定义在构造函数中的函数,每个实例都会创造一个新的函数)
function Person() {}
Person.prototype = {
constructor: Person,
name: 'aaa',
friends: [1, 2, 3]
}
var friend1 = new Person();
var friend2 = new Person();
friend1.friends.push(4);
console.log(friend1.friends) // [1, 2, 3, 4]
console.log(friend2.friends) // [1, 2, 3, 4]
在构造函数中定义各自特有的属性,在prototype中写共享的方法。
通常都是用这种方式创建自定义类型。
在构造函数中初始化原型(仅在必要的情况下)
可以通过检查某个应该存在的方法是否有效,来决定是否要初始化原型。
function Person(name) {
this.name = name;
// 方法
if(typeof this.name !== "function") {
Person.prototype.sayName = function(){console.log(1)}
}
}
但是只要有一次创建实例时,typeof this.name !== “function”,那么所有实例(之前创建和之后)都会有sayName这个方法。所以不能使用对象字面量来重写原型,这样就会切断之前创建的实例与新原型之间的联系。
封装创建对象的代码,再返回新创建的对象。
构造函数在不返回值的情况下,默认会返回新对象的实例(new操作符做的事情),寄生模式相当于手动做new操作符的一些事情,可以重写new调用构造函数时返回的值。
function Person(name) {
var o = new Object();
o.name = name;
return o;
}
这种方式有个问题,因为相当于改变了new的默认行为,所以不存在默认的原型,原型就是Object,所以不能使用instanceOf来确定对象类型,所以一般情况下不要使用这种模式。
继承包括接口继承和实现继承。
实现继承通过原型链实现,利用原型让一个引用类型继承另一个引用类型的属性和方法。
…prototype = new Object()的方式才能继承,让一个原型对象等于另一个类型的实例。
直接…prototype = …prototype只是一直在修改原型,而不是链式继承。
这种方式有个弊端,在给对象原型赋值的时候,实例化了另一个类型(eg:A.prototype = new B(); ),即调用了类型B的构造函数,通常我们希望在实例化A的时候,再调用B的构造函数,如果B的构造函数有一些方法,或者需要传参的方法,这种传undefined参数的实例化可能会引发一些问题,可以用Object.create()来解决:
A.prototype = Object.create(B.prototype);
前文中提到过,Object.create是创建了一个构造函数为空新对象,赋原型在实例化,所以可以避免可能的构造函数异常执行。
super([arguments])
,比如react中的super(props),还有其他的解决办法,见下面的借用构造函数)
。使用apply()、call()等方法在新创建的对象上执行继承对象的构造函数。还可以通过此种方式绑定当前this并传参,或者用super。
使用Object.defineProperty()定义属性时,可以设置get、set、enumerable。