module4-03-继承和函数进阶
一、继承相关
-
在代码量很多的时候,使用相对合适的继承方法,可以很好的优化代码节省代码量。如让公共部分提取出来,然后用组合继承来继承
1.1 对象之间的继承
(1)对象拷贝
-
使用for...in:父对象的属性拷贝给子对象
(2)原型继承属性
-
把公共部分提取出来作为一个构造函数,然后使单独的部分自己也作为一个构造函数,单独的那个构造函数使其原型对象等于new一个公共的构造函数,这样就都可以访问到公共部分了
-
缺点:
-
使得单独的构造函数的原型对象中constructor消失了,要重新赋予
-
参数只能传一次给原型对象
-
(3)构造函数继承
-
借用构造函数继承属性(call)
-
把公共部分提出取来封装在构造函数中,在单独的构造函数中使用call方法让公共的构造函数在单独构造函数的代码块中调用,把this作为参数传入
-
-
构造函数方法的继承
-
① (原型)对象拷贝继承
-
遍历父构造函数的原型对象,使用for in让子构造函数的原型对象上赋予相同的值(constructor除外)
-
-
② 原型继承
-
使子构造函数的原型对象等于new父构造函数,然后再添加constructor即可
-
-
(4)组合继承
-
组合继承即构造函数用call在子构造函数内部使用
-
然后原型对象等于new父构造函数来继承其方法
二、函数进阶
2.1 函数声明与函数表达式
(1)函数的定义方式
-
函数声明
-
函数表达式
-
new Function()
(2)函数声明与函数表达式的区别
-
函数声明
-
函数声明必须有名字
-
函数声明会函数提升(先提升函数后提升变量),在预解析阶段就已创建,声明前后都可以调用
-
-
函数表达式
-
函数表达式类似于变量赋值
-
函数表达式可以没有名字,例如匿名函数
-
函数表达式没有函数提升,在执行阶段创建,必须在表达式执行之后才可以调用
-
-
new Function()
-
使用Function构造函数来构建函数
-
传入的参数有位置要求且都要为字符串,最后一个参数为代码块,前面的都视为参数
-
这种创建函数的方法性能不优,不推荐
-
(3)不同方式的适用场景
-
大多数场景都推荐函数声明去创建函数
-
对于低版本浏览(如IE8)
-
会对if语句的模糊的块级作用域的概念里面,会让不执行的代码块中的函数声明的语句生效,并且会提升
-
这时候使用函数表达式可以兼容这一小问题
-
2.2 函数也是对象
-
从上面的new Function()创建方法知道,我们使用的函数都是Function的一个实例对象,那么就会有它的原型对象即Function.prototype
-
在下面发现一个问题即Function有一个属性__proto__指向的地址与其prototype相同.
-
即所有构造函数包括原生的都继承了Function.prototype包括其自身Function
-
2.3 函数的调用和this
-
this指向的是要联系执行的上下文
(1)普通函数
-
普通方式声明的函数,并且普通的调用
-
this指向为window
(2)构造函数
-
使用new关键字调用的函数
-
this指向为构造函数new出来的实例对象,内部指向的调用对象自己
(3)对象方法
-
对象方法中打点调用的函数
-
调用函数的对象
(4)事件函数
-
DOM元素监听事件绑定的函数
-
DOM元素
(5)定时器、延时器的函数
-
setTimeout、setInterval中作为参数的函数
-
指向为window
2.4 call、apply、bind方法介绍
-
这些方法存在于Function.prototype中,可用于函数的调用,如:普通的调用直接加上()可以,也可以打点call()调用
-
这样的话默认接受第一个参数是函数将来所在的作用域,也可以是一个具体的对象
var age = 20
var obj = { age: 18 }
function foo () {
console.log(this.age)
}
foo.call(obj) // 18
foo() // 20
(1)call方法
-
功能:可以指定函数的this,可以调用函数并传参
-
参数
-
① 指定的this,如果不传或者为null/undefined则会指向widnow
-
② 参数列表
-
-
返回值:函数自己的返回值
(2)apply方法
-
功能:可以指定函数的this,可以调用函数并传参
-
参数
-
① 指定的this,如果不传或者为null/undefined则会指向widnow
-
② 参数数组(将参数列表都放在一个数组中)
-
-
返回值:函数自己的返回值
(3)bind方法
-
功能:会创建并返回一个新函数,可以指定新函数的this,可以定义函数的参数,但是不调用
-
注意:
-
① 在一个bind返回的构造函数使用new调用的话,this还是会指向实例对象
-
② 普通function声明完之后不能立刻调用
-
-
参数
-
① 指定的this,如果不传或者为null/undefined则会指向widnow
-
② 参数列表
-
如果传进之后,这些参数会作为新函数的默认实参
-
-
-
返回值:改变了this指向的函数本身
2.5 call、apply、bind方法应用
(1)call方法应用
-
可以接用一些内置构造函数的原型对象的方法,用call来调用出来
-
比如为伪数组使用push
var obj = {
0: 'aaa',
1: 'bbb',
2: 'ccc'
}
;[].push.call(obj, 'ddd')
console.log(obj) // 多了个 4: 'ddd'
(2)apply方法应用
-
可以用apply第二个参数为参数数组的特性来让一个数组里面的值变成为参数数列进行传入
-
如:Math.max方法中传入的参数都是单个的,而我想找出一个数组的最大值
var arr = [1, 2, 3, 4, 5]
Math.max.apply(null, arr) // 5
(3)bind方法应用
-
在一些固定的this指向的函数调用如定时器/延时器/事件函数可以使用bind返回一个修改了this指向的函数
-
如1秒结束过后输出o对象的age
var o = { age: 18 }
setTimout(function () {
console.log(this.age)
}.bind(o), 1000)
2.6 函数的其他成员
-
除了arguments都需要打点调用,不然就会访问到(除了直接调用caller会报错外)创建这个函数的Finction.prototype原型对象中的属性,它里面也有这些属性与方法
(1)arguments
-
一个实参的伪数组
-
arguments.callee:指向函数自己本身
-
dir一个函数上面看始终都是null,如console.dir(fn)
(2)length
-
返回函数的形参数目
-
默认为0是因为访问了原型对象上的属性
(3)caller
-
返回函数的调用时候所处的作用域
-
全局范围内的话是null
(4)name
-
返回函数名,匿名函数为空字符串’‘
2.7 高阶函数
-
① 将函数作为参数
-
② 将函数作为返回值,可以生成闭包
2.8 闭包
(1)回顾作用域、作用域链、预解析
-
全局作用域
-
函数作用域
-
没有块级作用域
-
内层作用域可以访问外层作用域,反之不行
(2)什么是闭包
-
一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说闭包让你可以在一个内层函数中访问到其外层函数的作用域。在JS中,每当创建一个函数,闭包就会在函数创建的同时被创建出来
-
闭包定义时天生就能记住自己生成的作用域和函数自己,将他们形成一个密闭的环境,这就是闭包。不论函数以任何方式在任何地方进行调用,都会回到自己定义时的密闭环境进行执行
(3)闭包的理解和应用
-
案例1
function foo () {
var a = 1
return function () {
console.log(a)
}
}
var a = 2
var fn = foo()
fn() // 1
-
上面的代码块就是使用闭包而输出了foo函数内部的a,虽然全局变量也有,但根据作用域链会优先调用foo内部的
-
案例2
function foo () {
var a = 10
return function () {
console.log(++a)
}
}
var fn = foo()
fn() // 11
fn() // 12
fn() // 13
-
接著浏览器断点工具可以很好的反映出执行状态下的闭包是怎么样的(找到closure)
(4)闭包的问题
-
函数内部不会在声明的时候记住变量,调用的时候会访问外面的变量
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i)
}
}
arr[0]() // 10
arr[1]() // 10
arr[2]() // 10
-
使用闭包解决问题
var arr = []
for (var i = 0; i < 10; i++) {
;(function (i) {
arr[i] = function () {
console.log(i)
}
})(i);
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2