一、构造函数
1.构造函数是一种特殊的函数,主要用来初始化对象
2.使用场景
常见的{...}语法允许创建一个对象。可以通过构造函数来快速创建多个类似的对象。
const Peppa = {
name: '佩奇',
age: 6,
sex: '女'
}
const George = {
name: '乔治',
age: 3,
sex: '男'
}
const Mum = {
name: '佩奇妈妈',
age: 30,
sex: '女'
}
const Dad = {
name: '佩奇爸爸',
age: 32,
sex: '男'
}
function Pig(name, age, sex) {
this,
name = name;
this.age = age;
this.sex = sex;
}
const Peppa1 = new Pig('佩奇', 6, '女')
const George1 = new Pig('乔治', 3, '男')
const Mum1 = new Pig('佩奇妈妈', 30, '女')
const Dad1 = new Pig('佩奇爸爸', 32, '男')
构造函数在技术上是常规函数
命名以大写字母开头。
通过new关键字来执行。
3.创建构造
使用new关键字调用函数的行为被称为实例化
实例化构造函数时没有参数可以省略()
构造函数内部无需写return,返回值即为新创建的对象
构造函数内部的return返回的值无效,所以不需要写return
new Object()和new Date()也是实例化的构造函数
4.实例化执行过程!
a.创建新空对象,
b.构造函数this指向新对象,
c.执行构造函数代码,修改this,添加新的属性,
d.返回新对象
5.实例成员和静态成员
实例成员:通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)
为构造函数传入参数,创建结构相同但值不同的对象
构造函数创建的实例对象彼此独立互不影响
//实例对象上的属性和方法属于实例成员
function Pig(name) {
this.name = name;
}
const peiqi = new Pig('佩奇')
const qiaozhi = new Pig('乔治')
peiqi.name = '小猪佩奇' //实例属性
peiqi.sayHi = () => { //实例方法
console.log('hi~');
}
console.log('peiqi', peiqi);
console.log('qiaozhi', qiaozhi);
构造函数的属性和方法称为静态成员(静态属性和静态成员)
静态成员只能通过构造函数来访问
静态方法中的this指向构造函数
//构造函数上的属性和方法称为静态成员
function Pig(name) {
this.name = name;
}
Pig.eyes = 2 //静态属性
console.log('Pig.eyes', Pig.eyes);
Pig.sayHi = function() { //静态方法
console.log(this);
}
console.log('Pig.sayHi', Pig.sayHi);
6.内置构造函数
引用类型Object,Array,RegExp,Date等
包装类型String,Number,Boolean等
Object的静态方法Object.keys(),Object.values(),Object.assign()
const o = {
name: '佩奇',
age: 6
}
//获取所有属性名,返回数组
console.log(Object.keys(o));
//获取所有属性值,返回数组
console.log(Object.values(o));
//拷贝对象
const oo = {}
Object.assign(oo, o)
//给对象添加属性
console.log(oo);
Object.assign(o, {
gender: '女'
})
console.log(o);
二、编程思想
1.面向过程
分析解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用。按照分析好了的步骤,按照步骤解决问题。
优点:性能比面向对象高,适合和硬件联系很紧密的东西
缺点:没有面向对象易维护、易复用、易扩展
2.面向对象
把食物分解成一个一个对象,然后由对象之间分工与工作。
以功能来划分问题,而不是步骤。
面向对象编程(oop):在面向对象程序开发中,每一个对象都是功能中心,具有明确分工。面向对象编程具有灵活,代码可复用,容易维护和开发的优点,更适合多人合作的大型软件项目。
优点:封装性,继承性,多态性。可以使系统更加灵活,更加易于维护
缺点:性能比面向过程低
js实现面向对象需要借助构造函数来实现
构造函数实例创建的对象彼此独立,互不影响。
构造函数存在浪费性能的问题
function Star(name, age, sex) {
this.name = name;
this.age = age;
this.sing = function() {
console.log('sing');
};
}
const ldh = new Star('ldh', 55)
const zxy = new Star('zxy', 58)
console.log(ldh.sing === zxy.sing); //false
三、原型
原型可以解决构造函数里浪费内存的问题。
1.目的:能够利用原型对象实现方法共享
构造函数通过原型分配的函数是所有对象所共享的。
js规定:每一个构造函数都有一个prototype属性,指向另一个对象,我们也称为原型对象。
这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存。
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
function Star(name, age, sex) {
//公共属性写到构造函数里
this.name = name;
this.age = age;
}
const ldh = new Star('ldh', 55)
const zxy = new Star('zxy', 58)
console.log('Star.prototype', Star.prototype)
//构造函数有一个prototype属性,公共方法写到原型对象身上
Star.prototype.sing = function() {
console.log('唱歌');
};
ldh.sing()
zxy.sing()
console.log(ldh.sing === zxy.sing); //true
构造函数和原型对象中的this都指向实例化对象。
let a;
let b;
function Star(name, age, sex) {
a = this;
this.name = name;
this.age = age;
}
const ldh = new Star('ldh', 55)
console.log(a === ldh);//true
//构造函数里面的 this就是实例对象
Star.prototype.sing = function() {
b = this;
console.log('唱歌');
};
ldh.sing()
console.log(b === ldh);//true
//自己定义数组扩展方法max,min,sum
Array.prototype.max = function() {
//原型函数里面的this指向实例化对象arr
return Math.max(...this)
}
Array.prototype.min = function() {
//原型函数里面的this指向实例化对象arr
return Math.min(...this)
}
Array.prototype.sum = function() {
//原型函数里面的this指向实例化对象arr
return this.reduce((sum, item) => sum + item, 0)
}
const arr = [1, 2, 3]
console.log(arr.max());
console.log(arr.min());
console.log(arr.sum());
2.constructor属性
每个原型对象里面都有一个constructor属性,该属性指向该原型对象的构造函数.
function Star() {}
const ldh = new Star()
console.log(Star.prototype.constructor === Star);//true
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。
但是这样就会覆盖构造函数原型对象原来的内容,修改后原型对象constructor就不再指向当前构造函数了,我们需要在修改后的原型对象中,添加一个constructor指向原来的构造函数。
function Star() {}
// Star.prototype.sing = function() {
// console.log('唱歌');
// }
// Star.prototype.dance = function() {
// console.log('跳');
// }
Star.prototype = {
//重新指回创造这个原型对象的构造函数
constructor: Star,
sing: function() {
console.log('唱歌');
},
dance: function() {
console.log('跳');
}
}
console.log(Star.prototype);
3.原型
在每个对象都会有一个__proto_指向构造函数的prototype原型对象,之所以我们可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto_原型存在。
__protp_是js非标准属性,[prototype]和__protp_意义相同
用来表明当前实例对象指向哪个原型对象prototype
__protp_对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数。
function Star() {}
const ldh = new Star()
//每一个构造函数都有一个prototype属性,指向另一个对象,我们也称为原型对象。
console.log('Star.prototype', Star.prototype);
//每个原型对象里面都有一个constructor属性,该属性指向该原型对象的构造函数.
console.log(Star.prototype.constructor === Star);//true
//ldh.__proto__ 指向该构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype); //true
//对象原型里面也有constructor属性,指向创建该实例对象的构造函数。
console.log(ldh.__proto__.constructor === Star); //true
总结
prototype是原型对象,构造函数都自动有原型对象
construvtor属性在原型对象(prototype)和对象原型(__proto__)上都有 ,指向创建实例对象/原型的构造函数。
__proto__属性在实例对象里面,指向原型对象。
4.原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,js中大多是借助原型对象实现继承的特性。
const Person = {
head: 1,
eyes: 2
}
function Woman() {}
//Woman通过原型来继承Person
Woman.prototype = Person
//指回原来的构造函数
Woman.prototype.constructor = Woman
const red = new Woman()
console.log('red', red);
问题:同时使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个都会改变。
const Person = {
head: 1,
eyes: 2
}
function Woman() {}
Woman.prototype = Person
Woman.prototype.constructor = Woman
Woman.prototype.sing = function() {
console.log('唱歌');
}
const red = new Woman()
console.log('red', red);
function Man() {}
Man.prototype = Person
Man.prototype.constructor = Man
const bob = new Man()
console.log('bob', bob);
function Person() {
this.head = 1
this.eyes = 2
}
function Woman() {}
//父构造函数(父类)子构造函数(子类) 子类的原型=new父类
Woman.prototype = new Person()
Woman.prototype.constructor = Woman
Woman.prototype.sing = function() {
console.log('唱歌');
}
const red = new Woman()
console.log('red', red);
function Man() {}
Man.prototype = new Person()
Man.prototype.constructor = Man
const bob = new Man()
console.log('bob', bob);
5.原型链
基于原型对象的继承使得不同的构造函数的原型对象关联在一起,并且这种关联的关系是一种链状结构,我们将原型对象的链状结构关系称为原型链。
function Person() {}
const ldh = new Person()
console.log(ldh.__proto__);
console.log(Person.prototype);
console.log(ldh.__proto__ === Person.prototype); //true
console.log(Person.prototype.__proto__ === Object.prototype); //true
console.log(Object.prototype.__proto__); //null
console.log(ldh instanceof Person); //true
console.log(ldh instanceof Object); //true
原型链查找规则
a.当访问一个对象的属性时,首先查找这个对象自身有没有该属性。
b.如果没有就查找它的原型,也就是__proto__指向的prototype原型对象。
c.如果没有找到就查找原型对象的原型。
d.依次类推一直找到Object为之。
e.__proto__对象原型的意义就在于为对象成员查找机制提供一个方向。
f.可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
四、示例
1.封装模态框
<!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>Document</title>
</head>
<body>
<button id="delete">删除</button>
<button id="login">登录</button>
</body>
</html>
<script>
//构造函数封装模态框
function Modal(title = '', message = '') {
// 1.创建div标签
// 2.给div标签添加类名modal
// 3.modal盒子内部填充2个div并且修改文字内容
this.modalBox = document.createElement('div')
this.modalBox.className = "modal"
this.modalBox.innerHTML = `<div class="header">${title}<i>x</i></div>
<div class="main">${message}</div>`
}
//挂载open方法
Modal.prototype.open = function() {
//准备open显示时先判断 页面中有没有modal,有就移除,没有就添加
const box = document.querySelector('.modal')
box && box.remove()
document.body.append(this.modalBox)
//等盒子显示出来,就可以绑定关闭事件
this.modalBox.querySelector('i').addEventListener('click', () => {
//需要用箭头函数,这个this指向实例对象
this.close()
})
}
Modal.prototype.close = function() {
this.modalBox.remove()
}
document.querySelector('#delete').addEventListener('click', () => {
const del = new Modal('温馨提示', '你没有权限删除')
del.open()
})
document.querySelector('#login').addEventListener('click', () => {
const login = new Modal('友情提示', '你没有注册')
login.open()
})
</script>
2.选项卡(构造函数写法)
<!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>Document</title>
<style>
#box div,
#box1 div {
width: 200px;
height: 50px;
background-color: palegreen;
display: none;
}
.active {
background-color: palevioletred;
}
#box .current,
#box1 .current {
display: block;
background-color: palegreen;
}
</style>
</head>
<body>
<div id="box">
<section>
<button class="active">娱乐</button>
<button>体育</button>
<button>教育</button>
</section>
<section>
<div class="current">娱乐</div>
<div>体育</div>
<div>教育</div>
</section>
</div>
<hr>
<div id="box1">
<section>
<button class="active">娱乐</button>
<button>体育</button>
<button>教育</button>
</section>
<section>
<div class="current">娱乐</div>
<div>体育</div>
<div>教育</div>
</section>
</div>
</body>
<script>
//构造函数
function Tab(ele, type) {
this.ele = document.querySelector(ele); // {ele:"元素"}
this.btns = this.ele.children[0].children
this.divs = this.ele.children[1].children
this.changeColor(type); // 函数的调用 "click"
}
Tab.prototype.changeColor = function(type) {
// this: 指向的是实例化对象的this
for (let i = 0; i < this.btns.length; i++) {
this.btns[i].addEventListener(type, () => {
console.log(2);
// this.btns[i]:i就可以用了,this.btns[i]代表具体点击的元素
for (var j = 0; j < this.btns.length; j++) {
this.btns[j].classList.remove("active")
this.divs[j].classList.remove("current")
}
this.btns[i].classList.add("active")
this.divs[i].classList.add("current")
})
}
}
// 创建对象
let tab1 = new Tab("#box", "click")
let tab2 = new Tab("#box1", "mouseover")
</script>
</html>
五、class类
从ES6开始,JavaScript引入了类(class
)的语法,可以用来创建构造函数。这种语法更加简洁和清晰。
//写法一: 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log("呵呵");
}
let p1 = new Person("zs", 10)
let p2 = new Person("zs", 10)
console.log(p1);
console.log(p1 == p2); // false
//写法二: class类的写法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
//相当于挂载在了构造函数的原型对象上, Person.prototype.say = function(){}
say() {
console.log("呵呵");
}
}
let p1 = new Person("zs", 10)
let p2 = new Person("zs", 10)
console.log(p1);
console.log(p1 == p2); // false
选项卡(class类写法)
<!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>Document</title>
<style>
#box div,
#box1 div {
width: 200px;
height: 50px;
background-color: palegreen;
display: none;
}
.active {
background-color: palevioletred;
}
#box .current,
#box1 .current {
display: block;
background-color: palegreen;
}
</style>
</head>
<body>
<div id="box">
<section>
<button class="active">娱乐</button>
<button>体育</button>
<button>教育</button>
</section>
<section>
<div class="current">娱乐</div>
<div>体育</div>
<div>教育</div>
</section>
</div>
<hr>
<div id="box1">
<section>
<button class="active">娱乐</button>
<button>体育</button>
<button>教育</button>
</section>
<section>
<div class="current">娱乐</div>
<div>体育</div>
<div>教育</div>
</section>
</div>
</body>
<script>
// class类
class Tab {
constructor(ele, type) {
this.ele = document.querySelector(ele); // {ele:"元素"}
this.btns = this.ele.children[0].children
this.divs = this.ele.children[1].children
this.changeColor(type); // 函数的调用 "click"
}
changeColor(type) {
for (let i = 0; i < this.btns.length; i++) {
this.btns[i].addEventListener(type, () => {
console.log(2);
// this.btns[i]:i就可以用了,this.btns[i]代表具体点击的元素
for (var j = 0; j < this.btns.length; j++) {
this.btns[j].classList.remove("active")
this.divs[j].classList.remove("current")
}
this.btns[i].classList.add("active")
this.divs[i].classList.add("current")
})
}
}
}
let tab1 = new Tab("#box", "click")
let tab2 = new Tab("#box1", "mouseover")
</script>
</html>
六、继承
发生在两个构造函数之间,如果说A构造函数使用了B构造函数中的属性或者方法,A继承自B,B就属于父类,A属于子类
可以把构造函数当成一个类
1.借用父类构造函数继承
优点:可以继承父类构造函数内的属性和方法 缺点:不可以继承父类原型对象上的属性和方法
// 定义父类构造函数
function Father(name, age) {
this.name = name;
this.age = age;
}
// 父类原型方法
Father.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
// 定义子类构造函数
function Child(name, age, grade) {
// 借用父类构造函数
Father.call(this, name, age); // 通过call()借用父类构造函数
this.grade = grade; // 子类特有的属性
}
// 使用示例
const child = new Child('Alice', 10, 5); //{name: 'Alice', age: 10, grade: 5}
child.sayHello(); // child.sayHello is not a function
2.原型链继承
优点:可以继承父类原型对象上的属性和方法 缺点:不可以继承父类构造函数内的属性和方法
// Father类
function Father(money) {
this.money = money
}
Father.prototype.smoke = function() {
console.log("吸烟");
}
// Son类
function Son(name, money) {
this.name = name;
}
Son.prototype = new Father(); // 会覆盖掉Son构造函数原型对象的constructor属性
Son.prototype.constructor = Son; // 手动加上
Son.prototype.study = function() {
console.log("study");
}
var f = new Father(10000)
var s = new Son("小张")
console.log(s);
s.smoke();
3.组合继承
优点:实现函数复用,保证每个实例之间的属性不会相互影响。
缺点:使用了两次继承,代码书写繁琐,还出现了constructor的覆盖
// Father类
function Father(money) {
this.money = money
}
Father.prototype.smoke = function() {
console.log("吸烟");
}
// Son类
function Son(name, money) {
this.name = name;
Father.call(this, money); // 继承父类属性的关键
}
Son.prototype = new Father(); // 继承父类原型对象方法的关键
Son.prototype.study = function() {
console.log("study");
}
var s = new Son("小张", 1000); // 即继承了属性
console.log(s);
s.smoke(); // 又继承了父类原型对象上的方法
4.es6继承:extends super
优点:1.既可以继承父类的属性,也可以继承父类原型对象上的方法 2.写法简单
class Father {
constructor(money) {
this.money = money;
}
smoke() {
console.log('抽大烟');
}
}
class Son extends Father {
constructor(name, money) {
super(money);
this.name = name;
}
}
// extends来继承父类的属性和方法,必须还需要使用 super()访问父类的构造器(构造器的代码走一遍)
var son = new Son("小明", 10000)
console.log(son);
son.smoke()
// 既可以继承父类的属性,也可以继承父类原型对象上的方法