面向对象编程/原型及原型链
参数按值传递
参数分为基本类型与引用类型
- 基本类型值存储于栈内存中,传递的就是当前值,修改不会影响原有变量的值;
- 引用类型值其实也存于栈内存中(存的是实际内容的地址),它的值是指向堆内存;当改变时,改变了堆内存当中的实际值;
栈存固定大小的值
手写call 和 apply
改变函数的this指向
call() :在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
let foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
等价于
//等价于
let foo = {
value: 1,
bar: function() {
console.log(this.value)
}
};
foo.bar(); // 1
1. call 改变了 this 的指向,指向到 foo;
2. bar 函数执行了;
实现思路:
1、对象里添加一个属性函数
2、调用函数
3、删除函数
var obj = {
value: 1
}
function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
Function.prototype.myCall=function(context){
var context=context || window;
let arg= [...arguments].slice(1) //splice会改变原数组,返回被移除的元素组成的数组。slice不会改变原数组
// arguments.slice(1)
context.fn=this
let resObj=context.fn(...arg)
delete context.fn
return resObj
}
console.log(bar.call(obj, 'kevin', 18));
//标准写法,防止原型上有fn这个属性,被覆盖。用Symbol创建唯一的属性
Function.prototype.call2 = function(context, ...args) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
let fnSymbol = Symbol()
context[fnSymbol] = this
let fn = context[fnSymbol](...args)
delete context[fnSymbol]
return fn
}
apply 的实现跟 call 类似,只是入参不一样,apply为数组
var obj = {
value: 1
}
function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
Function.prototype.myApply=function(context,arg){
var context=context || window;
// arguments.slice(1)
context.fn=this
let resObj
if(arg&&arg instanceof Array &&arg.length){
resObj=context.fn(...arg)
}else{
resObj=context.fn()
}
delete context.fn
return resObj
}
// console.log(bar.apply(obj,'kevin'));
console.log(bar.myApply(obj,'kevin'));
//标准简化写法
Function.prototype.apply2 = function(context, args) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
let fnSymbol = Symbol()
context[fnSymbol] = this
let fn ;
if(args&&args instanceof Array &&args.length){
fn = context[fnSymbol](...args)
}else{
fn = context[fnSymbol]()
}
delete context[fnSymbol]
return fn
}
手写 bind
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。
call,apply改变了函数的this指向,且直接执行了函数。bind只改变this指向,不执行函数,返回一个函数
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
// 返回了一个函数
var bindFoo = bar.bind(foo);
bindFoo(); // 1
思路:
1、返回的是一个函数
2、函数改变this指向
var foo = {
value: 1
};
function bar(name,age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
// return{
// name:name,
// age:age,
// value:this.value
// }
}
bar.prototype.friend = 'kevin';
// 返回了一个函数
var bindFoo = bar.bind(foo,'张三');
let res=new bindFoo(18); // 1
console.log(res);
// 1返回函数
// 2改变函数指向
Function.prototype.mybind=function(context,...args){
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var that =this;
var fBound=function(...args2){
let res;
if( this instanceof fBound){
res =that.apply(this,[...args,...args2])
}else{
res =that.apply(context,[...args,...args2])
}
return res
}
// 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
// fBound.prototype = this.prototype; 这么赋值,修改fBound.prototype时,也会影响bar的prototype
function fnp(){
}
fnp.prototype=this.prototype
fBound.prototype =new fnp()
//fBound.prototype.name23='李四' //测试用
return fBound
}
// var bindFoo = bar.mybind(foo,'张三');
// bindFoo(18); // 1
var bindFoo2 = bar.mybind(foo,'张三');
let res2=new bindFoo2(18); // 1
console.log(res2);
最终手写代码
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
手写模拟 new
思路:
var person=new Person()//person 实例,Person 构造函数,Person.prototype 原型
1、创建一个对象,对象拥有Person的内部属性
2、对象的__proto__等于Person的prototype,让对象可以访问Person的原型对象
因为 new 是关键字,所以无法像 bind 函数一样直接覆盖
所以我们写一个函数
命名为 objectFactory,来模拟 new 的效果
function Person () {
……
}
// 使用 new
var person = new Person(……);
// 使用 objectFactory
var person = objectFactory(Person, ……)
function Person (name,age) {
this.name=name
this.age=age
}
Person.prototype.habit='sing'
var name='李四'
var age=18
// 使用 new
var person1 = new Person(name,age);
console.log('person1',person1)
function objectFactory(fn,...args){
var obj=new Object() //创建对象
fn.call(obj,...args) //改变构造函数this指向,指向对象,使对象拥有Person的内部属性,实现思路1
obj.__proto__=fn.prototype //继承原型上的属性,实现思路2
return obj
}
// 使用 objectFactory
var person2 = objectFactory(Person,name,age)
console.log('person2',person2)
假设构造函数有返回值
function Person (name, age) {
this.strength = 60;
this.age = age;
return {
name: name,
habit: 'Games'
}
}
Person.prototype.habit22='sing'
var person = new Person('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined
console.log(person.habit22) // undefined
//或者返回的不是一个对象
function Person (name, age) {
this.strength = 60;
this.age = age;
return 'handsome boy';
}
var person = new Otaku('Kevin', '18');
console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18
可以看出,当构造函数有返回值的时候,分为是对象和不是对象
当返回值是对象时,this指针失效,包括Person.prototype上的值,当返回值不是对象的时候,跟原来保持一致
最终版本
function Person (name,age) {
this.strength = 60;
this.age = age;
return {
name: name,
habit: 'Games'
}
}
Person.prototype.habit2='sing'
var name='李四'
var age=18
// 使用 new
var person1 = new Person(name,age);
console.log('person1',person1)
function objectFactory(fn,...args){
var obj=new Object() //创建对象
// fn.call(obj,...args) //改变构造函数this指向,指向对象,使对象拥有Person的内部属性,实现思路1
let res=fn.call(obj,...args)//接收返回值
// 判断res是不是对象,是对象,返回对象,不是对象返回原来的
obj.__proto__=fn.prototype //继承原型上的属性,实现思路2
return typeof res === 'object' ? res :obj //没有返回值的时候,res是undefined
}
// 使用 objectFactory
var person2 = objectFactory(Person,name,age)
console.log('person2',person2)
类数组对象和 arguments
所谓的类数组对象:
拥有一个 length 属性和若干索引属性的对象
总结:
- 类数组可以按数组方式正常读写,获取长度,循环
- 但是不能调用数组方法push,join,map等
- 类数组能通过 Function.call间接调用数组的方法
- 类数组可以转成数组
var array = ['name', 'age', 'sex'];
var arrayLike = {
0: 'name',
1: 'age',
2: 'sex',
length: 3
}
//读写
console.log(array[0]); // name
console.log(arrayLike[0]); // name
array[0] = 'new name';
arrayLike[0] = 'new name';
//获取长度
console.log(array.length); // 3
console.log(arrayLike.length); // 3
//遍历
for(var i = 0, len = array.length; i < len; i++) {
}
for(var i = 0, len = arrayLike.length; i < len; i++) {
}
//通过Function.call 间接调用
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
Array.prototype.join.call(arrayLike, '&'); // name&age&sex
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] // slice可以做到类数组转数组
Array.prototype.map.call(arrayLike, function(item){
return item.toUpperCase();
});
// ["NAME", "AGE", "SEX"]
// 类数组转数组
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"]
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"]
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"]
// 4. apply
Array.prototype.concat.apply([], arrayLike)
在函数体中,arguments 指代该函数的 Arguments 对象。Arguments是一个类数组,指代函数的所有实参
创建对象的多种方式&优缺点
- 对象字面量(常用赋值)
- 使用内置构造函数(基本不使用,用new object创建)
- 工厂模式(不推荐使用)
- 构造函数模式(每次创建调用一次方法)
- 原型模式 (属性和方法都共享,不能初始化参数)
- 组合模式 (最广泛的使用,该共享共享(通常是方法,不需要每次创建调用),该私有私有(通常是属性,不用被共享))
- 寄生构造函数模式 、稳妥构造函数模式(了解概念即可,寄生构造函数模式比工厂模式多个外面的new,稳妥构造函数模式比工厂模式少个里面的new)
对象字面量
var obj={
name:"赵六",
age:18,
}
优点:简单;
缺点:只能创建一次对象,复用性较差,如果要创建多个对象,代码冗余度太高
使用内置构造函数
var obj=new Object();
obj.name="赵六";
obj.age=18;
优点:简单;
缺点:只能创建一次对象,复用性较差,如果要创建多个对象,代码冗余度太高
工厂模式
function createPerson(name) {
var o = new Object();
o.name = name;
o.getName = function () {
console.log(this.name);
};
return o;
}
var person1 = createPerson('kevin');
优点:简单;
缺点:对象无法识别,因为所有的实例都指向一个原型Object,没有person类的概念
个人理解:new出来的实例(除了构造函数返回新对象的new,不具有构造函数的类型,后续寄生构造函数模式会具体介绍),原型链上有Person和Object ,工厂模式只有Object ,所以构造函数可以分类,工厂不行
构造函数模式
function Person(name) {
this.name = name;
this.getName = function () {
console.log(this.name);
};
}
var person1 = new Person('kevin');
//person1 instanceof Person true
优点:实例可以识别为一个特定的类型;对比工厂模式
缺点:每次创建实例时,每个方法都要被创建一次;
构造函数模式优化:
function Person(name) {
this.name = name;
this.getName = getName;
}
function getName() {
console.log(this.name);
}
var person1 = new Person('kevin');
//
解决了每个方法都要重新创建的问题,不过每个实例上依然都挂着方法getName
原型模式
function Person(name) {
}
Person.prototype.name = 'xianzao';
Person.prototype.getName = function () {
console.log(this.name);
};
var person1 = new Person();
优点:方法不会重新创建;(同样有特定的类型)
缺点:
- 所有的属性和方法都共享;
- 不能初始化参数;
组合模式
为了兼容原型模式属性和方法都共享,构造函数属性和方法都私有(即方法都创建一遍,每个实例上都有属性和方法),最好的办法是:属性在实例上,每次创建实例都有自己的属性值,方法在原型上(构造函数.prototype上),这就是组合模式
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
var person1 = new Person();
优点:该共享的共享,该私有的私有,使用最广泛的方式;
寄生构造函数模式 、稳妥构造函数模式
寄生构造函数模式
function SpecialArray() {
var values = new Array();
for (var i = 0, len = arguments.length; i < len; i++) {
values.push(arguments[i]);
}
values.toPipedString = function () {
return this.join("|");
};
return values;
}
var colors = new SpecialArray('red', 'blue', 'green');//寄生构造函数模式
var colors2 = SpecialArray('red2', 'blue2', 'green2'); //工厂模式
console.log(colors instanceof SpecialArray) // false
console.log(colors);
console.log(colors.toPipedString()); // red|blue|green
console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2
寄生构造函数模式对比工厂模式,创建对象实例时,寄生构造函数模式采用new操作符。不过这对于创建出的对象来说,没有任何差别。
对于两者的差别,js高级程序作者在书中是这么说的:
“除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。“
“它们又是用在什么样的情境下呢?”至于这个问题。书里也给出了明确的回答:“我们建议在可以使用其他模式的情况下,不要使用这种模式。”
根据作者的意思,构造函数和普通函数的区别在于:当使用new+构造函数创建对象时,如果构造函数内部没有return语句,那么默认情况下构造函数将返回一个该类型的实例,但如果构造函数内部通过return语句返回了一个其它类型的对象实例,那么这种默认的设置将被打破,构造函数最终返回的实例类型将以return语句中对象实例的类型为准。因为寄生构造函数模式的构造函数返回了一个对象,因此可以说工厂模式和寄生构造函数模式在功能上是等价的
如果非要说两者的不同,new Person()(寄生构造函数模式)更能感觉是正在创建一个对象,而不是在调用一个函数(工厂模式)。
稳妥构造函数模式
function Person(para_name, para_age, para_job) {
//创建要返回的对象
var o = {};
//在这里定义私有属性和方法
var name = para_name;
var age = para_age;
var job = para_job;
var sayAge = function() {
alert(age);
};
//在这里定义公共方法
o.sayName = function() {
alert(name);
};
//返回对象
return o;
}
var person1 = Person("Nicholas", 29, "Software Engineer"); //创建对象实例
person1.sayName(); //Nicholas
person1.name; //undefined
person1.sayAge(); // 报错
稳妥构造函数模式对比工厂模式,在函数内部不使用new来创建一个对象,使用对象字面量赋值,这种设计模式最适合在一些安全的环境中使用(这些环境中会禁止使用this和new)
继承的多种方式&优缺点
- 原型链继承
- 借用构造函数
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合继承
- ES6 Class继承(这里先不讲,以后es6一起总结)
关系间的分类关系:
前提:根据创建对象时候的优缺点,可知,我们提倡:将属性封装在构造函数中,将方法定义在原型对象上。
function A(name){
this.name = name; //实例基本属性(该属性,强调私有,不共享)
this.arr = [1]; //实例引用属性(该属性,强调私用,不共享)
this.say = function(){ //实例引用属性(该属性,强调复用,需要共享)
console.log('hello');
}
}
//数组和方法都属于’实例引用属性’,但是数组强调私有不共享,方法需要复用共享。
修正constructor指向的意义:任何一个prototype对象都有一个constructor属性,指向它的构造函数(它本身),更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
若是不将子类的 constructor指回到子类, 此时的子类实例的.constructor则指向父类,因为有的类型判断可以用constructor(实例.constructor==子类,不指回,会显示false) 来判断创建者的,根据这个去走不同的逻辑。instanceof检查的是原型链
原型链继承
function Person(name){
this.name=name
this.arr = [1];
}
Person.prototype.say=function(){
console.log(this.name,'这是Person的say')
return this.name
}
Student.prototype=new Person('张三')
function Student(age){
this.age=age
}
Student.prototype.eat=function(){
console.log(this.name,this.age,'这是Student的eat')
}
const s1=new Student(18)
const s2=new Student(20)
s1.__proto__.name='李四'
s1.arr.push(2);
console.log(s1.arr,s2.arr); //[1,2],[1,2]
console.log(s1.name,s2.name); //李四 李四
console.log('s1',s1)
console.log('s2',s2)
原型链继承思路:让子类的原型等于父类的实例,如下图,原型链的关键在于子类的原型等于父类的实例Student.prototype=new Person(),形成原型链。
缺点:
1、引用类型的属性被所有实例共享(属性还在父类上,子类只是在自己身上没有的时候向上访问),代码中s1的name添加了Lisi,打印的s2中name也添加了Lisi,实例基本属性通过__proto__修改也会被改变(改变了原型上的值),直接修改不会 s1.name=‘李四’(相当于实例s1上新建一个name属性)
2、无法传参,new Student不能传参赋值name
3、继承单一,无法实现多继承
借用构造函数
function PersonGo(name){
console.log('PersonGo')
this.name=name //实例基本属性(该属性,强调私有,不共享)
this.arr = [1]; //(该属性,强调私有)
this.getNmae = function(){ //方法都在构造函数中定义,每次创建实例都会创建一遍方法。缺点1
console.log('hello);
}
}
PersonGo.prototype.say=function(){
console.log(this.name,'这是PersonGo的say')
}
function StudentGo(age,name){
PersonGo.call(this,name)//构造函数继承的核心,拷贝了父类构造函数里的实例属性和方法
this.age=age
}
StudentGo.prototype.eat=function(){
console.log(this.name,this.age,'这是StudentGo的eat')
}
const s3=new StudentGo(18,'张三')//优点1:可向父类构造函数传参
const s4=new StudentGo(20,'李四')
//优点2:不共享父类构造函数的引用属性
s3.arr.push(2);
console.log(s3.arr,s4.arr); //[1,2],[1]
console.log('s3',s3)
console.log(s3.say)//undefined,获取不到父类原型上的方法,缺点2
console.log('s4',s4)
思路:借用父类构造函数来增强子类实例,等于是复制父类的实例属性给子类(属于子类自己的了,没有PersonGo这个类概念,只单纯调用了PersonGo()函数)
优点:
1、解决了共享变量问题
2、可以传参
3、可实现多继承(通过多个call或apply继承多个父类)
缺点:
1、方法都在构造函数中定义,每次创建实例都会创建一遍方法。
2、只继承了父类的构造函数(因为只执行了构造函数,原型链继承里的new会把原型上东西也继承(不懂的可以看上面的new模拟实现)),导致父类原型(PersonGo.prototype)上定义的方法和属性不能继承
组合继承
思路:保留原型链继承和构造函数的优点,解决原型链继承和构造函数的缺点
原型链的优点:可以访问原型上的对象和方法。构造函数的优点:可以传参,属性私有(引用类型也私有)
function PersonZu(name){
console.log('PersonZu')
this.name=name
this.arr = [1];
}
PersonZu.prototype.say=function(){
console.log(this.name,'这是PersonZu的say')
}
function StudentZu(age,name){
PersonZu.call(this,name)//核心,构造函数继承,拷贝一份到实例上,每次new StudentZu都会调用一次
this.age=age
}
StudentZu.prototype =new PersonZu() //核心,原型链继承,继承原型上属性方法 ,属性方法还在原型上 只会调用一次,这里因为没传name值,所以下面缺点讲述里原型上的那个name是undefined
// 若是不将StudentZu constructor指回到StudentZu, 此时的StudentZu实例zs5.constructor则指向PersonZu,
//因为有的判断是使用constructor来判断创建者的,根据这个去走不同的逻辑,instanceof检查的是原型链
StudentZu.prototype.constructor = StudentZu;//修正constructor指向
StudentZu.prototype.eat=function(){
console.log(this.name,this.age,'这是StudentZu的eat')
}
const s5=new StudentZu(18,'张三')
const s6=new StudentZu(20,'李四')
s5.arr.push(2)
console.log('s5',s5)
// console.log(s5.say,s5.eat)
console.log('s6',s6)
// console.log(s6.say,s6.eat)
console.log(s5 instanceof StudentZu) //true
console.log(s5 instanceof PersonZu)//true
console.log(s5.constructor==PersonZu) //false
console.log(s5.constructor==StudentZu) //true
优点:
1、原型对象上方法复用,即继承了原型链
2、属性实例化私有,不共享父类属性,用构造函数拷贝了一份属性在实例上
缺点:
1、调用了2次父类构造,一次用构造函数拷贝了一份属性在实例上,一次new的时候原型上又有一份,不影响使用,每次寻找是寻找最近的属性,但是造成了属性多余,删除实例属性的时候,会有问题,原型上还有。如上面的s5属性里有name,arr。PersonZu上也有一份
组合继承优化:
为了优化调用了2次父类构造,我们需要替换 StudentZu.prototype =new PersonZu() 的继承,但是同时也要PersonZu原型(也就是PersonZu.prototype)上的属性和方法。所以优化方案:将StudentZu.prototype =new PersonZu() 替换成StudentZu.prototype=PersonZu.prototype,其他不变。
缺点:修正构造函数的指向之后,父类实例的构造函数指向,同时也发生变化(PersonZu的构造函数也指向了StudentZu),因为赋值是浅拷贝,StudentZu.prototype复制的是PersonZu.prototype的地址,改变StudentZu.prototype.constructor也同步修改了PersonZu.prototype.constructor
寄生组合继承
完美的继承方案
组合继承优化里 将StudentZu.prototype =new PersonZu()替换成了 StudentZu.prototype=PersonZu.prototype,依然存在问题
寄生组合将StudentZu.prototype =new PersonZu()替换成StudentZu.prototype = Object.create(PersonZu.prototype)
StudentZu.prototype = Object.create(PersonZu.prototype);
StudentZu.prototype.constructor = StudentZu;
//其中,Object.create()函数等价为:
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
//所以也可以等价于
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
StudentZu.prototype = object(PersonZu.prototype);
StudentZu.prototype.constructor = StudentZu;
//原理是
function F(){} //创建一个函数
F.prototype=PersonZu.prototype,//这个函数继承了PersonZu.prototype,拥有了PersonZu原型的方法和属性,但是没有PersonZu构造函数的属性
let f1=new F()//创建一个F的实例
StudentZu.prototype=f1
// 让StudentZu.prototype等于这个实例,因为改变实例的值,不会影响到原型上,也影响不了PersonZu.prototype.constructor
function PersonZu(name){
// console.log('PersonZu')
this.name=name
this.arr = [1];
}
PersonZu.prototype.say=function(){
console.log(this.name,'这是PersonZu的say')
}
function StudentZu(age,name){
PersonZu.call(this,name)//核心,构造函数继承,拷贝一份到实例上
this.age=age
}
// StudentZu.prototype =new PersonZu() //核心,原型链继承,继承原型上属性方法 ,属性方法还在原型上
StudentZu.prototype = Object.create(PersonZu.prototype);
StudentZu.prototype.constructor = StudentZu;
const s5=new StudentZu(18,'张三')
const s6=new StudentZu(20,'李四')
const p1=new PersonZu('小爸爸');
console.log(s5.constructor); //修复之后:Child,Parent
console.log(p1.constructor); //修复之后:Child,Parent
原型继承
缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
function createObj(o) {
function Fn(){}
Fn.prototype = o;
return new Fn();
}
var person = {
name: 'kevin',
friends: ['daisy', 'kelly']
}
var person1 = createObj(person); //对比创建对象里的工厂模式,这里的原型继承,没有person概念,打印person1,会发现他属于Fn类,对属性的修改读写,还是对Fn的原型上去修改,除了没有类的概念,缺点上跟原型链一致,可以理解为跳过person,挂在了Object上
// console.log(person1 instanceof Fn)// Fn is not defined 但是由于Fn在createObj函数内部,所以他也用不了instanceof去判断是什么类,没有类的概念.
var person2 = createObj(person);
console.log(person1);
console.log(person2);
person1.name = 'person1';
console.log(person2.name); // kevin 没改变
person1.__proto__.age = 20;
console.log(person2.age); // kevin 跟着变了
person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"] 跟着变了
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
比原型继承多一个增强功能(可以添加属性和方法)
执行结果跟原型继承一样,只是多了个方法
这种继承只提供一个思路,没什么优点,缺点同原型继承
function createObj (o) {
var clone = Object.create(o);//
clone.sayName = function () {
console.log('hi');
}
return clone;
}
//其中,Object.create()函数等价为:
function object(o) {
function F(){}
F.prototype = o;
return new F();
}
//寄生继承
function createObj(o) {
function Fn(){}
Fn.prototype = o;
return new Fn();
}
function createAnother(original){
var clone=createObj(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: 'kevin',
age:18,
friends: ['daisy', 'kelly']
}
var person1 = createAnother(person);
// console.log(person1 instanceof Fn)// Fn is not defined
var person2 = createAnother(person);
console.log(person1);
console.log(person2);
person1.name = 'person1';
console.log(person2.name); // kevin 没改变
person1.__proto__.age = 20;
console.log(person2.age); // kevin 跟着变了
person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"] 跟着变了