最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 继承—— The good parts of js

    正文概述 掘金(MinyaChan)   2020-12-04   526

    Why 继承?

    1. 实现代码的复用(key point)
    2. 引入一套类型系统的规范

    What 继承

    继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

    类 & 实例

    举个例子,假如动物是一个类,那么人是动物的一个子类。在这句话中,动物是父类,人就是子类。人是一个类,但是具体到单个人,单个个体,我是人类的一个实例,你是人类的一个实例。我们相似却又不同,我们都是一类,但是我们都不一样。

    // 类 class
    Class Person({ some properties }){ ... }; 
    
    // 实例 instance
    var Me = new Person({ name: "", birth : "" ..... });
    

    值得一提的是,在 JavaScript 中,原本是没有类的概念的,只有 prototype 的概念,所以本文所提及的 类和实例 都是需要人为给他添加的语义理解。看起来好像大家都一样,实际上是不同的。 为了方便区分类和实例,会约定俗成地使用大写首字母作为 类名,方便开发过程中区分开来。 同时在本文中,为了更好区分原型继承和伪类继承,我们只使用核心 code 作为继承的方法,而不采用 new 操作符。 new 操作符只作为新建实例中使用。

    前置知识

    New 操作符

    // Function.method(name, func) => Function.prototype.name = func 
    
    Function.method('new', function(){
    	// 1. 创建一个新对象,继承自构造器函数的原型对象,隐式创建一个空对象
    	// 2. 将构造函数的作用域赋值,新对象被执行[[Prototype]]连接。
    	var that = Object.create(this.prototype) 
    
    	// 3. 调用执行构造器函数,并绑定 this 到这个新对象上面,this 指向新对象
    	var other = this.apply(that, arguments)
    
    	// 4. 如果构造函数的返回值不是一个对象,则返回该新对象 return that
    	return (typeof other === 'object' && other) || that
    
    })
    

    New 操作符的小应用 demo 面试题

    function fn() {
    	this.user = 'hello world'
    	return 1 // 由于 1 不是对象,所以返回的是构造函数里面的对象
    }
    var a = new fn()
    console.log(a.user) // 'hello world'
    
    // ---------
    
    function fn() {
    	this.user = 'hello world'
    	return {} // 直接放回了这个空对象
    }
    var a = new fn()
    console.log(a.user) // undefined
    

    How 继承

    原型式 Prototype

    摈弃传统却又在 JS 中有些许怪异的“类”的想法,基于原型的继承比起基于类的继承更加易于理解:一个对象可以继承旧的对象

    • 通过字面量去构造一个对象
    var Animal = {
    	name: 'animal',
    	arr: [1],
    	get_name: function () {
    		console.log('this is inner func ' + this.name)
    	},
    	getName: function () {
    		console.log(this.name + ' hello')
    		return this.name
    	},
    }
    
    • 通过 Object.create(Animal) 构造子类
    var Cat = Object.create(Animal)
    console.log(Object.getPrototypeOf(Cat) === Animal) // true 说明以及绑定了原型链
    Cat.says = function () {
    	console.log('cat ' + this.name)
    }
    
    // var mycat = Object.create(Cat) // 实例 也可以使用 new 构造实例
    // var mycat2 = Object.create(Cat) // 实例 也可以使用 new 构造实例
    var mycat1 = new Cat('cici')
    var mycat2 = new Cat('mimi')
    mycat.name = 'mimi'
    mycat2.getName()
    mycat.says()
    mycat2.arr.push('2')
    console.log(mycat.arr, mycat2.arr) // [1,2] [1,2] 说明了原型继承无法隔离父对象的属性
    

    Advanced

    1. 利用 object.assign 实现混合继承。原理是 object.assign( target, source1, source2 )source1 上可枚举的属性值赋值给 target 。但是注意,当有同名时,最后一个出现的属性将会覆盖。通过这个方式还可以实现混合继承,但是没必要。

    小结

    原型链继承的方法可以专注于对象本身,利用的是一个新对象可以继承旧对象的属性。通过在对象字面量构造属性,在原型链上绑定方法,实现一个通用的模板。子对象通过 object.create 的方式继承父对象的属性及方法,同时可以在子对象中新增父对象没有属性或者方法实现差异化继承。但是,在对象上,由于大家共享了父对象的原型链,所以实例与实例之间会相互干扰。

    但是,当实例 1 修改了原型链上的属性是,修改的内容也会影响到实例 2 ,实例之间没有隔离属性,相互影响。这是因为:1 、当子对象拥有和父对象同名的属性或者方法,会覆盖父对象的属性或者方法,如果不存在时,就会沿着原型链逐层往上进行寻找直至找到该属性或者方法。2、包含着引用类型值的原型,修改时也会影响其他的引用该地址的值对象。

    此外,可以看到,子类无法向父类传递参数,或者说,无法在不影响其他实例的情况向父类传递参数。这也是在实际情况中很少直接使用原型链方式的继承的原因。

    伪类继承(构造器)

    构造函数

    • 如果函数调用前面加上了 new 关键字,后面的函数的调用模式就会变成了构造器的调用模式
    • 这个模式调用将会隐式创建一个连接到该函数的 prototype 的新对象,同时 this 也会被绑定到这个新对象上面**(使用 call 或者 apply 是伪类继承的核心)**
    • 如果这个函数 return 的内容不是一个对象,那么就会隐式返回一个 this 的值(该新对象)
    function Animal(type) {
    	this.type = type || 'animal'
    	this.animal = '123'
    	this.arr = [1]
    	this.get_name = function () 
    		console.log('this is inner func ' + this.name)
    	}
        
    	this.get_type = function () {
    		console.log(this.type)
    	}
    }
    
    Animal.prototype.getName_proto = function () {
    	console.log(this.name + ' hello')
    	return this.name
    }
    
    function Cat(name) {
    	// 核心是 call、apply 绑定 this
    	Animal.call(this,'cat') // 为防止覆盖,应该提于子类函数体顶部
    	this.name = name || 'cat'
    	// 会覆盖
    	this.get_name = function () {
    		console.log("this is cat's func " + this.name)
    	}
    }
    
    var mycat1 = new Cat('cici')
    var mycat2 = new Cat('mimi')
    mycat1.get_name() // this is cat's func cici 
    mycat1.get_type()
    
    // mycat1.getName_proto() // animal prototype func 无法访问父类原型链上的方法
    
    mycat2.arr.push('2')
    console.log(mycat1.arr, mycat2.arr) // [1] , [1, '2']
    
    
    • little question: Cat 上面的原型方法,mycat 可以使用么?

      关键在 new 操作符上,前文提到, new 操作符会链接原型链+执行构造函数,所以 mycat 是可以访问 Cat 类上的原型方法。

    function Cat(name) {
    	Animal.call(this, 'cat')
    	// your code here
    	...
    }
    Cat.prototype.getName_proto = function () {
    	console.log('prototype')
    }
    var mycat1 = new Cat('cici')
    mycat1.getName_proto() // 'prototype' 可以访问
    
    • 构造器需要接收一大串参数的时候,将函数参数列表改为对象会更加友好
    // var myObj = maker(a,b,c) 
    var myObj = maker({ first:a, second:b, third:c })
    

    小结

    实际上伪类继承(构造器)是通过以 call 或者 apply 为核心的代码片段的复用,再通过 new 方法构造实例。通过伪类继承的方法,可以实现 1 属性之间的隔离,2. 父类可以提供一个接收参数,供给未来的子类传入,实现子类构造函数中向父类构造函数传递参数,通过 ParentClass.call(childClass, args.. ) 进行参数的传递。为了防止父类覆盖子类方法,调用父类构造函数应该提前至子类函数体顶部。

    但是由于父类子类之间,只执行了构造函数,但是没有连接原型链,所以子类没有办法使用父类原型链上的方法。

    函数化 (寄生式继承)

    迄今为止的继承模式都无法有私有的属性值或者方法,对象的所有属性都是公开可见的,所以我们可以通过函数化继承的方式实现私有化。函数内部可以定义一些私有的函数,只要不绑定到向外返回的对象上时,外部将无法访问到内部的属性或者方法,从而实现私有化。

    实际例子:

    var Animal = function (spec) {
    	var that = {} // 实际抛出的对象
    
    	var _name = 'private'
    	var _age = 0
    	_private_func = function () {
    		console.log('this is private function')
    	}
    
    	// 只有绑定到 that 上面的才会公开到外面的实例进行调用
    	that.get_name = function () {
    		console.log(spec.name)
    		return spec.name
    	}
    	that.says = function () {
    		console.log(spec.saying || "can't say")
    		return spec.saying || "can't say"
    	}
    	that.getPrivateName = function () {
    		return _name
    	}
    	that.addAge = function (year = 1) {
    		_age += year
    		return _age
    	}
    	that.getAge = function () {
    		return _age
    	}
    	return that
    }
    Animal._privateFunc = function () {
    	console.log('private call')
    }
    
    var cat = Animal({ name: 'cat', saying: 'meow' })
    // cat.says()
    
    var cat = function (spec) {
    	spec.saying = 'meow'
    	var that = Animal(spec)
    	that.say_my_name = function () {
    		console.log(that.says() + spec.name + that.says())
    		return that.says() + spec.name + that.says()
    	}
    	_func = function () {
    		return 'private'
    	}
    	return that
    }
    // console.log(cat.says())
    
    var mycat = cat({ name: 'cai1' })
    var mycat2 = cat({ name: 'cai2' })
    mycat.addAge(2)
    mycat2.addAge(5)
    console.log(cat2.getAge())
    

    小结

    传入一个对象(可以理解为寄生对象)在函数内部,以某种方式来增强这个对象,然后返回这个对象,从而在外部可以访问该对象。这个对象仅绑定上需要外显的属性或者方法,而私有的方法仅存放在函数体内,从而实现了属性或者方法的私有化。外部引用的对象只能访问到绑定在寄生对象的属性或者方法。任何能够返回新对象的函数都适用于这个模式。

    部件化*

    把所有复用的函数进行抽取,重心放在了函数本身,而非对象。当对象需要该函数时,将对象传入绑定该函数,从而实现函数代码的复用。

    • 定义一个函数,接收 对象 作为参数。 传入的参数绑定该函数后,重新抛出,相当于给对象绑定了函数。
    var addFunc = function (obj) {
    	obj.name = !!obj.name ? obj.name : 'test_name'
    	obj.Func = function () {
    		console.log('say my name:' + obj.name)
    		return 'say my name:' + obj.name
    	}
    	return obj
    }
    
    var getName = function (obj) {
    	obj.name = !!obj.name ? obj.name : 'test_name'
    	obj.getName = function () {
    		console.log('get my name:' + obj.name)
    		return obj.name
    	}
    	return obj
    }
    
    var cat = { name: 'minya' }
    cat = addFunc(cat)
    cat = getName(cat) // 通过函数化去包装,给这个对象绑定上新的函数方法。实现代码的复用。
    console.log(cat)
    cat.Func() // 可与包装函数同名,因为指代意义不一样
    cat.getName()
    

    总结

    本文仅针对 Javascript 精粹这本书的继承部分进行整理。 只是通过最核心的方式实现了最简单的继承模式,在红宝书中还有很多不同类型或者不同类型组合的内容。在实际使用中,上述继承方法不会单独出现。 ref2 提供了与红宝书较为接近的继承模式。 下次有机会再来整理。

    Reference:

    1. 廖雪峰 JAVA 继承

    2. ? 一文看懂 JS 继承

    3. ES6 入门教程


    起源地下载网 » 继承—— The good parts of js

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元