文章目录
- 一、概述
- 1.2 构造函数
- 二、静态成员 & 实例成员
- 2.1 实例成员
- 2.2 静态成员
- 三、原型
- 3.1对象原型
- 3.2 原型的 constructor构造函数
- 3.3 原型链
- 3.4 原型对象的应用
- 3.5 总结
- 四、继承
- 2.1 call方法
- 2.2 利用父构造函数实现继承
- 2.3 class 类的本质
一、概述
在典型的OOP的语言中(如Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前,JS中并没用引入类的概念。
ES6,全称 ECMAScript6.0,2015.06发版。但是目前浏览器的 JavaScript是ES5版本,大多数高版本的浏览器也支持ES6,不过只实现了ES6的部分特性和功能。
在ES6之前,对象不是基于类创建的,而是用一种称为 构建函数 的特殊函数来定义对象和它们的特征。
这里提一下创建对象的三种方法:
// 1. 利用 new Object()创建对象
let obj1 = new Object();
// 2. 利用对象字面量创建对象
let obj2 = {}
// 3. 利用构建函数创造对象
function Star(uname, age) {
this.uname = uname
this.age = age
this.sing = function () {
console.log('我会唱歌');
}
}
let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 20)
console.log(ldh); // {uname: "刘德华", age: 18, sing: ƒ}
ldh.sing() //我会唱歌
zxy.sing() //我会唱歌
1.2 构造函数
构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我
们可以把对象中一些公共的属性和方法抽取出来,然后封裝到这个函数里面
在JS中,使用构造函数时要注意以下两点:
- 构造函数用于创建某一类对象,其首字母要大写
- 构造函数要和new一起使用才有意义
new在执行时会做四件事情:
- 在内存中创建一个新的空对象。
- 让this指向这个新的对象。
- 执行构造函数里面的代码,给这个新对象派加属性和方法。
- 返回这个新对象(所以构造函数里面不需要 return)。
二、静态成员 & 实例成员
构造函数中的属性和方法我们成为成员,成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
console.log(ldh.uname); //刘德华
ldh.sing() //我会唱歌
console.log(Star.uname);//undefined 不能通过构造函数访问
2.1 实例成员
实例成员就是构造函数内部通过this添加的成员, 在上述代码中 uname age sing 就是实例成员
实例成员只能通过实例化的对象来访问
2.2 静态成员
在构造函数本身上添加静态成员
Star.sex = "男" //添加静态成员
静态成员只能通过构造函数访问
console.log(Star.sex); //男
console.log(ldh.sex); // undefine
三、原型
new
构造函数的时候,复杂数据类型是重新开辟新的内存空间,会浪费内存和降低运行速度。
请看下面示例:
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 2)
console.log(ldh.sing === zxy.sing); // false
通过 ===
符号,我们可以知道 这两个方法的内存地址是不一样的!
那么 在ES6出来之前,是怎么实现继承的同时又解决掉这个问题的呢?
答案是:原型!
构造函数通过原型分配的函数是所有对象所共享的。
Javascript规定,每一个构造函数都有一个 prototype属性,指向另一个对象。
注意这个 prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype对象上,这样所有对象的实例就可以共享这些方法
接下来改进一下我们的 Star
构造函数:
function Star(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function () {
// console.log('我会唱歌');
// }
}
Star.prototype.sing = function (){
console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 2)
console.log(ldh.sing === zxy.sing); // true
问答?
1.原型是什么?
一个对象,我们也称为 prototype为原型对象
2.原型的作用是什么
共享方法
总结: 一般情況下,我们的公共属性定义到构造函数里面,公共的方法我们放到原型对象身上
3.1对象原型
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function (){
console.log('我会唱歌');
}
let ldh = new Star('刘德华', 18)
对象都会有一个属性 __proto__ 指向构造函数的 prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有 proto際型的存在。
console.log(Star.prototype === ldh.__proto__); //true
console.log(Star.prototype.sing === ldh.__proto__.sing);//
console.log(ldh.sing === ldh.__proto__.sing);//true
个人总结就是:实例对象的 __proto__ 指向(等于) 构造函数的 prototype
注意,方法的查找规则:
- 首先先看ldh对象身上是否有sing方法,如果有就执行这个对象上的sing
- 如果没有sing这个方法,因为有 proto的存在,就去构造函数原型对象 prototype身上去查找sing这个方
3.2 原型的 constructor构造函数
对象原型(__proto__)和构造函数( prototype)原型对象里面都有一个属性 constructor属性, constructor我们称为构造函数,因为它指回构造函数本身。
constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
重新指回原来的构造函数:
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype = {
// 手动指回原来的构造函数
constructor: Star,
sing: function () {
console.log('我会唱歌');
},
movie: function () {
console.log("我会演电影");
},
}
let ldh = new Star('刘德华', 18)
console.log(ldh.constructor === ldh.__proto__.constructor); // true
console.log(ldh.constructor === Star.prototype.constructor); // true
铁三角:
3.3 原型链
示例代码:
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// prototype是一个对象,默认是带有constructor的,但是使用赋值操作后把对象覆盖了
Star.prototype = {
// 手动指回原来的构造函数
constructor: Star,
sing: function () {
console.log('我会唱歌');
},
movie: function () {
console.log("我会演电影");
},
}
//1.只要是对象就有 proto_原型,指向原型对象
console.log(Star.prototype.__proto__ === Object.prototype); //true
//2.我们Star原型对象里面的_ proto_原型指向的是 object. prototype
// 3. 我们Object.prototype原型对象里边的 __proto__原型 指向 null
console.log(Object.prototype.__proto__); // null
Object.prototype.gogo = function (){
console.log("gogo");
}
let ldh = new Star("刘德华",18)
console.log(ldh.__proto__.__proto__.gogo === Object.prototype.gogo); // true
object就是所有类的父类,上面没有了
3.4 原型对象的应用
// 原型对象的应用 扩展内置对象方法
Array.prototype.sum = function () {
let sum = 0
for (let i = 0; i < this.length; i++) {
sum += this[i]
}
return sum
}
console.log(Array.prototype);
let arr = [1,2,3,4,5,6,7,8,9]
console.log(arr.sum());//45
注意:数组和字符串内置对象不能给原型对象覆盖操作
Array prototype = {…} //错误示例
只能是
Array.prototype.xxx= function(){…}
的方式
3.5 总结
- 构造函数有原型对象 prototype
- 构造函数原型对象 prototype里面有 constructor指向构造函数本身
- 构造函数可以通过原型对象添加方法
- 构造函数创建的实例对象有 __proto__原型指向构造函数的原型对象
四、继承
ES6之前并没有给我们提供 extends继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
2.1 call方法
调用这个函数并且修改函数运行时的this指向
fun. call(thisArg,arg1,arg2, …)
thisArg:当前调用函数this的指向对象
arg1,arg2:传递的其他参数
示例代码:
function fn(x, y) {
console.log("我想喝咖啡");
console.log(this);
console.log(x + y);
}
let obj = {
name: "andy"
}
// call方法
// 1.call方法可以调用方法
fn.call() // this => window
// 2.call() 可以修改函数的 this
fn.call(obj, 2, 3) // this => obj result:5
2.2 利用父构造函数实现继承
这里能实现继承的核心就是,call 方法改变了this 指向。
// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname,age){
// this指向对象实例
this.uname = uname
this.age = age
}
// 2.子构造函数
function Son(uname,age,score){
// this 指向子构造函数的对象实例
Father.call(this,uname,age)
this.score = score
}
let son = new Son("刘德华",18,99)
console.log(son); //Son {uname: "刘德华", age: 18, score: 99}
但是这么做的话,只能继承属性,没办法继承方法,请看示例:
// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname, age) {
// this指向对象实例
this.uname = uname
this.age = age
}
Father.prototype.money= function (){
console.log("挣钱了!!");
}
// 2.子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age)
this.score = score
}
let son = new Son("刘德华", 18, 99)
console.log(son)
在控制台我们可以看到,子构造函数并没有 父构造函数里的 money 方法
那怎么才能实现功能齐全的继承呢,请看下面示例 :
核心是: Son.prototype = new Father()
// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname, age) {
// this指向对象实例
this.uname = uname
this.age = age
}
Father.prototype.money = function (){
console.log("挣钱了!!");
}
// 2.子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age)
this.score = score
}
Son.prototype = new Father()
let son = new Son("刘德华", 18, 99)
console.log(son); //Son {uname: "刘德华", age: 18, score: 99}
其原理是:
我们再来看一下控制台:
这么做基本就是实现了我们的继承,但是还是有一个小问题,因为我们把Son.prototype
给覆盖了,那么son.prototype.constructor
就指向了父构造函数的 constructor
,因此,我们需要把它手动修正过来。
最终格式:
// 利用父构造函数继承属性
// 1.父构造函数
function Father(uname, age) {
// this指向对象实例
this.uname = uname
this.age = age
}
Father.prototype.money = function () {
console.log("挣钱了!!");
}
// 2.子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age)
this.score = score
}
// 利用赋值的形式覆盖了 Son的prototype,所以要把constructor手动修改回来,不然指向的是Father的constructor
Son.prototype = new Father()
Son.prototype.constructor = Son
let son = new Son("刘德华", 18, 99)
console.log(son);
2.3 class 类的本质
// 类的本质
// ES6 通过类 实现 面向对编程
class Star {
}
console.log(typeof Star); // function
// 类的本质其实还是一个函数,我们呢也可以简单的认为类就是构造函数的另外一种写法
// 1.类和构造函数都有原型对象
console.log(Star.prototype); // {constructor: ƒ}
// 2.类和构造函数的原型对象里边都有一个constructor,它指向类或构造函数本身
console.log(Star.prototype.constructor === Star); // true
// 3.类和构造函数都可以通过原型添加方法
Star.prototype.sing = function () {
console.log("我会唱歌");
}
let ldh = new Star()
ldh.sing() //我会唱歌
// 4.类和构造函数创建的实例对象都有 __proto__,它指向类或构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype); // true
// 所以:ES6的类 实际上就是 语法糖
- 类和构造函数都有原型对象
- 类和构造函数的原型对象里边都有一个constructor,它指向类或构造函数本身
- 类和构造函数都可以通过原型添加方法
- 类和构造函数创建的实例对象都有 proto,它指向类或构造函数的原型对象
所以:ES6的类 实际上就是 语法糖
ES6的类它的绝大部分功能,ES5都可以做到,新的 class 写法只是让对象的原型写法更加清晰、更像面向对象编程的语法而已。
补充 :语法糖就是一种便捷写法。简单理解,有两种方法可以实现同样的功能但是一种写法更加清晰、方便那么这个方法就是语法糖