文章目录
- 1.1、对象的定义
- 1.2、访问对象值(两种形式)
- 1.3、修改、创建、删除对象值
- 1.4、方法的创建与使用
- 1.5、遍历对象(for...in...)
- 1.6、对象的深浅克隆(针对于object)
- 规则1:`对象.方法()`,则这个函数的上下文就是打点的对象
- 规则2:`函数()`,则这个函数的上下文就是window对象
- 规则3:`数组[下标]()`,则这个函数上下文就是这个数组
- 规则4:IIFE中的函数`(function(){})()`,上下文是window对象
- 规则5:定时器延时器调用函数,上下文是window对象
- 规则6:事件处理函数的上下文是绑定事件的dom元素
- 规则7:call和apply方法能够自由指定上下文
- 三、构造函数
前言
本篇博客是关于javascript中面向对象知识点,若文章中出现相关问题,请指出!
所有博客文件目录索引:博客目录索引(持续更新)
一、认识对象
1.1、对象的定义
语法:
//注意点1:js中使用{}来表示对象,每组k:v之间使用逗号相隔
//注意点2:其中k一般不用引号,对于不符合命名规范的时候''包裹;v可以使用任意类型(基本类型,引用类型)
var obj = {
k: v,
k: v,
k: v,
k: v //注意点3:最后一个键值对可以不写逗号
}
示例:
<script>
var changlu = {
name: "cl",
age: 18,
sex: '男',
hobbys: ['编程','java','运动'],
'favourite-boot': '刻意练习' //属性名(key)中若是有不符合命名规范的要用''包裹
};
console.log(changlu);
</script>
1.2、访问对象值(两种形式)
两种形式:
- 使用
obj.属性
方式直接访问原本在对象中key没有’'包裹的值。 - 使用
obj['属性名']
来访问key有’‘(或无’'也是可以的)包裹的值。
我们就拿上面的示例进行举例:
<script>
var changlu = {
name: "cl",
age: 18,
sex: '男',
hobbys: ['编程', 'java', '运动'],
'favourite-boot': '刻意练习' //属性名(key)中若是有不符合命名规范的要用''包裹
};
console.log(changlu);
//方式一:直接使用.属性方式
console.log(changlu.name);
console.log(changlu.age);
console.log(changlu.sex);
console.log(changlu.hobbys);
//方式二:使用[]的形式来访问值
//针对于获取键使用''包裹的值。
console.log(changlu['favourite-boot']);
//访问正常的key值使用这类方式也是ok的
//console.log(changlu['name']);
//访问存储到变量名key对应的值
var searchName = "age";
console.log(changlu[searchName]);
</script>
小总结:对于正确命名规范的key
,我们可以直接使用.属性的方式进行访问;使用['属性名']
是能够访问到指定对象中的所有属性的!
1.3、修改、创建、删除对象值
介绍
修改:直接访问对象值进行修改即可。
创建:直接.属性即可创建。
删除:delete obj.属性
或delete obj['属性']
示例
<script>
var changlu = {
name: "cl",
age: 18,
sex: '男',
hobbys: ['编程', 'java', '运动'],
'favourite-boot': '刻意练习' //属性名(key)中若是有不符合命名规范的要用''包裹
};
//修改
changlu.age = 20;//直接修改即可
console.log(changlu.age);
//创建一个身高height
changlu.height = 180;//直接赋值就好
console.log(changlu.height);
//删除身高height以及key被''包裹的键值对
delete changlu.height;
delete changlu['favourite-boot'];
console.log(changlu);
</script>
1.4、方法的创建与使用
对于对象方法的创建你可以直接声明在在内部,或者在外部进行调用对象.新函数= function(){}
;
示例:
<body>
<script>
var changlu = {
name: "cl",
age: 18,
sex: '男',
info: function () {//创建函数方式(方式一)
//在对象的函数中你不能直接调用该对象的属性,只能使用this.属性或者对象名.属性获取。
console.log("My name is " + this.name + "," + "age:" + changlu.age);
}
};
//调用内部函数
changlu.info();
//创建函数方式(方式二)
changlu.sleep = function () {
console.log("I will be sleep!");
}
//调用函数
changlu.sleep();
console.log(changlu);
</script>
</body>
1.5、遍历对象(for…in…)
语法:for(var key in 对象)
,该key是对象的键,其是一个字符串。
示例:
<script>
var changlu = {
name: "cl",
age: 18,
sex: '男',
info: function () {//创建函数方式(方式一)
//在对象的函数中你不能直接调用该对象的属性,只能使用this.属性或者对象名.属性获取。
console.log("My name is " + this.name + "," + "age:" + changlu.age);
}
};
//遍历对象
for (const key in changlu) {
console.log(typeof key);//该key变量为string类型
console.log("key:" + key + ",value:" + changlu[key])
}
</script>
1.6、对象的深浅克隆(针对于object)
浅克隆
使用for ... in ...
来实现对象的浅克隆:对于对象中的引用类型实际上仅仅只是引用赋值
<script>
var obj = {
name: "cl",
age: 18,
t: [0, 1, 2, 3, {
a: 1,
b: 2,
c: [11, 22, 33]
}]
};
//浅克隆:for...in...
var newObj = {};
for (var key in obj) {
newObj[key] = obj[key];
}
//测试
console.log(newObj);
//证明是浅克隆:第三个属性是引用类型t,若是两个值相同则表示是没有成功
console.log(newObj.t == obj.t);
</script>
深克隆
对于深克隆需要通过使用递归的形式进行:
<script>
var obj = {
name: "cl",
age: 18,
t: [0, 1, 2, 3, {
a: 1,
b: 2,
c: [11, 22, 33]
}]
};
//深克隆(数组、对象、其他类型)
function deepClone(o) {
//判断是否是数组(数组类型实际就是object,所有需要提前进行判断)
if (Array.isArray(o)) {
var result = [];
//数组
for (let i = 0; i < o.length; i++) {
result.push(deepClone(o[i]));//进行递归调用
}
} else if (typeof o == "object") {
//需要克隆的是对象
var result = {};
for (let key in o) {
result[key] = deepClone(o[key]);//进行递归调用
}
} else {
//其他类型
var result = o;
}
return result;
}
//调用函数进行深克隆
var newobj = deepClone(obj);
console.log(newobj);
//测试一:obj.t
console.log(obj.t == newobj.t);
//测试二:obj.t[4]
console.log(obj.t[4] == newobj.t[4]);
//测试三:obj.t[4].c
console.log(obj.t[4].c == newobj.t[4].c);
</script>
二、认识函数的上下文
2.1、this关键字(函数上下文)
重点:函数的上下文(this
)是由调用方式决定的,并不是由本身决定的。
注意:对于一个对象的函数中使用到了this,我们不要一定认为该函数就是使用的本对象的属性或其他类型,要根据函数的调用方式以及那些形式调用的相关!!!
可见下面两个函数调用的时机与情况:
<script>
var obj = {
a: 1,
b: 2,
printAB: function () {
console.log("a:" + this.a + ",b:" + this.b);
}
}
//情况一:直接调用(此时this指代的是obj对象的上下文)
obj.printAB();
//情况二:将obj对象的函数赋值给变量,此时再调用方法,log中的this就指代window对象
var printAB = obj.printAB;//此时赋值到变量的printAB的this表示window对象上下文
printAB();//此时的this.a => window.a ,this.b => window.b,由于window对象没有全局变量a,b,所以就是undefined
</script>
2.2、上下文规则
规则1:对象.方法()
,则这个函数的上下文就是打点的对象
规则1:对象打点调用它的函数方法(对象.方法()
),则函数的上下文就是这个打点的对象!
案例1:
<script>
function fn() {
console.log(this.a + this.b);
}
var obj = {
a: 66,
b: 33,
fn: fn
};
//是这个obj对象调用的,那么this上下文指的就是obj
obj.fn();//99
</script>
案例2:最具有迷惑性
<script>
var obj = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
};
var obj2 = {
a: 3,
b: 4,
fn: obj.fn //千万不要被迷惑成了调用obj2.fn是obj.fn调动执行的,这里仅仅只是赋值函数过来
};
//obj2调用的,那么上下对象依旧是obj2
obj2.fn();//7
</script>
案例3:
<script>
function outer() {
var a = 11;
var b = 22;
return { //匿名对象
a: 33,
b: 44,
fn: function () {
console.log(this.a + this.b);
}
};
}
//outer()返回的是指定的匿名对象,那么就是匿名对象.fn()
//上下文对象就是这个匿名对象,this.a与this.b就依次是33、44
outer().fn();//77
</script>
案例4:
<script>
function fun() {
console.log(this.a + this.b);
}
var obj = {
a: 1,
b: 2,
c: [{
a: 3,
b: 4,
c: fun
}]
};
var a = 5;
//obj.c[0]的对象是指定数组中下标为0的对象,那么此时函数的上下文就是这个下标为0的对象
obj.c[0].c();//7
</script>
规则2:函数()
,则这个函数的上下文就是window对象
规则2:函数()
,则这个函数的上下文就是window对象。
案例1:
<script>
var obj1 = {
a: 1,
b: 2,
fn: function () {
console.log(this.a + this.b);
}
};
var a = 3;
var b = 4;
var fn = obj1.fn;
//规则2:直接是函数(),那么上下文就是window,3+4=7
fn();//7
</script>
案例2:这个案例十分good
<script>
function fun() {
return this.a + this.b;
}
var a = 1;
var b = 2;
var obj = {
a: 3,
b: fun(), //规则2:直接函数(),上下文为window,则为1+2=3
fun: fun //仅仅只是引用传递
};
var result = obj.fun();//规则1:上下文为obj,则为3+3=6
console.log(result);
</script>
规则3:数组[下标]()
,则这个函数上下文就是这个数组
规则3:数组(类数组对象,数组[下标]()
)枚举出函数进行声明,则这个函数上下文就是这个数组。
-
类数组对象
:所有键名为自然数序列(从0开始),且有length属性的对象。例如arguments
对象是最常见的类数组对象,是函数的实参列表。
案例1:
<script>
var arr = ['A', 'B', 'C', function () {
console.log(this[0]);
}];
//规则3:上下文对象是arr数组,那么this指的就是这个数组,this[0]='A'
arr[3]();//'A'
</script>
案例2:
<script>
function fun() {
//规则3:arguments就是类数组,这里指代的就是多个参数
arguments[3]();//arguments类数组就是上下文,使用this就是指的是arguments类数组
}
//调用函数
fun('A', 'B', 'C', function () {
console.log(this[1]);//'B'
});
</script>
规则4:IIFE中的函数(function(){})()
,上下文是window对象
规则四:IIFE中的函数(function(){})()
,上下文是window对象。
案例1:其中包含规则1、4,闭包概念,解题的关键点一定要从上至下细细查看!!!
<script>
var a = 1;
var obj = {
a: 2,
fun: (function () {
//IIFE函数中返回值为函数,形成闭包,a会存储起来值
var a = this.a;//1、上下文对象是window,所以为1
return function () {
console.log(a + this.a);
};//2、此时fun = function(){ console.log(a + this.a); }。其中a为闭包值1,this.a由于没有调用还不确定
})() //规则4:上下文对象是window
};
//规则1:上下文对象为obj,所以this.a = 2
obj.fun();//最终执行函数:1+2 = 3
</script>
规则5:定时器延时器调用函数,上下文是window对象
规则5:定时器延时器(setInterval()
、setTimeout()
)调用函数,上下文是window对象。
对于定时器或延时器中调用函数有两种情况:
①直接传入对应的函数
<script>
var obj = {
a: 1,
b: 2,
fun: function () {
console.log(this.a + this.b);
}
};
var a = 3;
var b = 4;
//首先将obj的对象传给回调函数,此时2s后由setTimeout进行调用该函数
//规则6:定时器或延时器调用的函数上下文对象是window
setTimeout(obj.fun, 1000);//7 | window.a+window.b = 7
</script>
②在延时器的回调函数中去打点调用对象函数
<script>
var obj = {
a: 1,
b: 2,
fun: function () {
console.log(this.a + this.b);
}
};
var a = 3;
var b = 4;
//规则5:此时setTimeout的回调函数指的是其中的匿名函数,确实该函数的作用域是window
//规则1:注意在匿名函数中调用的函数方法是以对象.方法形式进行调用函数,上下文为obj
setTimeout(function(){
obj.fun();//3 | 该上下文对象是obj,所以this.a+this.b=1+2=3
}, 1000);
</script>
规则6:事件处理函数的上下文是绑定事件的dom元素
DOM元素.onclick = function(){}
:该事件处理函数的上下文就是这个DOM元素。
案例1:点击哪个盒子,哪个盒子就变红,使用同一个事件处理函数实现。(利用规则5)
<style>
* {
margin: 0;
padding: 0;
}
div {
float: left;
width: 100px;
height: 100px;
border: 1px solid #000;
margin-left: 10px;
}
</style>
<body>
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
<script>
//点击变红
function clickToColor() {
//规则6:DOM元素调用时,上下文对象为DOM元素
this.style.backgroundColor = "red";
}
//绑定单击事件,使用同一个函数
var divlists = document.getElementsByTagName("div");
divlists[0].onclick = clickToColor;
divlists[1].onclick = clickToColor;
divlists[2].onclick = clickToColor;
</script>
</body>
案例2:与案例2大致相同,不同的是,点击盒子要延迟2s才变红。
思路:在函数中使用延时器,但需要注意的是延时器中的回调函数上下文是window对象,所以我们需要先在函数中保存一份DOM元素的上下文,之后使用DOM元素上下文进行调用。
<script>
//对应html、css样式实际与案例1相同,这里就不再重复使用
function clickToColor() {
//由于setTimeout中的回调函数的上下文是window对象,若是直接使用this就是指代window对象
//所以我们需要在函数中保存对应DOM元素的上下文对象,即this
var self = this;
setTimeout(function () {
self.style.backgroundColor = "red";
}, 2000);
}
//绑定单击事件,使用同一个函数
var divlists = document.getElementsByTagName("div");
divlists[0].onclick = clickToColor;
divlists[1].onclick = clickToColor;
divlists[2].onclick = clickToColor;
</script>
规则7:call和apply方法能够自由指定上下文
引出为什么要自由指定上下文?
首先我们通过一个示例来进行说明,对于下面该案例,我们想要输出stu对象的总成绩,有什么办法能够让我们直接使用该函数打印出总成绩吗?
<script>
function sum() {
console.log("总成绩为:" + this.chinese + this.english + this.math);
}
var stu = {
chinese: 99,
english: 100,
math: 66
}
</script>
有下面两种方式进行:都是通过在内部添加或者外部添加方式接着进行调用才可以
<script>
function sum() {
console.log("总成绩为:" + (this.chinese + this.english + this.math));
}
var stu = {
chinese: 99,
english: 100,
math: 98,
//方式一:直接在对象内部添加函数
//sum: sum
}
//方式二:在外部手动为stu添加一个键值对
stu.sum = sum;
//执行方法
stu.sum();//297
</script>
为了更加方便不需要在原本对象中进行添加,就可以使用call()或apply()方法!!!
call与apply的语法
语法:
- call方法:
函数.call(上下文对象)
- apply方法:
函数.apply(上下文对象)
我们接着通过使用call、apply来解决上面案例问题:
<script>
function sum() {
console.log("总成绩为:" + (this.chinese + this.english + this.math));
}
var stu = {
chinese: 99,
english: 100,
math: 98,
}
//call()方法
sum.call(stu);//总成绩为:297
//apply()方法
sum.apply(stu);//总成绩为:297
</script>
说明:这两个方法都是能够去指定上下文对象,效果都是一致,唯一不同的是两种方式第二个参数传值不同。
call与apply不同点
介绍不同点
使用call()
或apply()
都可以进行传值给指定调用的函数作为函数的形参,其中不同点:
-
call()
:函数的形参需要一个个传入。 -
apply()
:函数形参需要直接传入一个数组。
- 若是两种传值与规定不符就会出现异常或者读取到的值有误!!!
apply()使用情境
对于apply()
中需要传入数组,我们在什么情况下可以进行使用呢?
- 内部函数需要获取到外部的所有形参值时,可以直接将
arguments
传入!!!
示例:
<script>
function fun1() {
//若是函数(外)中的函数(内)需要获取到函数(外)传入的参数值的话,就可以直接将arguments传入
fun2.apply(this, arguments);
}
function fun2(a, b) {
alert(a + b);
}
fun1(33, 44);
</script>
2.3、七个规则总结
规则 | 上下文 |
对象.函数() | 对象 |
函数() | window |
| 数组 |
IIFE | window |
定时器 | window |
DOM事件处理函数 | 绑定DOM的元素 |
call和apply | 可任意指定 |
三、构造函数
3.1、new 函数()
来创建对象(四步骤)
语法:new 函数()
,返回一个对象。
根据JS规定,使用new操作符来调用函数会进行四个步骤:
- 执行函数体之前会自动创建一个空白对象。===>
var obj = {};
- 此时函数的上下文(this)会指向这个对象。 ===>
this = obj;
- 函数体内的语句会一一执行。 ===> 就会使用
this
指向的这个空白对象进行一系列操作。 - 最终函数会自动返回一个上下文对象this(无论函数是否有没有return,都会返回一个对象)。
示例:
<script>
function fun() {//1、var obj = {} 2、this = obj
this.a = 5;//3.1、obj.a = 5 | 相当于新创建一个属性a
this.b = 6;//3.2、obj.b = 6 | 相当于新创建一个属性b
var m = 123;
//4、返回一个对象this(即上下文)
}
var myfun = new fun();
console.log(myfun);
</script>
3.2、认识构造函数
构造函数:使用new调用一个函数即可,任何函数都可以使用构造函数,只需要用new
调用它。
- 通常我们约定对于构造函数的函数首字母开头要大写!(并不是说首字母大写的才是构造函数,而是根据是否使用
new 函数()
方式急性调用)
示例:
<script>
//业界约定好首字母开头大写的是构造函数
function People(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
this.info = function () {
console.log("name:" + name + ",sex=" + sex + ",age=" + age);
}
}
//new 函数()来创建一个对象
var changlu = new People("changlu", '男', 20);
var liner = new People("liner", '女', 23);
console.log(changlu);
console.log(liner);
changlu.info();
liner.info();
</script>
3.3、js中的"类"与实例(js没有纯粹类的概念)
Java
、C++
等是"面向对象"(oo,object-oriented)语言。
JavaScript
是"基于对象"(ob,object-based)语言。
- 在
js
中的构造函数实际上就可以类比于OO语言中的"类"。js
中没有特意书写类的格式,只能通过函数形式来模拟出类,在ES6
之中提出了class
类这个概念。js中没有纯粹的类概念,只有构造函数这个概念。
重点:
- js中使用构造函数来模拟"类"。(es6提出class概念,但是一定要记住js中没有纯粹的类概念,只有构造函数这个概念)
- 创建实例,就是通过
new 函数()
来返回一个上下文对象,也可以称为是实例。
四、原型与原型链
4.1、认识prototype属性
任何函数都有prototype
属性(意思是原型),该属性值实际上是一个对象{}
,这个对象中默认拥有constructor
属性指回函数。
作用:普通函数的prototype属性没有任何用户,而对于构造函数的prototype属性非常有用!
重点:构造函数的prototype属性是它的实例的原型。
<script>
//构造函数(其实就是一个函数)
function People(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//new 函数()来创建一个对象
var changlu = new People("changlu", '男', 20);
console.log(People.prototype);//输出原型
console.log(typeof People.prototype);//该函数的原型是一个对象
console.log(People === People.prototype.constructor);//这个原型对象中的constructor(函数)与我们自定义的函数相同
</script>
4.2、__proto__
与prototype属性
__proto__
:其是每个对象都有的属性。可以理解为构造器的原型!
- 简而言之将这个对象的
__proto__
属性看做是构造函数名.prototype
(也就是原型对象)
prototype
:其每个函数都有的属性。指的是原型,原型是一个对象,在这个对象中包含一个构造器属性指回函数。
<script>
//构造函数(其实就是一个函数)
function People(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//new 函数()来创建一个对象
var changlu = new People("changlu", '男', 20);
//对象的__proto__(指的就是其构造函数的原型)与构造函数原型比较:true
console.log(changlu.__proto__ == People.prototype);
//构造函数原型的构造函数属性与构造函数比较:true
console.log(People.prototype.constructor == People);
</script>
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>
我是长路,感谢你的耐心阅读。如有问题请指出,我会积极采纳!
欢迎关注我的公众号【长路Java】,分享Java学习文章及相关资料
Q群:851968786 我们可以一起探讨学习
注明:转载可,需要附带上文章链接