JS class和继承 原型和原型链

更新:慕课 快速解决前端技术一面 第五章

构造函数

  • 构造函数命名使用大写字母开头
  • 所有的引用类型都有构造函数(对象、数组、函数)
    • 所以精确判断 引用类型 时我们无法使用typeof,但可以通过 instanceof 判断她们的构造函数
  • 例子例子
  • 解析
    • 构造函数Foo,第五行代码屏蔽与都不影响,因为默认就是把this这个对象返回给调用这个构造函数的对象
      当我们执行第7行代码时,实际先把Foo函数内的this变成了一个空对象,this再指向f,然后往Foo函数中传入两个参数赋值在this上,在Foo函数中完成绑定后再把this对象返回给f,可以得到f.name=’zhangsan’,f.age=20。
  • 过程
    1. 创建一个新对象f
    2. 构造函数Foo()的this指向这个新对象f(这里的this是个空对象)
    3. 执行Foo()函数代码(即降属性绑定在this上)
    4. 返回this对象给f(屏蔽第5行代码也是一样的效果,默认返回,此时this已经不是空对象了)

扩展

  • vara ={}其实是var a = new Object()的语法糖
  • vara=[]其实是var a = new Array()的语法糖
  • function Foo(){...}其实是var Foo = new Function(){...}的语法糖
  • 可以使用instanceof判断 引用类型(数组、对象、函数) 属于哪个 构造函数。

判断一个变量是否为“数组”变量 instanceof Array


class和继承

  • ES6 class
  • class:class的例子
  • class的本质是函数(通过typeOf可判断),他是语法糖
  • 继承:(例子见下)
    • extends:子类继承父类,让子类获得父类的方法、属性
    • super:子类extends父类后,需要在子类的构造函数中使用super();(可传参,相当于执行父类的构造函数)
    • 子类中扩展或重写方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 父类
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}

// 子类
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi() {
console.log(`姓名 ${this.name} 学号 ${this.number}`)
}
eat() {
console.log(`${this.name} 爱吃油炸食品`)
}
}

