JS-原型链
首先我们需要牢记两点:
①_ _proto _和constructor属性是对象所独有的;
② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有 _proto __和constructor属性。
函数的prototype属性指向 函数的原型对象
对象的_ _proto __属性指向 原型对象
对象的constructor属性指向 该对象的构造函数
_ _proto __属性的作用就是 当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的_ _proto _属性所指向的那个对象(父对象)里找,一直找,直到proto属性的终点null,再往上找就相当于在null上取值,会报错。通过 _proto __属性将对象连接起来的这条链路即我们所谓的原型链。
prototype属性的作用就是 让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.proto === Foo.prototype。
constructor属性的含义就是 指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。
image.png
原文链接:https://blog.csdn.net/cc18868876837/article/details/81211729
1.基础知识
1.对数组,对象而言,都存在一个_ _proto _的私有属性,称之为原型,可为数组或对象附加更多的能力
2.对数组而言, _proto _依次上溯到Array ,Object;
对对象而言, _proto __只上溯到Object
3.可使用Object.getPrototypeOf()或proto获取当前成员的原型,使用hasOwnProperty()检测原型成员
4.可使用Object.create(null)创建无原型的对象(极少使用,完全字面量对象),不可使用hasOwnProperty()检测原型成员
const nums = [1, 2, 3, 4, 5]
const user = { name: 'xiaoming' }
console.log(nums)
console.log(user)
console.log(nums.__proto__)
console.log(user.__proto__)
console.log(Object.getPrototypeOf(nums)) //获取nums的当前成员原型
console.log(Object.getPrototypeOf(user)) //getPrototypeOf获取当前成员的原型
console.log(user.hasOwnProperty('name')) // true
2.原型方法
- 对象包含属性和方法,同时也继承其原型(_ _proto __)上的成员
-
如果对象和其原型同时拥有相同的成员,优先使用对象自身的成员
image.png
3.函数原型
1.函数原型
- 在js中函数也是对象,除拥有函数专属的prototype属性外,还存在_ _proto _ _属性
- 函数的本质其实也是对象,_ _proto _ _和prototype也都是对象
- 通常将实例方法定义在prototype上,实例的_ _ proto _ _指向类的类型prototype
- 可在_ _ proto _ _定义"类方法"(不推荐),适用于将函数当对象使用时的场景
image.png
2.函数原型的constructor
4.对象原型
- 对象的原型默认指向Object
- 可以使用Object.getPrototypeOf查看对象的原型
- this本身和原型是没有关联的,只会指向调用函数本身的对象
5.类型检测
- 可使用instanceof检测当前对象是否为指定的类型,如果原型发生改变形成‘继承’关系,则子实例也是其父类或祖先类的类型
- 可使用isPrototypeOf检测当前对象是否为指定类型的原型,类型检测会按原型链依次上溯直到Object为止
- 可结合in和hasOwnProperty来检测当前对象是否包含指定的属性,in将检测所有属性(含原型),hasOwnProperty仅检测自有属性。
// instanceof
function Child() {}
function Parent() {}
function Grandpa() {}
// 修改原型
const g = new Grandpa()
Parent.prototype = g
const p = new Parent()
Child.prototype = p
const c = new Child()
console.log(c instanceof Child) // true
console.log(c instanceof Parent) // true
console.log(c instanceof Grandpa) // true
console.log(p instanceof Parent) // true
console.log(p instanceof Grandpa) // true
console.log(g instanceof Grandpa) // true
// isPrototypeOf
const member = {}
const admin = {}
const user = {}
console.log(admin.isPrototypeOf(member)) // false
console.log(admin.__proto__.isPrototypeOf(member)) // true
console.log(admin.__proto__ === Object.prototype) // true
Object.setPrototypeOf(member, admin)
console.log(admin.isPrototypeOf(member)) // true
Object.setPrototypeOf(admin, user)
console.log(user.isPrototypeOf(admin)) // true
console.log(user.isPrototypeOf(member)) // true
// in & hasOwnProperty
const a = { name: 'xiaoming' }
const b = { url: 'baidu.com' }
console.log('name' in a) // true
console.log('url' in a) // false
Object.setPrototypeOf(a, b)
console.log('name' in a) // true
console.log('url' in a) // true
console.log(a.hasOwnProperty('name')) // true
console.log(a.hasOwnProperty('url')) // false
// 遍历属性
for(const key in a) {
console.log(`in: ${key}`)
if(a.hasOwnProperty(key)) {
console.log(`hasOwnProperty: ${key}`)
}
}
6.方法借用
+通过call()、apply()和bind()方法,我们可轻易地借用其它对象的方法,而无须从这些对象中继承它。
- 有时候在对象中实现某个功能,虽然自身不存在,但可“借用”别的对象中已有的方法
- 对DOM节点的操作经常会“借用”数组的某些函数来达到需求
const obj = {
nums: [1, 2, 3, 4, 5]
}
Object.setPrototypeOf(obj, {
max(nums) {
nums = nums || this.nums
return nums.sort(function (a, b) { return b - a })[0]
}
})
console.log(obj.max())
//Dom节点筛选
const btns = document.querySelectorAll('button')
//btns类数组借用数组的filter且将参数item=>item.hasAttribute('class')作为filter的回调
const filterBtns = Array.prototype.filter.call(btns, item => item.hasAttribute('class'))
/**使用字面量借用方法 字面量是一种遵循JavaScript规则的语法结构,MDN 这样解释:
在JavaScript中,使用字面量可以代表值。它们是固定值,不是变量,就是在脚本中按字面给出的。
字面量可以简写原型方法:
*/
const filterBtns = [].filter.call(btns, item => item.hasAttribute('class'))
7.深度认识_ _ proto _ _
https://blog.csdn.net/cc18868876837/article/details/81211729
8.原型继承(7种方式)
方式1:原型链继承(不推荐)
缺点:
无法向父类构造函数传参
父类的所有属性被共享,只要一个实例修改了属性,其他所有的子类示例都会被影响
//构造函数
function Person(age) {
this.age = age || 18
}
Person.prototype.sleep = function () {
console.log('sleeping')
}
//=======================================
function Programmer() { }
Programmer.prototype = new Person()
Programmer.prototype.code = function () {
console.log('编程人会coding')
}
let xiaoming = new Programmer()
xiaoming.code()
xiaoming.sleep()
console.log(xiaoming.age)
console.log(xiaoming instanceof Person)
console.log(xiaoming instanceof Programmer)
console.dir(Object.getPrototypeOf(xiaoming))
console.dir(xiaoming.__proto__)
方式 2:借用构造函数(经典继承)(不推荐)
优点:
- 可以为父类传参
- 避免了共享属性
缺点:
- 只是子类的实例,不是父类的实例
- 方法都在构造函数中定义,每次创建实例都会创建一遍方法
//构造函数
function Person(age) {
this.age = age || 18
}
Person.prototype.sleep = function () {
console.log('sleeping')
}
//==============================
function Programmer(name){
Person.call(this)
this.name=name
this.code=function(){
console.log('codecodecode')
}
}
let xiaoming=new Programmer('xiaoming')
console.log(`${xiaoming.name} ${xiaoming.age}`)
//方法都在构造函数中定义,每次创建实例都会创建一遍方法
xiaoming.code()
console.log(xiaoming instanceof Programmer)
// false 只是子类的实例,不是父类的实例
console.log(xiaoming instanceof Person)
//只是子类的实例,不是父类的实例
xiaoming.sleep() //Uncaught TypeError:xiaoming.sleep is not a function
3.组合继承(推荐)
组合 原型链继承 和 借用构造函数继承
优点:
- 可以为父类传参
- 方法都在构造函数中定义,每次创建实例都会直接继承方法
缺点: - 调用了两次父类构造函数
//构造函数
function Person(age) {
this.age = age || 18
}
Person.prototype.sleep = function () {
console.log('sleeping')
}
//==============================
function Programmer(age, name) {
Person.call(this, age)
this.name = name
}
Programmer.prototype = new Person()
//通过Programmer.prototype.constructor=Programmer 修正constructor指向,不加此句programmer的构造函//数是Person的 ??
Programmer.prototype.constructor = Programmer
let xiaoming = new Programmer(10, 'xiaoming')
console.log(`${xiaoming.age} ${xiaoming.age} ${xiaoming.sleep}`)
xiaoming.sleep()
let zhangsanfeng = new Programmer(100, 'zhangsanfeng')
console.log(`${zhangsanfeng.age} ${zhangsanfeng.age} ${zhangsanfeng.sleep}`)
zhangsanfeng.sleep()
console.log(`${xiaoming instanceof Programmer} ${xiaoming instanceof Person}`)
4.原型式继承 (不推荐)
原型 prototype共享了属性和方法
一个实例修改了原型上的方法,其他都会被影响
function create(o) {
function F() { }
F.prototype = o
return new F()
}
let obj = { gift: ['switch', 'Mini'] }
let gift1 = create(obj)
console.log(gift1.gift)
let gift2 = create(obj)
console.log(gift2.gift)
gift2.gift.push('xxx')
//只在gift2中新增了一个 gift1中也有了变化
console.log(gift2.gift)
console.log(gift1.gift)
image.png
5. 寄生式继承(不推荐)
创建一个仅用于仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
缺点:
跟借用构造函数一样,每次创建对象都要创建一次方法
function createObj(o) {
//通过Object.create()实现继承的顺序至关重要,必须保证定义原型方法或新建对象在Object.create()之后进行
const clone = Object.create(o)
//创建了一个对象,增强对象
clone.sayName = function () {
console.log('hi')
}
return clone
}
6.寄生组合继承(最佳)
子类构造函数继承父类自身的属性和方法,子类原型继承父类原型的属性和方法
function Person(age) {
this.age = age || 18
}
Person.prototype.sleep = function () {
console.log('sleeping')
}
function Programmer(age, name) {
Person.call(this) //子类构造函数复制父类的自身属性和方法
this.name = name
}
Programmer.prototype = Object.create(Person.prototype) //子类原型接受父类的原型属性和方法
Programmer.prototype.constructor = Programmer //修复Programmer的构造函数指向
let xiaoming = new Programmer(16, '小名')
console.log(xiaoming.name)
7:ES6 extends(最佳)
//父类
class Father {
constructor(age) {
this.age = age
}
sleep() {
console.log('sleeping')
}
}
class Children extends Father {
constructor(age, name) {
super(age)
this.name = name
}
code() {
console.log('coding')
}
}
let bo = new Children(20, '伯')
let zhong = new Children(18, '仲')
console.log(`${bo.name} ${zhong.name}`)
console.log(`${bo instanceof Children} ${bo instanceof Father}`)
console.log(`${zhong instanceof Children} ${zhong instanceof Father}`)
console.log(bo.prototype === zhong.prototype)
image.png