构造函数、实例、原型三者之间的关系面向过程编程:解决问题的过程,按步骤进行,第一步做什么,第二步做什么,一步一步完成
面向对象编程:找具有解决问题功能的对象,调用其功能,完成任务。如果对象不存在,则创建对象。
现实生活中面向过程和面向对象的理解?
今天想吃回锅肉?
1. 买肉自己做 -> 面向过程
1. 买肉
2. 清洗肉
3. 切片
4. 炒肉
5. 吃肉
2. 点外卖,去餐馆,找厨师做一份 -> 面向对象
找到解决问题的对象,使用其功能,解决你的问题
软件世界面向对象
面向过程编程思想
- 按步骤一步一步解决问题
面向对象编程思想:
找解决问题对象,调用功能解决问题, 如果对象不存在
=> 自己创建对象,封装功能,解决问题
以一个练习题来领悟一下面向对象和过程:
分割字符串按指定字符_,存储到数组中
'javascript_css_html' -> ['javascript','css','html']
function test1(){
let str = 'javascript_css_html_'
let line = '' // 存储字符串
let arr = [] // 数组
// 循环遍历字符串
for(let i = 0; i < str.length; i++){
let chars = str.charAt(i) // 获取字符
// 判断字符是不是——
if(chars !== '_'){
// 拼接字符
line = line + chars // line: 'javascript'
}else{
// 存储到数组
if(line != ''){
arr.push(line)
line = '' // 置空
}
}
}
console.log(arr)
}
// test1()
function test2(){
let str = 'javascript_css_html'
// 找解决问题的对象,调用其功能,解决问题
let arr = str.split('_') // 找字符串对象,调吸入分割字符的功能split方法
console.log(arr)
}
test2()
</script>
创建对象:
简单方式new Object和字面量方式
工厂函数,写一个函数,解决代码的重复问题。
<script>
// //创建方式
let personObj=new Object();
//对象属性
personObj.name='小明'
personObj.age=3
//对象方法
personObj.palyGame=function(){
console.log('玩游戏')
}
personObj.palyGame()
// 字面量方式
let person={
name:'小小',
age:18,
playGame:function(){
console.log('玩游戏')
}
}
person.playGame();
//工厂函数
function creat(name,age){
return{
name:name,
age:age,
say:function(){
console.log(this.name)
}
}
}
//生成实例对象
var p1=creat('jack',18)
var p2=creat('rose',17)
console.log(p1)
p1.say()
console.log(p2)
</script>
解析构造函数代码的执行
而要创建Person 实例,则必须使用new 操作符。
以这种方式调用构造函数会经历以下4 个步骤:
1. 创建一个新对象
2. 将构造函数的作用域赋给新对象(因此this 就指向了这个新对象)
3. 执行构造函数中的代码
4. 返回新对象
<script>
function Person(name,age){
//使用new操作符调用person()的时候,
// 实际上会先创建一个对象
// var instance={}
//然后让内部的this指向instance对象
// this=instance
// 接下来所针对this的操作实际是instance
this.name=name
this.age=age
this.say=function(){
console.log(this.name)
}
//在函数的结尾会将this返回,也就是instance
// return this
}
</script>
面向对象小案例: 点击按钮改变区块颜色
<style>
*{
padding: 0;
margin: 0;
}
div{
width: 200px;
height: 200px;
background-color: skyblue;
}
</style>
</head>
<body>
<button class="btn1">点我换皮肤</button>
<button class="btn2">点我也可以</button>
<div></div>
<script>
function ChangeColor(btn,color){
this.btn=document.querySelector(btn)
this.div=document.querySelector('div')
this.bindChange=function(){
//方法中的this是当前对象
let _this=this
this.btn.addEventListener('click',function(){
//事件处理函数中的This是事件源
_this.div.style.backgroundColor=color
})
}
}
let c1=new ChangeColor('.btn1','red')
c1.bindChange()
let c2=new ChangeColor('.btn2','green')
c2.bindChange()
</script>
这里有个小知识点:
- 如果对象属性是变量,调用对象属性时,点语句改为中括号
- Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
原型prototype:
<script>
function person(name,age){
this.name=name
this.age=age
}
person.prototype.sayhi=function(){console.log('hello,word')}
// 创建实例对象
// p1.__proto__===person.prototype
// p2.__proto__===person.prototype
var p1=new person('jack',18)
var p2=new person('rose',17)
console.log(p1)
console.log(p2)
console.log(p1.sayhi)
</script>
构造函数、实例、引用变量、对象名,对象概念区分
prototype原型对象
- Javascript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象(原型对象prototype)。
- 这个原型对象的所有属性和方法,都会被构造函数的实例访问(继承)。
- 这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。
构造函数、实例、原型三者之间的关系
属性成员的搜索原则:
属性成员的搜索原则 - 原型链
- - 先在自己身上找,找到即返回
- - 自己身上找不到,则沿着原型链向上查找,找到即返回
- - 如果一直到原型链的末端还没有找到,则返回 undefined
原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
实例对象读写原型对象成员:
- - 先在自己身上找,找到即返回
- - 自己身上找不到,则沿着原型链向上查找,找到即返回
- - 如果一直到原型链的末端还没有找到,则返回 undefined
值类型成员写入(实例对象.值类型成员 = xx):
- 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上,也就是说该行为实际上会屏蔽掉对原型对象成员的访问。
引用类型成员写入(实例对象.引用类型成员 = xx):
- 同上
复杂类型修改(实例对象.成员.xx = xx):
- 同样会先在自己身上找该成员,如果自己身上找到则直接修改
- 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改
- 如果一直到原型链的末端还没有找到该成员,则报错(实例对象.undefined.xx = xx)
更简单的原型语法 :
为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
在该示例中,我们将 Person.prototype 重置到了一个新的对象。
这样做的好处就是为 Person.prototype 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 constructor 成员。
所以,我们为了保持 constructor 的指向正确,建议的写法是:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
原型对象使用建议
- - 私有成员(一般就是非函数成员)放到构造函数中
- - 共享成员(一般就是函数)放到原型对象中
- - 如果重置了 prototype 记得修正 constructor 的指向
今天的分享到这里就结束了,若有错误请指正!