// 子类
class Teacher extends People {
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name} 教授 ${this.major}`)
}
}

// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()

// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()

class与原型关系

  • 原型关系
    • 每个class(本质是函数)都有显式原型prototype
    • 每个实例都有隐式原型__proto__
    • 实例的__proto__指向对应class的prototype
    • 子类的显示原型(prototype属性值为一个对象)的隐式原型 指向 父类的显式原型
    • 父类的显示原型(prototype属性值为一个对象)的隐式原型 指向 Object(构造函数 )的显式原型

class的本质是函数
原型与原型链的图示(代码结合上面的例子)

class与原型链

  • class实际上是函数
  • class的显式原型可以理解为一个对象
  • class与原型链图示:class与原型链Student是子类,People是父类,子类的显式原型的隐式原型全等于父类的显式原型
  • 子类的显式原型的隐式原型全等于父类的显式原型 图解:子类的显式原型的隐式原型全等于父类的显式原型 图解
  • 例子:(代码在上面的“class和继承中”)
    • **假设要访问xialuo.eat()**,则先在xialuo实例里找,找不到就到xialuo的隐式原型__proto__中找,即子类Stuent的显式原型中找,而里面只有sayHi()没有eat(),所以会继续到子类的Student的显式原型的隐式原型__proto__里找,即父类People的显式原型,成功找到eat()
    • 图示:class的显式原型 对象class的显式原型可以理解为一个对象,所以**class的显式原型的隐式原型 指向 Object的显式原型**

原型规则(5条)

原型规则是学习原型链的基础

引用类型的自由扩展属性

所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性
例子

引用类型的__proto__属性(隐式原型)

所有的引用类型(数组、对象、函数),都有一个__proto__属性(隐式原型),属性值是一个普通的对象
例子

函数的prototype属性(显式原型)

所有的函数都有一个prototype属性(显式原型),属性值也是一个普通的对象。
【注意:数组、对象没有prototype属性】
例子

__proto__prototype属性值

所有的引用类型(数组、对象、函数)的__proto__属性值 都指向它的 构造函数的prototype属性值。
“指向”即“全等”__proto__属性值===它的构造函数的prototype属性值】
例子

第五条原型规则

当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__属性(即它的构造函数的prototype属性)中寻找

例子
例子

解析
看第15行代码,f是通过构造函数Foo创建的对象,f只有第10行添加的printName属性,没有alertName属性,根据第五条原则,f会去它的__proto__属性(即它的构造函数Foo函数的prototype属性)中寻找,找到第5行的alert(this.name),**弹出’zhangsan’**。

注意:第6行的this也是指向对象f,14行打印出的也是’zhangsan’。


hasOwnProperty()判断原型属性

例子
使用for in循环对象属性时,我们希望避开他来自原型的属性,虽然现在高级浏览器上已经回屏蔽,但为了保证 程序健壮性 还是建议大家加上这个判断,能在第6行打印的就是来自原型的属性。


原型链

  • 例子例子
  • 解析
    • **f中找不到toString()**,根据第五条规则,去f的__proto__属性(即f的构造函数Foo()的prototype属性)中寻找,也没找到。
    • 注意:原型规则中提到**__proto__属性值是一个普通的对象,又因为每个对象都有一个__proto__属性**,所以我们去f.__proto__(一个对象)的__proto__中查找。
    • 根据第四条原则,我们知道 对象的__proto__ 全等于 它的构造函数(Object)的prototype属性值,也就是说要去Object的prototype属性值 中找toString(),Object.prototype.toString()是存在的。
  • 注意
    1. Object是构造函数,不是对象!
    2. JS为了防止死循环,到Object.prototype(构造函数的显示类型)如果找不到再继续往下的话就是到null了
  • 示意图示意图

instanceof运算符

  • 作用:判断引用类型(对象、数组、函数)属于哪个构造函数(Object()、Array()、Funtion())。【也就是让我们去找这个 引用类型 里有没有 构造函数的显示原型
  • 语法:object instanceof constructor
  • 参数:object(要检测的引用类型);constructor(某个构造函数)
  • 返回值:测试它 左边的引用类型 是否是它 右边的构造函数的实例,返回 boolean 的数据类型。是则返回true,否则返回false
  • 注意
    1
    2
    typeof null === 'object' //true
    null instanceof Object //false
  • 例子:结合上一个例子,**f instanceof Foo的判断逻辑**是:
    1. 判断f是否是Foo函数的实例,即判断f中是否能找到Foo函数的显示原型prototype
    2. 根据原则,f中找不到该属性时,顺着f的__proto__一层一层往上,看看能否对应到Foo.prototype
    3. 找到了,返回true

题目解答

如何准确判断一个变量是数组类型(typeof肯定不行)

  • 注意:数组通过instanceof到Array或Object都返回true
  • 使用Array.isArray(arrName)
    答案

写一个原型链继承的例子

基础例子
基础例子
1-12:创建两个构造函数Animal()、Dog():
13:将Dog的显示类型prototype更改为Animal()的一个对象:
15:new一个Dog()的对象hashiqi,此时hashiqi不仅有Dog()的属性bark,还有一个继承自Dog.prototype(即Animal()的一个对象)的eat属性
【eat属性 就来自原型链继承。】

【面试千万别写这么简单,写下面的例子】

封装DOM查询的例子

  • 功能:获取DOM节点的内容并给节点绑定一个事件。
  • 原理:通过扩展原型的方式来:根据id获取一个DOM节点,扩展Elem的显式原型方法 html(),调用html()可改变/返回节点的innerHTML。再扩展一个原型方法on(),调用on()可绑定一个事件函数。
  • 测试方法:先通过不传参的html()测试是否能成功获取该id的DOM节点的innerHTML,可以则传参改变该innerHTML,并使用on()绑定click函数用于点击节点后弹出警示框。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //根据传入的id获取DOM元素elem
    function Elem(id) {
    //this.elem是获取到的对象
    this.elem = document.getElementById(id);
    }

    // 修改Elem的显式原型,调用html()可改/返innerHTML
    Elem.prototype.html = function (val) {
    var elem = this.elem;//绑定elem
    //如果有传参就用参数替换掉获取到的DOM元素的innerHTML
    if (val) {
    elem.innerHTML = val;
    // 可不反回this,返回是为了实现链式操作
    return this;
    } else {
    return elem.innerHTML;
    }
    }

    // 调用on()可绑定事件函数,type是函数名,fn是函数
    Elem.prototype.on = function (type, fn) {
    var elem = this.elem;
    elem.addEventListener(type, fn);
    }

    var div1 = new Elem("main-navigation");
    // console.log(div1.html());说明能够获取innerHTML
    div1.html("<p>你好呀</p>");
    div1.on("click", function () {
    alert("你点击了我!") ;
    })

【关于“addEventListener”可参考笔记DOM基础的添加事件句柄。要区别于xx.onclick=function(){函数名(参数);}

测试:打开一个网页,找到菜单栏对应的id
测试1
此id用作参数调用构造函数Elem()创建一个对象div1,可打印出菜单栏的innerHTML,说明可以成功获取到div1的innerHTML
测试2
调用div1的显式原型中的html()进行数据的替换,调用div1的显式原型中的on()进行事件函数的绑定,点击div1后弹出警示框:
测试3

补充:链式操作

因为我们的html()返回的是this,相当于返回了div1,所以可以链式操作:

1
2
3
div1.html("<p>你好呀</p>").on("click", function () {
alert("你点击了我!");
})

描述new一个对象的过程

答案

  1. 创建一个新对象f
  2. 构造函数Foo()的this指向这个新对象f(这里的this是个空对象)
  3. 执行Foo()函数代码(即对this进行赋值)
  4. 返回this对象给f(屏蔽第5行代码也是一样的效果,默认返回,此时this已经不是空对象了)

zepto(或其他框架)源码中如何使用原型链

  • 阅读源码是高效提高技能的方式
  • 但不能“埋头苦钻”有技巧在其中
  • 慕课网搜索”zepto设计和源码分析”