JavaScript 中的函数相对于数据类型而言更加复杂,它可以有属性,也可以被赋值给一个变量,还可以作为参数被传递……
this 关键字
一般指向调用它的对象。
这句话其实有两层意思,首先 this 指向的应该是一个对象,更具体地说是函数执行的“上下文对象”。
其次这个对象指向的是“调用它”的对象,如果调用它的不是对象或对象不存在,则会指向全局对象(严格模式下为 undefined)。
1 | // 代码 1 |
1 | function fn() {console.log(this)} |
1 | function fn() {console.log(this)} |
1 | var dx = { |
1 | // ES6 下的 class 内部默认采用的是严格模式 |
1 | var arrow = {fn: () => { |
1 | var arrow = { |
1 | [0].forEach(function() {console.log(this)}, 0) // ? |
改变 this 指向的常见 3 种方式有 bind、call 和 apply。1
2
3
4
5
6
7function getName() {console.log(this.name)}
// bind 有些特殊,它不但可以绑定 this 指向也可以绑定函数参数并返回一个新的函数,当 c 调用新的函数时,绑定之后的 this 或参数将无法再被改变。
var b = getName.bind({name: 'bind'})
b()
// call 和 apply 用法功能基本类似,都是通过传入 this 指向的对象以及参数来调用函数。区别在于传参方式,前者为逐个参数传递,后者将参数放入一个数组,以数组的形式传递。
getName.call({name: 'call'})
getName.apply({name: 'apply'})
箭头函数
- 不绑定 arguments 对象,也就是说在箭头函数内访问 arguments 对象会报错;
- 不能用作构造器,也就是说不能通过关键字 new 来创建实例;
- 默认不会创建 prototype 原型属性;
- 不能用作 Generator() 函数,不能使用 yeild 关键字。
箭头函数的 this 指向定义是的上下文
函数的转换
编写一个 add() 函数,支持对多个参数求和以及多次调用求和。示例如下:1
2
3add(1) // 1
add(1)(2)// 3
add(1, 2)(3, 4, 5)(6) // 21
1 | function add(...args) { |
原型
函数其实也是一种特殊的对象1
2function fn(){}
fn instanceof Object // true
什么是原型和原型链?
简单地理解,原型就是对象的属性,包括被称为隐式原型的 __proto__ 属性和被称为显式原型的 prototype 属性。
隐式原型通常在创建实例的时候就会自动指向构造函数的显式原型。例如,在下面的示例代码中,当创建对象 a 时,a 的隐式原型会指向构造函数 Object() 的显式原型。1
2
3
4var a = {}
a.__proto__ === Object.prototype // true
var b= new Object()
b.__proto__ === a.__proto__ // true
显式原型是内置函数(比如 Date() 函数)的默认属性,在自定义函数时(箭头函数除外)也会默认生成,生成的显式原型对象只有一个属性 constructor ,该属性指向函数自身。通常配合 new 关键字一起使用,当通过 new 关键字创建函数实例时,会将实例的隐式原型指向构造函数的显式原型。
1 | function fn() {} |
1 | var parent = {code:'p',name:'parent'} |
在这个例子中,如果对象 parent 也没有属性 code,那么会继续在对象 parent 的原型对象中寻找属性 code,以此类推,逐个原型对象依次进行查找,直到找到属性 code 或原型对象没有指向时停止。
这种类似递归的链式查找机制被称作“原型链”。1
Function.prototype === Object.__proto__ //true
new 操作符实现了什么?
1 | function F(init) {} |
- 创建一个临时的空对象,为了表述方便,我们命名为 fn,让对象 fn 的隐式原型指向函数 F 的显式原型;
- 执行函数 F(),将 this 指向对象 fn,并传入参数 args,得到执行结果 result;
- 判断上一步的执行结果 result,如果 result 为非空对象,则返回 result,否则返回 fn。
1 | var fn = Object.create(F.prototype) |
怎么通过原型链实现多层继承?
1 | function A() { |
typeof 和 instanceof
typeof
用来获取一个值的类型,可能的结果有下面几种:
类型 | 结果 |
---|---|
Undefined | “undefined” |
Boolean | “boolean” |
Number | “number” |
BigInt | “bigint” |
String | “string” |
Symbol | “symbol” |
函数对象 | “function” |
其他对象及null | “object” |
instanceof
用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
作用域
作用域是指赋值、取值操作的执行范围,通过作用域机制可以有效地防止变量、函数的重复定义,以及控制它们的可访问性。
虽然在浏览器端和 Node.js 端作用域的处理有所不同,比如对于全局作用域,浏览器会自动将未主动声明的变量提升到全局作用域,而 Node.js 则需要显式的挂载到 global 对象上。又比如在 ES6 之前,浏览器不提供模块级别的作用域,而 Node.js 的 CommonJS 模块机制就提供了模块级别的作用域。但在类型上,可以分为全局作用域(window/global)、块级作用域(let、const、try/catch)、模块作用域(ES6 Module、CommonJS)及本课时重点讨论的函数作用域。
命名提升
1 | console.log(a) // undefined |
1 | f(); |
闭包
在函数内部访问外部函数作用域时就会产生闭包。闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这种关联不只是跨作用域引用,也可以实现数据与函数的隔离。1
2
3
4
5
6
7
8
9
10
11
12var SingleStudent = (function () {
function Student() {}
var _student;
return function () {
if (_student) return _student;
_student = new Student()
return _student;
}
}())
var s = new SingleStudent()
var s2 = new SingleStudent()
s === s2 // true
经典笔试题
1 | for( var i = 0; i < 5; i++ ) { |
1 | for(let i = 0; i < 5; i++ ) { |