脑图
目录
1 创建
一般有三种函数的创建模式
1.1 函数声明
/* 如果有变量声明变量 */
function fun_name(a,b){
/* code */
} // 注意这里无分号
1.2 函数表达式
const func_name = function(a,b){
}
1.3 构造函数
我们一般不使用构造函数来创建
// 函数体为字符串
const func_name = new Function("console.log('hello world');")
func_name()
输出
hello world
2 调用
直接使用函数名 + 括号进行调用
2.1 语法
func_name()
调用之后,程序会直接执行函数体内的代码
2.2 参数
掉用的时候我们可以直接传递参数
-
参数可以是任意数据类型,所以必要的时候我们需要对参数进行检查
-
传递的参数多于定义的参数,多余的参数会被忽略
-
传递的参数少于定义的参数,运算的时候会经过饮食类型转换
function sum(a,b){
return a + b
}
console.log(sum(1,2,3))
console.log(sum(1))
输出
3
NaN
3 构造函数
构造函数其实与普通函数没有什么太大的区别,当使用方式不同的时候,功能就不同
- 我们一般习惯的将构造函数名的首字母大写(当然你不大写也是可以的)
- 调用方式决定了他是普通函数还是构造函数
- 普通函数调用:
func_name()
- 构造函数调用:
new Func_name()
- 普通函数调用:
3.1 简介
我们所创建的对象和函数等引用类型都会调用构造函数,比如
var arr = []
是var arr = new Array()
的语法糖var obj = {}
是var obb = new Object()
的语法糖- ……
3.2 构造函数的执行流程
因为构造函数的执行需要new
关键字,我们关注构造函数的执行流程,其实就是关注new
关键字的作用
- 出现
new
关键字,会直接在对堆存中开辟一片空间,新建一个空对象 - 将空对象的
__proto__
指向构造函数的原型 - 将构造函数的
this
,通过apply
指向空对象(新建的空对象设置为函数中的this
) - 返回该对象(返回实例对象)
使用伪代码来模拟new
操作符
对于new
函数的要求
- 传递参数的时候要传递构造函数的名字,eg:
_new(Person,'Jerry',18)
- 可以只用
arguments
来获取参数
function _new (){
// 拿到构造函数的名称
const Func = [].shift.call(arguments)
// 1. 出现new关键字,会直接在对堆存中开辟一片空间,新建一个空对象
const obj = {}
// 2. 将空对象的`__proto__`指向构造函数的原型
obj.__proto__ = Func.prototype
// 3. 将构造函数的`this`,通过`apply`指向空对象
Func.call(obj)
// 4. 返回新创建的对象
return obj
}
测试
function Person(name,age){
this.name = name
this.age = age
}
const person = _new(Person,"Jerry",18)
console.log(person)
输出
优化
const obj = {}
他其实就是new Object()
这里也用到了new
,这样似乎不太好
const obj = {}
obj.__proto__ = Func.prototype
// 我们可以把上两句改成
const obj = Object.create(Func.prototype)//同样表示创建一个以Func.prototype为原形的空对象
3.3 构造函数与普通函数的区别
调用方式
- 构造函数需要用
new Func_name(args)
来调用 - 普通函数用
func_name(args)
调用
返回值
- 构造函数会将创建的空对象
this
返回 - 普通函数没有返回值,如果想要返回,必须使用
return
关键字
4 arguments
arguments
他获取的是调用函数的时候传递的参数,并把这种参数封装成了一个伪数组,对于伪数组调用数组的方法 点击
function sum(){
console.log(arguments)
}
sum(1,2,3,4)
输出
4.1 callee
的使用
由上面的输出图我们发现了在 arguments
中有一个属性 callee
,callee
执行被调用的函数,也就是 sum
本身
function sum(){
console.log(arguments.callee) // 指向sum本身
arguments.callee()// 递归调用sum()
}
sum(1,2,3,4)
5 构造函数与ES6 class
ES6的语法大多数都是语法糖,当然 class
也是构造函数的语法糖
5.1 对于构造函数优化
当我们向构造函数中添加方法,我们每使用 new
创建一个对象,就会在堆内存中开辟一块空间去存储这个方法,所以对于方法的处理我们一般写成下面的样式
function Person (name,age){
this.name = name
this.age = age
/* this.sayName = function(){
console.log(this.name)
} */
}
Person.prototype.sayName = function(){
console.log(this.name)
}
把方法添加到原型中,避免上面的缺陷,他的实例都会拥有这个方法
5.2 类的使用
因为类是构造函数的语法糖,使用类我们可以很方便的实现对于构造函数的优化,节省空间
class Person {
constructor(name,age) {
this.name = name
this.age = age
console.log(arguments)
}
sayName(){ // 类的方法,不要加上function关键字
console.log(this.name)
console.log(this)
}
}
const person = new Person("Jerry",18)
person.sayName()
输出
通过上面的例子我们就发现,他依旧具有构造函数的所有的特性
5.3 类的说明
constructor
属于构造器,用来接收参数this
就相当于构造函数中的this
,他所指向的是实例对象- 在类中添加的方法会直接添加到原型上,注意不要使用
function
关键字 - 构造函数可以当做普通函数使用,但是类不可以,他就是构造函数的语法糖
JS在执行的时候,也会把类转化为构造函数来执行,而类中的extends
,也是为了实现原型链上面的继承
6 原型与原型链
每一个函数都有原型对象,每一个函数都有 prototype
属性
6.1 原型对象
- 函数的
prototype
属性会指向一个空对象,这个对象就是原型对象 - 原型对象 中有一个
constructor
属性,这个属性反过来指向函数
function fn(){
console.log('hello world')
}
const proto = fn.prototype
console.log(typeof proto) // object
console.log(proto) // {constructor: ƒ}
console.log(proto.constructor) //fn constructor指向函数
proto.constructor()//通过原型调用函数
输出
原型对象图示
6.2 原型对象的使用
我们一般往原型对象中添加方法,这样他的实例都会自动拥有添加的方法(上面构造函数的优化)
Person.prototype.func_name = function(){ code }
6.3 原型的分类
原型分为显式原形和隐式原形
显式原形
- 每个函数都有一个显式原型,即
prototype
- 在定义函数的时候自动添加的,默认是一个空的
Object
对象 - 没有链
隐式原形
- 每一个 实例对象 都有一个隐式原型,即
__proto__
- 创建对象的时候自动添加,默认值为构造函数的
prototype
的值 - 我们所说的原型链,其实就是隐式原型形成的链
我们一般操作显式原型
6.4 原型链
__proto__
形成的链,当我们访问一个对象的属性或者方法的时候
- 他会先在自身的属性中寻找,如果找到则返回
- 如果没有,则沿着
__proto__
这条链向上查找,查找到则返回 - 如果没找到,返回
undefined
6.4.1 instanceof 的使用
语法
A instanceof B
如果B函数的显式原形对象在A对象的原型链上,返回true
,否则返回false
(简写A是否为B的实例)
function Fn(){}
const fn = new Fn()
console.log(fn instanceof Fn) // true
6.4.2 原型链图解
在图中一共有三列,最左边的一列为 实例对象,中间的一列为 构造函数,最右侧的一列为 原型对象
只有实例才拥有__proto__
属性,只有函数才拥有prototype
属性,原型对象拥有constructor
指针
- 对于
prototype
和constructor
两个属性较容易理解 - 主要考虑
__proto__
属性- 最左栏为相应构造函数的实例对象,所以有
__proto__
指向器原型对象 - 中间栏为函数,函数拥有
prototype
属性,但为什么会拥有__proto__
呢?拥有__proto__
说明他是某个对象的实例,请记住下面两句话- 所有的对象都是
Object()
的实例 - 所有的函数都是
Function()
的实例(包括他自身)
- 所有的对象都是
Object()
是一个函数,那么他就是Function()
的实例,而__proto__
指向原型,所以会有从Object()
的__proto__
指向Function
的原型- 同理,
Foo()
和Function()
本身就是函数,也同样有__proto__
指向Function
的原型 - 最右侧一列为原型对象,要记住这样一句话
- 函数的显式原型所指向的原型对象,默认是空
Object
的实例对象,所以原型对象间有指向Object.prototype
的__proto__
- 但是对于
Object()
函数并不满足
- 函数的显式原型所指向的原型对象,默认是空
Object()
的原形对象就是原型链的尽头,沿着他的隐式原型往上找到的是null
返回undefined
- 最左栏为相应构造函数的实例对象,所以有
console.log(Object instanceof Function)
console.log(Object instanceof Object)
console.log(Function instanceof Function)
console.log(Function instanceof Object)
/* 以上四句全部打印为 true,证明了第2,3,4个结论 */
function Fn(){}
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // ture
/* 以上三句证明了结论5*/
console.log(Object.prototype.__proto__) //null
6.4.3 总结
对于原型链的总结
- 实例只有隐式原型(我们的对象
{}
,数组[]
等都是实例) - 函数既有显式原形又有隐式原型
- 原型对象只有隐式原型
对与原型链继承总结
- 原型上的属性会因为先后顺序而被覆盖
- 普通构造函数的实例不会指向
Function.prototype
function A(){}
A.prototype.n = 1
const b = new A()
A.prototype = {
n:2,
m:3
}
const c = new A()
console.log(b.n,b.m,c.n,c.m) // 1 undefined 2 3
function Fn(){}
Object.prototype.sayName = function(){
console.log("sayName")
}
Function.prototype.sayAge = function(){
console.log("sayAge")
}
const fn = new Fn()
fn.sayName() // sayName
// fn.sayAge() 报错
Fn.sayName() // sayName
Fn.sayAge() // sayAge