JavaScript学习笔记 08、面向对象(下)
4.3、原型链查找(在原型propotype上添加属性)
原理分析(含案例)
前面也说到了原型对于普通函数(即一个方法之类)的没有任何用处,而对于构造函数则有很多的作用!
什么是原型链查找呢?
- js规定,通过实例打点可以访问它的原型的属性和方法,这就称为
原型链查找
。
我们来看一个例子:
<script>
//构造函数(其实就是一个函数)
function People(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//在构造函数中的prototype(原型)里添加属性
People.prototype.nationName = '中国';
var changlu = new People("changlu", '男', 18);
console.log(changlu)
console.log(changlu.nationName);
</script>
得到的结论:若是从实例上去查找指定属性,若是原本实例上没有该属性,就会从其原型(propotype
属性)里去找。(任何一个对象同样也会按着这个原型链去查找),若是找到就将其输出。
- 实际上这个对象中并没有这个
propotype
属性,仅仅只是浏览器用来给我们方便查看的,对象中真正拥有的应该__propt__
属性,该属性你就可以看做是原本构造函数的propotype
原型属性(即浏览器给我们展示出来的)。
原型链遮蔽效应(在对象中直接添加属性即可)
直接使用之前学习的给对象加属性的方式:对象.属性
即可
示例:
<script>
//构造函数(其实就是一个函数)
function People(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//在构造函数中的prototype(原型)里添加属性
People.prototype.nationName = '英国';
var changlu = new People("changlu", '男', 18);
//此时我们的真实国家是中国!我们需要重新给对象加属性
changlu.nationName = "中国";
console.log(changlu.nationName);//由于在本对象中添加了属性,那么此时就不会去到原型对象里去找属性了
</script>
4.4、在propotype原型对象上添加方法
为什么要在原型上添加方法?
结论:若是在构造函数中添加方法,那么创建一个实例时,就会重新创造一个新的函数绑定到你新创建的对象上。这就会带来内存浪费的问题。
我们来看个例子,直接将方法添加到构造函数中去,接着我们来进行实例化两个对象,来尝试比较两个对象中的info函数是否是同一个引用:
<script>
//构造函数(其实就是一个函数)
function People(name, gender, age) {
this.name = name;
this.gender = gender;
this.age = age;
this.info = function () {
console.log("name:" + this.name + ",gender:" + this.gender, ",age:" + this.age);
}
}
//创建两个实例
var xiaoming = new People("xiaoming", '男', 18);
var xiaowang = new People("xiaowang", '男', 25);
//创建的两个实例上的函数引用值是否相同(实际是其的引用值不同,表示两个函数都是额外创建出来的)
console.log(xiaoming.info == xiaowang.info)
console.log(xiaoming.info === xiaowang.info)
</script>
说明:很明显,这两个对象创建后两个引用值不同就验证了两个对象的函数都是重新创建的!
原型上添加方法(减少内存开销)
方式:构造函数.prototype.方法 = function(){xxx}
。
好处:使用这种方式,就是将函数添加到了构造函数的原型中,当我们实例化的对象调用这个方法时,就会进行原型链查找进而调用方法,也就是说若是创建多个实例就是共用一个方法!!!
示例:
<script>
//构造函数(其实就是一个函数)
function People(name, gender, age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//在原型中添加方法
People.prototype.info = function () {
console.log("name:" + name + ",gender:" + gender, ",age:" + age);
};
//创建两个实例
var xiaoming = new People("xiaoming", '男', 18);
var xiaowang = new People("xiaowang", '男', 25);
//创建的两个实例上的函数引用值是否相同(实际是其的引用值不同,表示两个函数都是额外创建出来的)
console.log(xiaoming.info == xiaowang.info)
console.log(xiaoming.info === xiaowang.info)
console.log(xiaoming);
console.log(xiaowang);
</script>
4.5、原型链的终点(Object.prototype)
我们来看下面这个图:
- 首先我们要明确几个内容①你自定义的构造函数其本身会自带一个
propotype
(原型对象)。②你创建的对象(任何一个对象)其会自带一个__proto__
对象(实际就是其原本构造函数的原型对象)。 - 描述原型链查找过程:我们在调对象的一个属性或者一个方法时,若是其对象本身没有该属性或方法,会从该对象中的
__proto__
即构造函数的原型对象里去找,还找不到就继续往上去找直至终点—本章内容Object.prototype
。
通过上面的图,我们是不是一下子就很豁然开朗,我们创建自定义对象时就附带了很多的方法,为什么能调用呢?这就要归功于我们的原型链,那些方法都藏于构造函数的原型对象中!!!
<script>
function Own() {
}
//查看一下Own的原型指向的原型对象(实际就是Object的prototype原型对象)
console.log(Own.prototype.__proto__);
//来验证一下是不是Object的prototype原型对象
console.log(Own.prototype.__proto__ === Object.prototype);
//验证一下Object的原型对象prototype其原型是否为终点
console.log(Own.prototype.__proto__.__proto__);
</script>
说明:上面的测验能够进行很好的验证原型链的终点是Object的原型,并且我们能够注意到Object的原型对象中有需要的方法,这也就意味着我们能够对自定义的对象调用这些方法!!!
4.6、初体验:Object的原型对象方法测试(重写toString()方法)
Object构造函数的原型对象相关方法:
本部分我们来测试Object构造函数中原型对象里的hasOwnProperty()
、toString()
方法以及来进行重写toString()
方法(也称为遮盖效应):
<script>
function Own() {
this.name = "changlu";
}
//为自定义构造函数的原型对象添加属性
Own.prototype.nationName = "中国";
var xiaoming = new Own();
//测试两个原型链中的方法(Object的原型对象中的)
//hasOwnProperty():是否有属于自己的属性(非原型链中的)
console.log(xiaoming.hasOwnProperty("nationName"));//false | 该属性存在于Own的原型对象中,不属于xiaoming对象的属性
console.log(xiaoming.hasOwnProperty("name"));//true | 小明对象中有该属性
//toString():用来描述对象的,Object中仅仅只是返回地址(浏览器不显示地址)
console.log(xiaoming.toString());//[object Object] | 返回的就是引用地址
//自定义toString()方法
Own.prototype.toString = function () {
console.log("name:" + this.name);
}
//成功调用toString()方法
console.log(xiaoming.toString());//"name:changlu"
</script>
五、继承(ES6有新的继承方式)
介绍
在js
中我们如何继承父类呢?
继承方式:即将成为子类的函数名.propotype = new 父类();
重写父类方法方式:子类函数名.propotype.重写方法名 = function(){};
,需要注意的是你应当先继承了之后再重写!
示例
示例:包含了继承父类测试以及重写父类方法后测试
<script>
function People(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//给原型对象添加函数方法,方便复用
People.prototype.info = function () {
console.log("People=>name:" + this.name + ",sex:" + this.sex + ",age:" + this.age);
}
function Student(name, sex, age, height) {
this.name = name;
this.sex = sex;
this.age = age;
this.height = height;
}
//继承父类:学生构造函数继承父类
Student.prototype = new People();
//测试一:成功调用父类People的方法
var changlu = new Student("changlu", '男', 18);
changlu.info();
//重写父类方法(继承之后):重写父类的info方法
Student.prototype.info = function () {
console.log("我的名字叫做" + this.name + ",今年" + this.age + "岁啦!");
}
//测试二:重写父类方法info()是否生效
changlu.info();
</script>
六、实际案例
1、红绿灯
需求:点击红绿灯就能够进行红绿灯的切换。
分析:采用面向对象的思想,将红绿灯抽象为一个类,创建红绿灯构造函数。
- 属性:
color
,来决定点击后的颜色。 - 方法:
init()
初始化红绿灯(创建节点以及挂载到指定盒子中)、changeColor()
(绑定点击事件,只要点击就会进行切图操作)
<style>
* {
margin: 0;
padding: 0;
}
img {
width: 40px;
height: auto;
}
</style>
<body>
<!-- 用于挂载img图片 -->
<div class="box" id="box"></div>
<script>
var mybox = document.getElementById("box");
//红绿灯类
function TrafficLight() {
//颜色:红色1,绿2,黄3
this.color = 1;
this.init();
this.changeColor();
}
//初始化方法:挂载图片
TrafficLight.prototype.init = function () {
this.dom = document.createElement('img');
this.dom.src = "./images/" + this.color + ".jpg";
//挂载到盒子中去
mybox.appendChild(this.dom);
};
//绑定点击事件,对应事件就是改变图片(实现红绿灯转换)
TrafficLight.prototype.changeColor = function () {
var self = this;//由于要使用到对象的color属性,所以需要进行保存
//绑定点击事件:点击一下就更改一个图片(红绿黄改变)
this.dom.onclick = function () {
self.color++;
if (self.color == 4) {
self.color = 1;
}
this.src = "./images/" + self.color + ".jpg";
}
}
//实例化100个红绿灯
for (let i = 1; i <= 100; i++) {
new TrafficLight();
}
</script>
</body>
2、炫彩小灯案例
第一版本
效果与分析
效果展示:
思路分析:
将小球抽象成类,通过浏览器鼠标移动事件来每次创建一个小球,通过定时器来对所有的小球进行更新操作(一秒几十次小球更新吧,包括对小球的位置、大小、透明度)。
小球类:
- 属性:颜色、坐标位置、小球移动的方向(其实小球向x,y轴移动的长度,随机数[-10,10])、透明度、半径。
- 方法:
init()
:初始化方法,创建div元素,对对象属性进行初始化,并将像这些初始化值实际存储到dom元素上。update()
:对小球的一系列状态进行更新操作,这个并不是我们来单独调用的,而是通过一个定时器来进行不断更新操作!更新操作过程中将小球的透明度作用结束终点,结束时将数组中的指定对象移除以及从dom树上移除指定dom元素。(减少内存消耗)
定时器:每50ms执行更新操作,每次更新操作作用于所有的小球。(如何作用到所有小球呢?需要在new的过程中将对象push到数组中即可)。
onmousemove
事件绑定:给页面document添加事件,将new操作就放在这里进行!
源码
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: black;
}
div {
/* 将浏览器作为基准对象 */
position: absolute;
}
</style>
<body>
<script>
//用来存储小球的数组
var ballArr = [];
var ballColor = ['red', 'blue', 'yellow', "green", 'gold', "grey"];
// 小球类,需要传入坐标x,y
function Ball(x, y) {
this.x = x;
this.y = y;
//半径
this.radius = 10;
//作用就是用于控制小球向某个位置方向移动,其中mx、my表示的是每次移动的长度范围是[-10,10]
this.mx = parseInt(Math.random() * 21) - 10;
this.my = parseInt(Math.random() * 21) - 10;
//设置小球颜色
this.color = ballColor[parseInt(Math.random() * ballColor.length)];
//透明度
this.opacity = 0.8;
//方法:执行初始化操作
this.init();
//将创建好的实例小球传入到数组里
ballArr.push(this);
}
//init():进行小球的初始化操作
Ball.prototype.init = function () {
//创建一个div元素
this.obj = document.createElement("div");
//设置元素效果
//小球大小
this.obj.style.width = (this.radius * 2) + "px";
this.obj.style.height = (this.radius * 2) + "px";
//设置边框(小球)
this.obj.style.borderRadius = this.radius + "px";
//小球颜色
this.obj.style.backgroundColor = this.color;
//小球位置
this.obj.style.left = this.x + "px";
this.obj.style.top = this.y + "px";
//挂载到dom树上
document.body.appendChild(this.obj);
}
//update():进行更新小球的状态
Ball.prototype.update = function () {
//小球的透明度
this.opacity -= 0.05;
//以小球的透明度作为小球结束状态(一旦结束就要删除数组中存储的小球对象以及挂载好的dom元素)
if (this.opacity <= 0) {
//删除数组中的指定元素
for (let i = 0; i < ballArr.length; i++) {
if (ballArr[i] == this) {
ballArr.splice(i, 1);
break;
}
}
//移除在body上的dom元素
document.body.removeChild(this.obj);
return;
}
//对小球处于屏幕位置的重新计算
this.x += this.mx;
this.y -= this.my;
//小球半径越来越大
this.radius += 2.3;
//更新当前小球状态
//1、小球位置
this.obj.style.left = this.x + "px";
this.obj.style.top = this.y + "px";
//2、小球大小
this.obj.style.width = (this.radius * 2) + "px";
this.obj.style.height = (this.radius * 2) + "px";
this.obj.style.borderRadius = this.radius + "px";
//3、小球透明度
this.obj.style.opacity = this.opacity;
};
var ball = new Ball(100, 100);
//设置定时器
setInterval(function () {
//对数组中的元素都进行遍历更新操作
for (let i = 0; i < ballArr.length; i++) {
ballArr[i].update();
}
}, 50);
//鼠标移动事件(每移动一次就创建一个小球)
document.onmousemove = function (e) {
//获取到鼠标的x,y坐标
new Ball(e.clientX, e.clientY);
}
</script>
</body>
第二版本
效果:点击页面特效生效,并且特效小球会在一定大小时暂停;再次点击,特效失效。
思路分析:特效的效果与版本一一致,我们为对象添加了一个暂停属性,通过一个布尔值来判断是否需要暂停,暂停位置根据小球的透明度来决定(到达指定透明度)。对于重复单击令鼠标移动生效与不生效,仅仅只需要将移动的函数抽离出来,对鼠标移动事件进行null和函数赋值即可!!!
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: black;
}
div {
/* 将浏览器作为基准对象 */
position: absolute;
}
</style>
<body>
<script>
//用来存储小球的数组
var ballArr = [];
var ballColor = ['red', 'blue', 'yellow', "green", 'gold', "grey"];
// 小球类,需要传入坐标x,y
function Ball(x, y) {
this.x = x;
this.y = y;
//半径
this.radius = 10;
//作用就是用于控制小球向某个位置方向移动,其中mx、my表示的是每次移动的长度范围是[-10,10]
this.mx = parseInt(Math.random() * 21) - 10;
this.my = parseInt(Math.random() * 21) - 10;
//设置小球颜色
this.color = ballColor[parseInt(Math.random() * ballColor.length)];
//透明度
this.opacity = 0.8;
//设置是否暂停
this.isStop = false;
//方法:执行初始化操作
this.init();
//将创建好的实例小球传入到数组里
ballArr.push(this);
}
//init():进行小球的初始化操作
Ball.prototype.init = function () {
//创建一个div元素
this.obj = document.createElement("div");
//设置元素效果
//小球大小
this.obj.style.width = (this.radius * 2) + "px";
this.obj.style.height = (this.radius * 2) + "px";
//设置边框(小球)
this.obj.style.borderRadius = this.radius + "px";
//小球颜色
this.obj.style.backgroundColor = this.color;
//小球位置
this.obj.style.left = this.x + "px";
this.obj.style.top = this.y + "px";
//挂载到dom树上
document.body.appendChild(this.obj);
}
//update():进行更新小球的状态
Ball.prototype.update = function () {
//小球的透明度
this.opacity -= 0.05;
//以小球的透明度作为小球结束状态(一旦结束就要删除数组中存储的小球对象以及挂载好的dom元素)
if (this.opacity <= 0) {
//删除数组中的指定元素
for (let i = 0; i < ballArr.length; i++) {
if (ballArr[i] == this) {
ballArr.splice(i, 1);
break;
}
}
//移除在body上的dom元素
document.body.removeChild(this.obj);
return;
}
//设置小球暂停的情形(小球的宽在[30,50]区间)
var num = parseInt(this.obj.style.width);
if (num >= 30 && num <= 50) {
this.isStop = true;
}
//对小球处于屏幕位置的重新计算
this.x += this.mx;
this.y -= this.my;
//小球半径越来越大
this.radius += 2.3;
//更新当前小球状态
//1、小球位置
this.obj.style.left = this.x + "px";
this.obj.style.top = this.y + "px";
//2、小球大小
this.obj.style.width = (this.radius * 2) + "px";
this.obj.style.height = (this.radius * 2) + "px";
this.obj.style.borderRadius = this.radius + "px";
//3、小球透明度
this.obj.style.opacity = this.opacity;
};
//定时器
var myinterval = setInterval(function () {
//对数组中的元素都进行遍历更新操作
for (let i = 0; i < ballArr.length; i++) {
if (!ballArr[i].isStop)
ballArr[i].update();
}
}, 50);
//创建球函数
function createBall(e) {
//获取到鼠标的x,y坐标
new Ball(e.clientX, e.clientY);
}
//鼠标移动事件(每移动一次就创建一个小球)
//document.onmousemove = createBall;
document.onmousemove = null;
//文档的单击事件:控制onmousemove是否有对应的移动事件
var t = 0;
document.onclick = function () {
if (t == 1) {
t = 0;
document.onmousemove = null;
} else {
t = 1;
document.onmousemove = createBall;
}
}
</script>
</body>
- 点赞
- 收藏
- 关注作者
评论(0)