原型与原型链
原型与原型链
文章结构
- 原型与原型链
- 原型
- 原型链
- 原型链中的this
- 作用域与闭包
- 执行上下文
- 作用域
- 作用域链
- 闭包
- 面试题
原型与原型链
下面几条必须记住:
- 所有的引用类型(数组,函数,对象),都具有对象特性,既可以自由扩展属性。
- 所有的引用类型(数组,函数,对象),都具有__proto__属性,属性是一个普通的对象。
- 所有的函数,都具有prototype,属性是一个普通的对象。
- 所有的引用类型(数组,函数,对象),其__proto__属性都指向它的构造函数的prototype属性值。即:
obj.__proto__ === Object.prototype
// 要点一:自由扩展属性
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;
// 要点二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
// 要点三:函数有 prototype
console.log(fn.prototype)
// 要点四:引用类型的 __proto__ 属性值指向它的构造函数的 prototype 属性值
console.log(obj.__proto__ === Object.prototype)
原型
当试图找到一个对象的某个属性时,如果该对象没有这个属性,那么就会到它的__proto__(即它的构造函数的prototype)去寻找。如果它的__proto__还是没有则继续在f.proto.proto(也即F.prototype.proto)上寻找。如果直到最上层也没有找到,则返回undefined。
// 构造函数
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan')
// 测试
f.alertName()
执行f.alertName()时,因为该对象没有alertName()方法,所以就会到它的__proto__(即Foo.prototype)上去寻找。此时找到了,调用该原型链上的alertName()方法。没有找到则继续查找。
原型链
上述找的过程,是一个链式的结构,叫做“原型链”。所有原型链的最上层都是null(即Object.prototype.proto === null)。
原型链中的this
所有从原型链中得到的方法,在this执行时,都指向了当前触发这个方法的对象。
作用域与闭包
执行上下文
在一段javsscript脚本执行之前,要先解析代码,解析的时候,会创建一个全局执行上下文环境,先把即将执行代码中的变量和函数的声明拿出来存放在这个环境中:变量暂时赋值为undefined,函数则声明好可以使用。这一步完成之后,再正式开始执行代码。
console.log(a)
var a = 100
fn('zhangsan')
function fn(name) {
age = 20
console.log(name, age)
var age
}
console.log(b);
// Uncaught ReferenceError: b is not defined
b = 100;
上述代码执行过程:
- 创建全局执行环境
- 申明变量a(此时变量a赋值为undefined)、函数fn
- 开始执行代码:
console.log(a) // undefined
,fn('zhangsan') // zhangsan 20
,console.log(b) // 报错,因为全局执行环境中没有找到关于b的申明
一个函数在执行过程中,会创建函数执行上下文环境,相对于全局执行上下文环境,在函数执行上下文环境中,增加了this,arguments和函数的参数。
this
this在执行时才能确定其值,定义的时候不能确认。
?call apply bind的用法及手写实现。
作用域
在es6之前,javascript中没有块级作用域的概念。例如:
if (true) {
var name = 'tom'
}
console.log(name) // tom
作用域就是一个独立的地盘,让变量不会泄露、暴露出去。JS 没有块级作用域,只有全局作用域和函数作用域。
var a = 100
function fn() {
var a = 200
console.log('fn', a)
}
console.log('global', a)
fn()
这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在(function(){....})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。
ES6 中开始加入了块级作用域,使用let
定义变量即可。
if (true) {
let name = 'tom'
}
console.log(name) // 报错,因为在全局作用域中没有name的声明。
作用域链
执行某个函数作用域中的一段代码时,当一个变量的申明没有在当前作用域中找到,会一层一层向上级查找,这一层一层的关系,就是作用域链。若在全局作用域中还没有找到,才会报错。
var a = 100
function fn() {
var b = 200
console.log(a)
console.log(b)
}
fn()
上述代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a变量。像这种在当前作用域中没有定义但在作用域链中定义的变量,就是自由变量。
自由变量将在作用域链中寻找,但是依据的是函数定义时候的作用域链,而不是执行时候的。
闭包
闭包主要有两个执行场景:
- 函数作为返回值返回
- 函数作为参数传递
function F1() {
var a = 100
return function () {
console.log(a)
}
}
var f1 = F1()
var a = 200
f1() // 100
function F1() {
var a = 100
return function () {
console.log(a)
}
}
function F2(f1) {
var a = 200
console.log(f1())
}
var f1 = F1()
F2(f1) // 100