声明提升 this 原型链 作用域链 一篇就够
声明提升
tips:
- 函数表达式 function xxx(){} 会变量提升 可以简单理解为其代码在当前函数的最顶端
- 声明提升: var a = 2 会分两步执行, 函数顶部声明var a, 然后到了具体的代码行 才执行 a = 2 赋值
- 声明提升的权重(权重越大 它在越上面 就会越容易被覆盖): (var === 参数) > function xxx
- 给未声明的变量赋值: 非严格模式下 会隐式的变为全局变量, 严格模式下会报错
体验DEMO: http://demo.404mzk.com/val_up/
var b = function(a){
console.log(a) // ƒ a(){}
var a = 2
function a(){}
console.log(a) //2
}
b(1)
this的取值
直接定义function
function testFn(){
console.log(this)
}
testFn()
这样调用 this在非严格模式下为 window 在严格模式下为 undefined
属性方法调用
var testObj = {
data: 'testObjData',
testFn: function(){
console.log(this.data)
}
}
testObj.testFn() //testObjData
方法函数调用时 this为调用对象
构造函数
var testFn = function(){ this.data = 'counstructoredData' }
new testFn()
在new的时候 构造函数this是被实例化的对象
以上都是常规的this调用
那么js 有没有一些改变常规this的方法呢?
改变this的方法 bind apply call 箭头函数
绑定this的 优先级是
箭头函数 > bind > apply、call > 正常this
tips:
- 一旦函数被bind 或者 箭头函数定义时, 假如new 函数 会报错
xxx is not a constructor
- 如果一个函数连续被bind两次, 则this取第一次bind绑定this
可以看下demo http://demo.404mzk.com/this/
var data = 'globalData'
//bind apply 箭头会遵循哪个?
var bindThis = { data: 'bindThisData'},
bind2This = { data: 'bind2This'},
applyThis = { data: 'applyThisData'},
callThis = { data: 'callThisData'},
testObj = {
data: 'testObjData',
simple: function(){
var testFn = function(){
console.log(this.data)
},
obj = { data: 'objData'}
console.log(this.data)//'testObjData'
testFn() //'galobalData'
obj.testFn = testFn
obj.testFn() //'objData'
},
all: function(){
var testFn = () => {
console.log(this.data)
}
testFn = testFn.bind(bindThis)
testFn.apply(applyThis) //'testObjData'
testFn() //'testObjData'
},
binAndApply: function(){
var testFn =function(){
console.log(this.data)
}
testFn = testFn.bind(bindThis)
testFn.apply(applyThis) 'bindThisData'
testFn() 'bindThisData'
},
bind2: function(){
var testFn = function(){
console.log(this.data)
}
testFn1 = testFn.bind(bindThis)
testFn2 = testFn1.bind(bind2This)
testFn2() //'bindThisData'
},
constructorAndArrow: function(){
var testFn = () => {
console.log(this.data)
}
new testFn() //'TypeError: testFn is not a constructor'
},
constructorAndBind: function(){
var testFn = () => {
console.log(this.data)
}
testFn = testFn.bind(bindThis)
new testFn() //'TypeError: testFn is not a constructor'
}
}
console.log('testObj.simple 开始')
testObj.simple()
console.log('testObj.all 开始')
testObj.all()
console.log('testObj.binAndApply 开始')
testObj.binAndApply()
console.log('testObj.bind2 开始')
testObj.bind2()
try{
console.log('testObj.constructorAndArrow 开始')
testObj.constructorAndArrow()
}catch(e){
console.log(e)
}
try{
console.log('testObj.constructorAndBind 开始')
testObj.constructorAndBind()
}catch(e){
console.log(e)
}
作用域链
作用域的作用是什么
当我们取变量时
(function(){
var a = 1
function b(){
var a = 2
console.log(a)
}
b()
})()
它的输出会是2还是1呢?
这取决于 console.log(a)中取的是 b函数的a 还是 最外层定义的a变量
一般来说 前端er最起码能知道作用域链取是 local作用域 -> 闭包作用域 -> global作用域
但是有没有作用域是在local之前的呢? 有就是wich 和 try catch(e)中的catch(){}
体验demo: http://demo.404mzk.com/actionScope/index.html
普通的作用域
with的作用域
with作用域会排在local作用域前 在with内执行的获取变量
都会类似的先在 this.xxx(this为withObj) 的查找变量 然后再去查找local作用域
try catch(){}中catch的作用域
catch中会添加一个catch的作用域
当然with一般我们不用 catch里影响到作用域的就只有参数, 一般对我们影响不大
最主要的是 local -> 闭包 -> global的作用域查找
何时创建Scope
除了Global作用域 其他作用域 都是 function创建起来的作用域
现在ES6多了一种
{
let x = 0;
}
console.log(x); // Uncaught ReferenceError: x is not defined
原型链
了解原型链之前先理解几个概念
- prototype: xxx.protype指向的是xxx的原型
- constructor: 存在于xxx.prototype.constructor, 指向xxx
__proto__
: 每次被new的实例化对象 都会包含一个__proto__
属性 其值是其constructor的prototype
从上可得出
- 函数 都有
__proto__
和 prototype,__proto__
指向函数的构造函数的原型, prototype指向的是 函数的原型 - 对象 只有
__proto__
指向对象的构造函数的原型
下面列出几个对比 来理解原型链
体验地址: http://demo.404mzk.com/prototype_chain/
var Foo = function(){}
var foo = new Foo()
foo.a = 1
Foo.prototype.a = 2
console.log(foo)
console.log(foo.prototype === undefined)
console.log(foo.__proto__ === Foo.prototype)
console.log(foo.__proto__.constructor === Foo)
console.log(Foo.prototype.constructor === Foo)
//对象
var obj = {}
console.log(obj)
console.log( obj.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__ === null)
//函数
var fn = function(){}
console.log( fn )
console.log(fn.__proto__ === Function.prototype)
console.log(Function.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__ === null)
//正则
var regExp = /a/
console.log( regExp )
console.log( regExp.__proto__ === RegExp.prototype)
console.log( RegExp.prototype.__proto__ === Object.prototype)
console.log( Object.prototype.__proto__ === null)
//数组
var array = []
console.log(array)
console.log(array.__proto__ === Array.prototype)
console.log(Array.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__ === null)
可以看下几个类似的原型链
总结就是
- 万物都是Object.prototype出来的
需要注意的额外点
Function.__proto__
=== Function.prototype
可以说Function的构造函数还是其自身
好奇为什么Function的构造函数还是Function的读者
如果不是这样的话 那是先有Function的构造函数 还是先有Function呢?
最好看下网上的万能原型链图
原型链的利用
最常见的就是用来做继承了
体验地址: http://demo.404mzk.com/prototype_chain/extend.html
function inheritPrototype(subType, superType){
var prototype = Object(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
var Grandfather = function(name){
this.grandfahterName = name
}
Grandfather.prototype.grandfatherFn = function(){}
var Father = function(name){
Grandfather.apply(this, arguments)
this.fatherName = name
}
inheritPrototype(Father, Grandfather)
Father.prototype.fatherFn = function(){}
var Son = function(name){
Father.apply(this, arguments)
this.sonName = name
}
inheritPrototype(Son, Father)
Son.prototype.sonFn = function(){}
var son = new Son('mzk')
console.log(son)
console.log( typeof son) //object
console.log( son.__proto__ === Son.prototype)
console.log( Son.prototype === Father.prototype)
console.log( Father.prototype === Grandfather.prototype)
console.log( son instanceof Son)
console.log( son instanceof Father)
console.log( son instanceof Grandfather)
需要注意的是构造函数的.prototype 必须声明在 inheritPrototype函数之后 不然会被覆盖掉
考题: 为什么浏览器能判断son 是Son Father Grandfather的实例?
因为instanceof 主要是检验son.__protype__
=== Son.prototype