一.原型链
1.什么是原型链:
每一个对象都有自己的原型,而原型也是对象,也有自己的原型,以此类推而形成的链式结构就叫做原型链
由于每个对象都有原型,这样就形成了一个关联一个,层层相互依赖的从属关系,我们把它们叫做原型链;通过这种机制,让对象得以使用原型中的属性和方法,并凭借原型链一层一层的按顺序继承,让对象能拥有原型链上所有原型的功能,这就是JavaScript对象背后的运作机制。
补充: 在JavaScript中,几乎每个原型链的末端都会是Object,并最后指向到null
2.原型指针
- prototype:
prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象; 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象);
- proto:
proto 是原型链查询中实际用到的,它总是指向 prototype,换句话说就是指向构造函数的原型对象,它是对象独有的。 注意,为什么Foo构造也有这个属性呢,因为在js的宇宙里万物皆对象,包括函数;
3.代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
1.原型链 : 每一个对象都有自己的原型, 而原型也是对象,
也会有自己的原型,此次类推形成链式结构。称之为原型链。(原型链的终点是null)
2.对象访问原型链规则 : 就近原则
* 对象先访问自己的,自己没有就找原型的,原型没有就找原型的原型,
一直到原型链终点null.如果还找不到。 属性则获取undefined,
方法则会报错 xxx is not a function
*/
//1.构造函数
/* function Person(name, age) {
this.name = name
this.age = age
// this.type = '学生'//如果自己有type,优先访问自己的
}
//2.原型对象 : 存储具有共同特征的数据
Person.prototype.type = '哺乳动物'
Person.prototype.country = '中国'
Person.prototype.eat = function () {
console.log(this.name + '吃东西')
}
//3.实例对象
let p1 = new Person('张三', 20)
let p2 = new Person('李四', 20)
console.log(p1)
/* 小测试 */
/* console.log(p1.name)//张三 p1自己有name属性
console.log(p1.age)//20 p1自己有age
console.log(p1.type)//哺乳动物 p1自己没有type,但是p1的原型有
console.log(p1.girlFriend)//undefined p1自己没有girlFriend,
p1的原型也没有girlFriend
p1.eat()// 吃东西 */
// p1.learn()//报错 undefined is not a function
/* 思考: p1自己没有toString, p1的原型也没有toString, 但是为什么不报错呢?
原因: p1的原型的原型有toString
*/
/* p1.toString()
/* 如何查看实例对象原型 : 两行 */
//查看p1的原型
// console.log(p1.__proto__.constructor)//Person
// console.log(Person.prototype === p1.__proto__)//true
// //查看p1的原型的原型
// console.log(p1.__proto__.__proto__.constructor)//Object
// console.log(Object.prototype === p1.__proto__.__proto__)//true
// //查看p1的原型的原型的原型
// console.log(p1.__proto__.__proto__.__proto__)//null */ */
/* 1.原型链:每一个实例对象都有自己的原型,而原型也是对象,
也有自己的原型,以此类推形成链式结构,称之为原型链
2.对象访问原型链规则:就近原则
对象优先访问自己的属性,自己没有才会访问原型,
原型也没有就访问原型的原型,以此类推直到原型链终点(null),
如果还没有,属性则获取undefined,方法则报错 xxx is not a function */
// 构造函数
function Person(name, age) {
this.name = name
this.age = age
}
// 原型
Person.prototype.type = '哺乳动物'
Person.prototype.country = '中国'
Person.prototype.eat = function () {
console.log('吃东西')
}
// 实例对象
let p1 = new Person('张三', 20)
console.log(p1)//Person {name: '张三', age: 20}
// 请说出下列代码运行结果
console.log(p1.name)//张三,p1有name属性
console.log(p1.type)//哺乳动物,p1没有type,但是p1的原型有
console.log(p1.girlFriend)//undefined,p1没有这个属性,
而且p1的原型也没有这个属性
p1.eat()//吃东西,p1自己没有eat,但是p1的原型有
// p1.learn()//报错,报错原因,p1没有learn,取出来undefined
// 查找p1的原型
console.log(p1.__proto__.constructor)//person
console.log(p1.__proto__=== Person.prototype)//true
// 查找p1的原型的原型
console.log(p1.__proto__.__proto__.constructor)//object
console.log(p1.__proto__.__proto__=== Object.prototype)//true
</script>
</body>
</html>
效果如下:
二.内置对象原型链
1.内置对象:js语言自带的一些对象,提供了一些简单或者是基本的必要功能(属性和方法)
2.特点:方便快速开发;
js提供多个内置对象:math、data、array、string等等
3.代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
// 数组对象
//实例化对象
/* let arr = [10,20,30]//new Array(10,20,30)
console.log( arr )
//1.1 查看arr的原型
console.log( arr.__proto__.constructor )//Array
console.log( arr.__proto__ === Array.prototype )//true
//1.2 查看arr的原型的原型
console.log( arr.__proto__.__proto__.constructor )//Object
console.log( arr.__proto__.__proto__ === Object.prototype )//true
// 字符串对象
let str = new String('abc')
console.log( str )
//2.1 查看str的原型
console.log( str.__proto__.constructor )//String
console.log( str.__proto__ === String.prototype )//true
//2.2 查看arr的原型的原型
console.log( str.__proto__.__proto__.constructor )//Object
console.log( str.__proto__.__proto__ === Object.prototype )//true
// 日期对象
let date = new Date()
/* js有几个特殊的对象, 无法使用 log来打印的,
需要用dir来打印: function date dom对象 */
/* console.dir( date )
//3.1 查看date的原型
console.log( date.__proto__.constructor )//Date
console.log( date.__proto__ === Date.prototype )//true
//3.2 查看date的原型的原型
console.log( date.__proto__.__proto__.constructor )//Object
console.log( date.__proto__.__proto__ === Object.prototype ) *///true */
// 1.查看str的原型链
let str = new String('abc')
console.log(str)
// 2.查看str的原型
console.log(str.__proto__.constructor)//ƒ String() { [native code] }
console.log(str.__proto__ === String.prototype)//true
// 3. 查看str的原型的原型
console.log(str.__proto__.__proto__.constructor)//ƒ Object() { [native code] }
console.log(str.__proto__.__proto__ === Object.prototype)//true
// 4.查看date的原型链
// 细节:js中有部分对象无法用log来打印的:dom对象,函数,日期,
如果想查看堆内存结构,就可以使用console.dir()
let date = new Date()
console.dir(date)
</script>
</body>
</html>
效果如下:
三.instanceof运算符
1.概念:
对象运算符(instanceof)用来判断一个对象是否属于某个指定的类或其子类的实例,如果是,返回真(true),否则返回假(false)。
2.代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
1. instanceof(关键字): 运算符。
用于检测 构造函数的prototype在不在实例对象的原型链中
简单来说: 亲子鉴定,鉴定两个对象之间有没有血缘关系
2. 实例对象 instanceof 构造函数
检测 右边构造函数的prototype 在不在 左边实例对象的原型链中
3. 应用 : 某些函数为了限制你的数据类型,
在内部需要用instanceof进行判断是否是正确的数据类型
*/
let arr = [10, 20, 30]
// arr-> Array.prototype -> Object.prototype -> null
console.log(arr instanceof Array)//true
console.log(arr instanceof Object)//true
console.log(arr instanceof String)//false
//封装一个函数,要求这个函数必须要传数组类型、 传其他类型不可以
function fn(arr) {
if (arr instanceof Array) {
console.log(arr.reverse())
} else {
console.log('数据类型错误')
}
}
fn([10, 20, 30])
fn('abc')
</script>
</body>
</html>
效果如下:
四.原型链应用:封装模态框
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>面向对象封装消息提示</title>
<style>
.modal {
width: 300px;
min-height: 100px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 4px;
position: fixed;
z-index: 999;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
background-color: #fff;
}
.modal .header {
line-height: 40px;
padding: 0 10px;
position: relative;
font-size: 20px;
}
.modal .header i {
font-style: normal;
color: #999;
position: absolute;
right: 15px;
top: -2px;
cursor: pointer;
}
.modal .body {
text-align: center;
padding: 10px;
}
.modal .footer {
display: flex;
justify-content: flex-end;
padding: 10px;
}
.modal .footer a {
padding: 3px 8px;
background: #ccc;
text-decoration: none;
color: #fff;
border-radius: 2px;
margin-right: 10px;
font-size: 14px;
}
.modal .footer a.submit {
background-color: #369;
}
</style>
</head>
<body>
<button id="btn1">消息提示1</button>
<button id="btn2">消息提示2</button>
<button id="btn3">消息提示3</button>
<button id="btn4">消息提示4</button>
<!-- <div class="modal">
<div class="header">提示消息 <i>x</i></div>
<div class="body">消息内容</div>
<div class="footer">
<a href="javascript:;" class="cancel">取消</a>
<a href="javascript:;" class="submit">确认</a>
</div>
</div> -->
<script>
//1. 模态框构造函数
function Modal(title, message) {
this.title = title
this.message = message
this.modalBox = `<div class="modal">
<div class="header">${this.title} <i>x</i></div>
<div class="body">${this.message}</div>
</div>`
}
//2. 模态框原型
Modal.prototype.open = function () {
//(1)创建空标签
let div = document.createElement('div')
//(2)设置标签内容
div.innerHTML = this.modalBox
//(3)添加到页面
document.body.appendChild(div)
//给删除按钮注册点击事件
div.querySelector('.header i').onclick = function () {
document.body.removeChild(div)
}
}
//1.确认框构造函数
function Confirm(title, message) {
this.title = title
this.message = message
this.modalBox = ` <div class="modal">
<div class="header">${this.title} <i>x</i></div>
<div class="body">${this.message}</div>
<div class="footer">
<a href="javascript:;" class="cancel">取消</a>
<a href="javascript:;" class="submit">确认</a>
</div>
</div>`
}
/* 为什么要继承: confirm确认框和 modal模态框 功能是一样的,
也要显示到页面,也要点击xx移除
1.继承 : 一个对象 继承 另一个对象 所有的成员
2.原型继承 : 把父对象 作为 子对象构造函数的原型
*/
Confirm.prototype = Modal.prototype
//继续给Confirm的原型添加自己的方法
Confirm.prototype.addEvent = function (confirm) {
let modal = document.querySelector('.modal')
modal.querySelector('.submit').onclick = function () {
//调用函数
confirm()
//移除模态框
modal.parentNode.removeChild(modal)
}
modal.querySelector('.cancel').onclick = function () {
//移除模态框
modal.parentNode.removeChild(modal)
}
}
//弹窗1
document.querySelector('#btn1').onclick = function () {
//创建模态框
let del = new Modal('友情提示', '删除成功')
del.open()
console.log(del )
}
//弹窗2
document.querySelector('#btn2').onclick = function () {
//创建模态框
let operate = new Modal('提示消息', '您没有权限操作')
operate.open()
}
//弹窗3
document.querySelector('#btn3').onclick = function () {
//创建确认框
let hint = new Confirm('友情提示', '您确定要删除吗?')
console.log(hint)
hint.open()
//添加确认功能
hint.addEvent(function () { alert('删除成功') })
}
//弹窗4
document.querySelector('#btn4').onclick = function () {
//创建确认框
let hint = new Confirm('友情提示', '您确定要下单吗?')
hint.open()
//添加确认功能
hint.addEvent(function () {
alert('下单成功')
})
}
</script>
</body>
</html>
五.原型继承(拓展)
1.原型继承的作用:
可以解决原型中属性的复用问题,下级函数一旦继承上级函数,那么下级函数的实例,就可以使用自身原型中的属性,和上级函数原型中的属性
2.代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
/*
1.继承 : 一个对象 拥有 另一个对象 所有的 成员
2.原型继承 :把父对象 作为子对象构造函数的原型
*/
//父对象
let father = {
house:{
address : '深圳湾一号',
price: 20000000
},
car:{
brand : '劳斯莱斯幻影',
price:15000000
}
}
//子对象
//构造函数
function Son(name,age){
this.name = name
this.age = age
}
//原型继承: 把父对象 作为子对象构造函数的原型
Son.prototype = father
//可选 : 原型继承之后,由于父对象覆盖原来的 子对象构造函数原型,
就会导致constructor消失.
//解决办法: 手动添加。(对开发几乎没有影响,也可以不加)
Son.prototype.constructor = Son
//实例对象
let s1 = new Son('张三',30)
let s2 = new Son('李四',20)
console.log(s1,s2)
</script>
</body>
</html>
效果如下:
六.总结
- 原型链:从一个实例对象往上找构造这个实例的相关联的对象,然后这个关联的对象再往上找它又有创造它的上一级的原型对象,以此类推,一直到 Object.prototype 原型对象终止,这个链条就断了,也就是说 Object.prototype 是整个原型链的顶端。通过什么往上找?就是通过 prototype 和 (两个下划线)proto(两个下划线).
- 原型:构造函数都有一个 prototype 属性,这是在声明一个函数时js 自动增加的,这个 prototype 指的就是原型对象。原型对象怎么区分被哪个构造函数引用?就是通过 constructor(构造器),原型对象中会有一个构造器,这个构造器会默认声明的那个函数。
- 构造函数:任何一个函数只要被 new 去操作(使用)就是构造函数,构造函数也是函数
- 实例:只要是对象就是一个实例
- 构造函数可以使用new 生成实例