持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情
函数的prototype
- 函数的prototype属性
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
- 原型对象中有一个属性constructor, 它指向函数对象
也就是说函数与函数的原型对象有一个互相引用的关系
- 给原型对象添加属性(一般都是方法)
- 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
证明:
<script type="text/javascript">
// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype)
function Fun () {//alt + shift +r(重命名rename)
}
console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性)
// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)
//给原型对象添加属性(一般是方法) ===>实例对象可以访问
Fun.prototype.test = function () {
console.log('test()')
}
var fun = new Fun()
fun.test()
</script>
显示原型与隐式原型
- 每个函数function都有一个prototype,即显式原型(属性)
- 每个实例对象都有一个__proto__,可称为隐式原型(属性)
- 对象的隐式原型的值为其对应构造函数的显式原型的值
- 内存结构(图)
- 总结:
- 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
- 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
- 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
例如:
<script type="text/javascript">
//定义构造函数
function Fn() { // 内部语句: this.prototype = {}
}
// 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
console.log(Fn.prototype)
// 2. 每个实例对象都有一个__proto__,可称为隐式原型
//创建实例对象
var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__)
// 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(Fn.prototype===fn.__proto__) // true
//给原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
//通过实例调用原型的方法
fn.test()
</script>
原型链
原型链
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着__proto__这条链向上查找, 找到返回
- 如果最终没找到, 返回undefined
- 别名: ==隐式==原型链
- 作用: 查找对象的属性(方法)
注意:
/*
1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
*/
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
/*
2. 所有函数都是Function的实例(包含Function)
*/
console.log(Function.__proto__===Function.prototype)
/*
3. Object的原型对象是原型链尽头
*/
console.log(Object.prototype.__proto__) // null
构造函数/原型/实例对象的关系
例一:
var o1 = new Object();
var o2 = {};
例二:
function Foo(){ }
这张图有几个注意点:
我们知道所有的函数都是Function的实例对象,这其中甚至包括Function自身 这也就解释了:
- Foo的构造函数的__proto__指向Function的原型对象,因为实例对象的__proto__永远指向构造函数的prototype。
- Function自己也是Function的实例对象,既然是对象那么就有__proto__属性,根据实例对象的__proto__永远指向构造函数的prototype,我们可以知道Function的__proto__指向Function的原型对象。
- Object的构造函数是Function的实例对象,同样也根据实例对象的__proto__永远指向构造函数的prototype,所以Object的构造函数的__proto__指向Function的原型对象。
不建议用父子级的概念去理解原型
原型链的属性问题
- 读取对象的属性值时: 会自动到原型链中查找
- 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
- 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
从以下的例子中可以发现如上的规律:
function Fn() {
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a) //xxx
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a, fn2.a) //xxx yyy
在fn2中:
instanceof
- instanceof是如何判断的?
- 表达式: A instanceof B
- 如果B构造函数的显式原型对象(prototype)在A对象的原型链上(换句话说有交点), 返回true, 否则返回false
- Function是通过new自己产生的实例
我们可以通过以下的两个例子去理解instanceof: ①
/*
案例1
*/
function Foo() { }
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true
原型链也叫做隐式原型链,所以我们的注意力要放在以__proto__为连接,以prototype为节点的线,那就是原型链。
②
/*
案例2
*/
console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
function Foo() {}
console.log(Object instanceof Foo) // false
变量提升与函数提升
- 变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
- 函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
验证:
<script type="text/javascript">
console.log('-----')
/*
面试题 : 输出 undefined
*/
var a = 3
function fn () {
console.log(a)
var a = 4
}
fn()
console.log(b) //undefined 变量提升
fn2() //可调用 函数提升
// fn3() //不能 变量提升
var b = 3
function fn2() {
console.log('fn2()')
}
var fn3 = function () {
console.log('fn3()')
}
</script>
有一个需要的点:
var fn3 = function () {
console.log('fn3()')
}
像这样声明的函数会被当作变量提升,而不会被当作函数提升。如果想要函数提升只能使用function来声明。
执行上下文
①代码分类(位置)
- 全局代码
- 函数(局部)代码
② 全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
- var定义的全局变量==>undefined, 添加为window的属性
- function声明的全局函数==>赋值(fun), 添加为window的方法
- this==>赋值(window)
- 开始执行全局代码
③函数执行上下文
- 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中的封闭区域,对外不可见)
- 对局部数据进行预处理
- 形参变量==>赋值(实参)==>添加为执行上下文的属性
- arguments==>赋值(实参列表), 添加为执行上下文的属性
- var定义的局部变量==>undefined, 添加为执行上下文的属性
- function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
- this==>赋值(调用函数的对象)
- 开始执行函数体代码
执行上下文栈
- 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
- 在函数执行上下文创建后, 将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后, 栈中只剩下window
例如:
<script type="text/javascript">
var a = 10
var bar = function (x) {
var b = 5
foo(x + b)
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
bar(10)
// bar(10)
</script>
注:函数只有在调用的时候,才会创建函数的执行上下文。定义时不会创建。
作用域
①理解
- 就是一块"地盘", 一个代码段所在的区域
- 它是静态的(相对于上下文对象), 在编写代码时就确定了
②分类
- 全局作用域
- 函数作用域
- 没有块作用域(ES6有了)
③ 作用
- 隔离变量,不同作用域下同名变量不会有冲突
例如:
<script type="text/javascript">
/* //没块作用域
if(true) {
var c = 3
}
console.log(c)*/
var a = 10,
b = 20
function fn(x) {
var a = 100,
c = 300;
console.log('fn()', a, b, c, x) //100,20,300,10
function bar(x) {
var a = 1000,
d = 400
console.log('bar()', a, b, c, d, x) //1000,20,300,400,200
}
bar(100)
bar(200)
}
fn(10)
</script>
在哪个作用域就现在哪个作用域找,找不到就往外找。
作用域与执行上下文
区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文是在调用函数时, 函数体代码执行之前创建
区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
联系
- 执行上下文(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
例如:
<script type="text/javascript">
var a = 10,
b = 20
function fn(x) {
var a = 100,
c = 300;
console.log('fn()', a, b, c, x)
function bar(x) {
var a = 1000,
d = 400
console.log('bar()', a, b, c, d, x)
}
bar(100)
bar(200)
}
fn(10)
</script>
上图修正:全局上下文环境中应该是b=20
作用域链
①理解
- 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
- 查找变量时就是沿着作用域链来查找的
②查找一个变量的查找规则
- 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
- 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
- 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
如上图输出结果: 4,3,2,报错(d is not define)