theme: smartblue
前几天面试,突然被面试官问到ES6中class类
是如何实现的,我一脸懵逼,这不是类吗,难道让我说js底层如何实现的?这不瞎扯淡吗!
于是我问,我不懂js是用什么实现的,类的底层实现也不懂。
面试官说,其实ES6中的class是js语法糖,完全可以通过构造函数实现!
我一脸懵逼,构造函数??
面试官看我不解,说:其实就是function函数!class就是通过function实现的!
看来我基本功还差的很!当然,最后面试也没过。
记录一下ES6中class语法糖是怎么实现的吧!
构造函数的创建和使用
构造函数的创建
function Dog(){
//构造函数中的属性
this.name = "贝贝";
//构造函数中的方法
this.bark = function(){
console.log("汪汪汪");
}
}
- 构造函数一般首字母会大写,为了和普通函数区分
- 构造函数的属性和方法前必须加this关键字,指代要生成的对象
构造函数的调用
- new 就是执行构造函数,返回一个对象,不写new就是普通函数的调用,没有创造对象的能力
function Dog(){
}
var d1 = new Dog();
var d2 = Dog();
- 调用函数时,如果不传递参数,()可以不写,如果传递参数就必须写,建议都写上
function Dog(){
}
var d1 = new Dog();
var d2 = new Dog;
- 构造函数中的this,由于和 new 连用的关系,是指向当前实例对象的
function Dog(){
console.log(this);
}
var d1 = new Dog();// this => d1
var d2 = new Dog();// this => d2
构造函数的return
function Dog(){
this.name = "贝贝";
this.bark = function(){
console.log("汪汪汪");
}
// return 0;
// return [];
}
var d1 = new Dog();
console.log(d1);
- 不写return,返回一个对象
- return一个基本数据类型,结果不变,依旧返回一个对象
- return一个复杂数据类型,返回一个复杂数据类型
成员
实例成员
function Star(name){
this.name = name
this.age = age
this.sing = function(){
}
}
- 实例成员是构造函数内部通过this添加的成员
- 实例成员只能通过实例化的对象来访问
let ldh = new Star("刘德华")
ldh.sing()
- 不能通过构造函数来访问实例成员
Star().name //错误
静态成员
在构造函数本身添加的成员
Star.sex = "男"
静态成员只能通过构造函数来访问
Star.sex
构造函数和原型
原型对象prototype
- 构造函数通过原型分配的函数是所有对象所共享的
- js规定。每一个构造函数都有一个prototype属性,,指向另一个对象。prototype是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有
consloe.dir(Star)
- 我们可以将不变的方法,定义在prototype对象上,实现所有实例共享方法。
function Star(name){
this.name = name
this.age = age
}
Star.prototype.sing = function(){
}
一般情况下,公用属性放在构造函数里,公共方法放在原型对象上。
问:什么是原型?原型的作用?
答:一个对象,我们称prototype为原型对象。共享方法。
对象原型proto
对象都会有一个属性proto指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数原型对象的属性和方法,就是因为对象有proto存在
function Star(name){
this.name = name
this.age = age
}
Star.prototype.sing = function(){
}
let ldh = new Star("刘德华",18)
consloe.log(ldh)
对象身上会默认添加一个指向prototype原型对象的proto
ldh.__proto__ === Star.prototype //true
方法的查找规则:先在实例上查找,找不到后在原型链上查找
constructor函数
对象原型(proto) 和构造函数( prototype ) 原型对象里面都有一个constructor属性,constructo我们称为构造函数,因为它指向构造函数本身。
function Star(name){
this.name = name
this.age = age
}
Star.prototype.sing = function(){
}
let ldh = new Star("刘德华",18)
consloe.log(ldh.__proto__)
consloe.log(Star.prototype)
constructor主要用于记录该对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数。
很多情况下,我们需要手动利用constructor这个属性指回原来的构造函数
function Star(name){
this.name = name
this.age = age
}
//很多情况下,我们需要手动利用constructor这个属性指回原来的构造函数
Star.prototype.sing = function(){
consloe.log("唱歌")
}
Star.prototype.movie = function(){
consloe.log("电影")
}
let ldh = new Star("刘德华",18)
consloe.log(ldh.__proto__.constructor)
consloe.log(Star.prototype.constructor)
prototype上的方法比较多,我们可以用对象的形式写
function Star(name){
this.name = name
this.age = age
}
//很多情况下,我们需要手动利用constructor这个属性指回原来的构造函数
Star.prototype = {
sing:function(){
consloe.log("唱歌")
},
movie: function(){
consloe.log("电影")
}
}
let ldh = new Star("刘德华",18)
consloe.log(ldh.__proto__.constructor)
consloe.log(Star.prototype.constructor)
注意,此时prototype和proto的constructor指向都发生了改变!
这是因为,一开始的prototype是通过“.”方式添加内容,但是第二种方法是直接给prototype进行赋值,导致
导致此时prototype的constructor指向发生了改变!
(有Star构造函数变成了Object,constructor指向构造它的原型函数)
此时打印
consloe.log(ldh.__proto__)
consloe.log(Star.prototype)
也可以发现prototype的constructor方法已经被覆盖没有了。此时,我们需要手动利用constructor这个属性指回原来的构造函数
function Star(name){
this.name = name
this.age = age
}
//很多情况下,我们需要手动利用constructor这个属性指回原来的构造函数
Star.prototype = {
constructor:Star
sing:function(){
consloe.log("唱歌")
},
movie: function(){
consloe.log("电影")
}
}
构造函数、实例原型对象之间的关系
原型链
原型对象也有proto
consloe.log(Star.prototype)
通过construtor我们可以知道,Star.prototype.proto指向Object.Prototype
consloe.log(Star.prototype.__proto__ === Object.Prototype) //true
每个对象的proto就形成了原型链
js的成员查找机制
原型对象this指向
构造函数中,里面的this指向的是对象实例 ldh
拓展内置对象
示例:给数组增加自定义某种功能!
Array.prototype.sum = function(){
}
继承
ES6之前没有提供extends继承。我们通过构造函数 + 原型对象模拟实现继承,被称为组合继承。
call方法
1.调用函数;2.并且修改函数运行时的this指向
语法:function.call(thisArg, arg1, arg2, ...)
function eat(){
consloe.log("我想吃")
}
eat.call()
借用构造函数继承父类型属性
核心原理:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性
//父函数
function Father(name, price) {
//thi指向父构造函数的对象实例
this.name = name;
this.price = price;
}
//子构造函数
function Son(name, price) {
//thi指向子构造函数的对象实例
//将父构造函数当一个普通函数调用,并改变this指向
Father.call(this, name, price);
this.age = 18
}
借用构造函数继承父类型方法
使用Son.prototype = Father.prototype
//父函数
function Father(name, price) {
//thi指向父构造函数的对象实例
this.name = name;
this.price = price;
}
Father.prototype.money = function(){
console.log(111111)
}
//子构造函数
function Son(name, price) {
//thi指向子构造函数的对象实例
//将父构造函数当一个普通函数调用,并改变this指向
Father.call(this, name, price);
this.age = 18
}
Son.prototype = Father.prototype
Son.prototype.study = function(){
}
这样会导致父函数的原型对象也添加上study方法,修改子对象原型,父对象原型也会改变!这是不可行的!!!!!
我们使用下面的方法实现继承:
Son.ProtoType = new Father()
但注意,此时Son的ProtoType上的construtor已经被覆盖了,所以我们需要将constuctor指回原来的原型对象
Son.prototype.construtor = Son