更新:慕课 快速解决前端技术一面 第五章
构造函数
- 构造函数命名使用大写字母开头。
- 所有的引用类型都有构造函数(对象、数组、函数)
- 所以精确判断 引用类型 时我们无法使用typeof,但可以通过
instanceof
判断她们的构造函数)
- 所以精确判断 引用类型 时我们无法使用typeof,但可以通过
- 例子:
- 解析:
- 构造函数Foo,第五行代码屏蔽与都不影响,因为默认就是把this这个对象返回给调用这个构造函数的对象。
当我们执行第7行代码时,实际先把Foo函数内的this变成了一个空对象,this再指向f,然后往Foo函数中传入两个参数赋值在this上,在Foo函数中完成绑定后再把this对象返回给f,可以得到f.name=’zhangsan’,f.age=20。
- 构造函数Foo,第五行代码屏蔽与都不影响,因为默认就是把this这个对象返回给调用这个构造函数的对象。
- 过程:
- 创建一个新对象f
- 构造函数Foo()的this指向这个新对象f(这里的this是个空对象)
- 执行Foo()函数代码(即降属性绑定在this上)
- 返回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的本质是函数(通过typeOf可判断),他是语法糖
- 继承:(例子见下)
- extends:子类继承父类,让子类获得父类的方法、属性
- super:子类extends父类后,需要在子类的构造函数中使用
super();
(可传参,相当于执行父类的构造函数) - 子类中扩展或重写方法
1 | // 父类 |
class与原型关系
- 原型关系:
- 每个class(本质是函数)都有显式原型
prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应class的prototype
- 子类的显示原型(
prototype
属性值为一个对象)的隐式原型 指向 父类的显式原型 - 父类的显示原型(
prototype
属性值为一个对象)的隐式原型 指向 Object(构造函数 )的显式原型
- 每个class(本质是函数)都有显式原型
class与原型链
- class实际上是函数
- class的显式原型可以理解为一个对象
- class与原型链图示:Student是子类,People是父类,子类的显式原型的隐式原型全等于父类的显式原型
- 子类的显式原型的隐式原型全等于父类的显式原型 图解:
- 例子:(代码在上面的“class和继承中”)
- **假设要访问xialuo.eat()**,则先在xialuo实例里找,找不到就到xialuo的隐式原型
__proto__
中找,即子类Stuent的显式原型中找,而里面只有sayHi()没有eat(),所以会继续到子类的Student的显式原型
的隐式原型__proto__
里找,即父类People的显式原型,成功找到eat() - 图示:class的显式原型 对象class的显式原型可以理解为一个对象,所以**
class的显式原型
的隐式原型 指向 Object的显式原型**
- **假设要访问xialuo.eat()**,则先在xialuo实例里找,找不到就到xialuo的隐式原型
原型规则(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()是存在的。
- **f中找不到toString()**,根据第五条规则,去f的
- 注意:
- Object是构造函数,不是对象!
- JS为了防止死循环,到
Object.prototype
(构造函数的显示类型)如果找不到再继续往下的话就是到null了
- 示意图:
instanceof
运算符
- 作用:判断引用类型(对象、数组、函数)属于哪个构造函数(Object()、Array()、Funtion())。【也就是让我们去找这个 引用类型 里有没有 构造函数的显示原型】
- 语法:
object instanceof constructor
- 参数:object(要检测的引用类型);constructor(某个构造函数)
- 返回值:测试它 左边的引用类型 是否是它 右边的构造函数的实例,返回 boolean 的数据类型。是则返回true,否则返回false。
- 注意:
1
2typeof null === 'object' //true
null instanceof Object //false - 例子:结合上一个例子,**
f instanceof Foo
的判断逻辑**是:- 判断f是否是Foo函数的实例,即判断f中是否能找到Foo函数的显示原型prototype。
- 根据原则,f中找不到该属性时,顺着f的
__proto__
一层一层往上,看看能否对应到Foo.prototype
- 找到了,返回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:
将此id用作参数调用构造函数Elem()创建一个对象div1,可打印出菜单栏的innerHTML,说明可以成功获取到div1的innerHTML:
调用div1的显式原型中的html()进行数据的替换,调用div1的显式原型中的on()进行事件函数的绑定,点击div1后弹出警示框:
补充:链式操作
因为我们的html()返回的是this,相当于返回了div1,所以可以链式操作:
1 | div1.html("<p>你好呀</p>").on("click", function () { |
描述new一个对象的过程
- 创建一个新对象f
- 构造函数Foo()的this指向这个新对象f(这里的this是个空对象)
- 执行Foo()函数代码(即对this进行赋值)
- 返回this对象给f(屏蔽第5行代码也是一样的效果,默认返回,此时this已经不是空对象了)
zepto(或其他框架)源码中如何使用原型链
- 阅读源码是高效提高技能的方式
- 但不能“埋头苦钻”有技巧在其中
- 慕课网搜索”zepto设计和源码分析”