一、前置概念
- 对象:在js中一切function都是对象,function是构建对象的基本属性
- 实例:通过new关键字定义的变量称之为实例
- 显示原型:对象有显示原型,并且当前的显示原型指向的原型
- 隐示原型:实例都有隐示原型,隐示原型指向对象的显示原型*
二、继承实现的两种常见方法
1. 原型继承
- 原型继承主要的实现原理是将子类的显示原型指向父类的实例
/**
* 定义父类,一切继承都来源于对象,js的对象就是有function构成的
*/
function Book(){
this.bookname =arguments[0]
this.author =arguments[1]
this.getBookInfo=function(){
console.log(`${this.bookname} 出版人 ${this.author},共计1本书籍`)
}
}
/**
* 定义子类,通过原型链继承父类
*/
function EnBook(){
this.readyEn = function(){
console.log(`${this.bookname} 出版人 ${this.author},可以读英语`)
}
}
EnBook.prototype = new Book('','')
EnBook.bookname="英语教科书1" // 无效操作
EnBook.author="liuxinzhous1" // 无效操作
// EnBook.getBookInfo()
const enbook =new EnBook()
enbook.bookname="英语教科书1"
enbook.author="liuxinzhous1"
console.log(enbook.bookname)
console.log(enbook.author)
enbook.getBookInfo()
enbook.readyEn()
// 实例永远是对象,而且一直指向最顶端的父类,只能按照对象来判断
console.log(enbook instanceof EnBook)
console.log(enbook instanceof Book)
console.log(enbook instanceof Object)
// 对象永远是方法,
console.log(EnBook instanceof Function)
console.log(Book instanceof Function)
console.log(Object instanceof Function)
重要点:
- 实例对象可以访问父类对象的属性和方法,也可以访问子子类对象的方法和属性
- 也就是说实例对象既是子类的实例,也是父类的实例
- 实例永远是对象,而且一直指向最顶端的父类,只能按照对象来判断
- 对象永远是方法 缺点:
- 没有构造函数,导致无法在构造方法中添加属性和方法
- 无法实现多继承
- 创建子类实例时,无法向父类构造函数传参,没有super方法
2. es6新语法实现继承
constructor(name){
this.name = name
}
sayHi(){
console.log(`${this.name}:正在说话,我是老大`)
}
}
class Student extends Person{
constructor(name){
super(name)
}
study(){
console.log(`${this.name}:我是学生`)
}
}
console.log(Person.prototype)
console.log(Student.prototype)
console.log(Person instanceof Function) // class 对象的本质是function
console.log(Student instanceof Function) // class 对象的本质是function
const xiaoming = new Student()
// 实例对象的隐示原型于创建的对象的显示原型一致
console.log(xiaoming.__proto__ === Student.prototype)
// 对象的显示原型等于父类的隐示原型,再类型匹配上,对象的显示原型==== 对象的类型
console.log(Student.prototype instanceof Person)
三、原型与原型链
- 类对象的原型实际指的是该对象的prototype对象
- 子类对象的prototype原型永远指向父类
- 实例对象的__proto__指向对象的原型prototype
constructor(name){
this.name = name
}
}
class EnBook extends Book{
constructor(name,school){
super(name)
this.school = school
}
}
class ClassEN extends EnBook{
constructor(name,school ,classNo){
super(name,school)
this.classNo = classNo
}
}
// const xiaoxue = new ClassEN('英语练习册', '小学','一年级')
// console.log(xiaoxue.name)
// console.log(xiaoxue.school)
// console.log(xiaoxue.classNo)
// // 类型可以继承显示,但是原型不可以继承显示
// console.log(xiaoxue instanceof ClassEN) // true
// console.log(xiaoxue instanceof EnBook) // true
// console.log(xiaoxue instanceof Book) // true
// // 实例对象的隐示原型指向类对象的显示原型
// console.log(xiaoxue.__proto__ ===ClassEN.prototype) // 只对象当前创建对象
// console.log(xiaoxue.__proto__ ===EnBook.prototype) // false
// console.log(xiaoxue.__proto__ ===Book.prototype) // false
// console.log("--------------------------")
// console.log(xiaoxue.prototype) // 实例没有prototype对象
// console.log(ClassEN.prototype) // 对象的显示对象指向父对象,构造方法是自己
// console.log(EnBook.prototype) // 对象的显示对象指向父对象,构造方法是自己
// console.log(Book.prototype) // 对象的显示对象指向父对象,构造方法是自己
// console.log("--------------------------")
// console.log(xiaoxue.__proto__) // 实例没有prototype对象
// console.log(ClassEN.prototype) // 对象的显示对象指向父对象,构造方法是自己
// console.log(ClassEN.prototype.__proto__) // 对象的显示对象指向父对象,构造方法是自己
// // console.log(ClassEN.prototype.__proto__) // 对象的显示对象指向父对象,构造方法是自己
const xiaoming = new EnBook('英语词典','大学')
// 子类对象的prototype原型永远指向父类
// console.log(ClassEN.prototype) // EnBook
// console.log(EnBook.prototype) // Book
// console.log(Book.prototype) // Object
console.log(xiaoming.__proto__ == EnBook.prototype) // Book
console.log(EnBook.__proto__)
console.log(ClassEN.__proto__)
四、作用域和闭包的关系
js改变作用域的三种方式进行理解改造
1. bind函数的改造
* 重新写bind函数
* 要点利用数组的slice函数对参数进行分解,
* 然后返回一个内部函数
* */
Function.prototype.bind = function () {
// 利用slice函数进行类数组转换为真实数组
let args = Array.prototype.slice.call(arguments)
// 获取arguments的第一个对象为最新的this对象
let that = args.shift()
const sefl = this
// 返回一个新的方法
return function () {
// 获取bing函数时的参数,并合并到bing调用时的参数
let applyArgs = Array.prototype.slice.call(arguments)
applyArgs.shift()
// 利用apply进行数据传递
sefl.apply(that, args.concat(applyArgs))
// 或者利用call进行散列参数传递
sefl.call(that,...args,...applyArgs)
}
}
2. call函数的改造
* 参数为当前的要修改的this,已经要传递的参数,传递的参数需要用...进行解析
* 解析参数以后给第一个context添加一个this对象,此时的this指向的就是这个方法
* 然后调用这个方法
* */
Function.prototype.call = function (context, ...args) {
//这里默认不传就是给window,也可以用es6给参数设置默认参数
context = context || window
args = args ? args : []
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol()
context[key] = this
//通过隐式绑定的方式调用函数
const result = context[key](...args)
//删除添加的属性
delete context[key]
//返回函数调用的返回值
return result
}
3. apply函数改造
* context ,要改变的this对象
* args,要调用调用的参数
* 和call一样,只是接受的参数不同
*/
Function.prototype.apply = function (context, args) {
// 获取参数
context = context || window
args =args
let key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
3. apply加强函数改造可以传递多个数组
* context ,要改变的this对象
* args,要调用调用的参数
* 主要是拼接多个参数
*/
Function.prototype.applyPlus = function (context, ...args) {
let newArrs =[]
// 获取参数
context = context || window
args = Array.prototype.slice.call(args)
for (let index = 0; index < args.length; index++) {
const element = args[index];
newArrs = newArrs.concat(element)
}
let key = Symbol()
context[key] = this
const result = context[key](...newArrs)
delete context[key]
return result
}