js作用域:存储和访问变量的规则
左查询:编译器寻找出现在等号左侧的变量,目的是对变量进行赋值
右查询:所有的非左查询,目的是寻找变量的值
左查询在所有作用域都无法找到该变量的情况下将在非严格模式下声明这个变量,右查询则会报refernceError
闭包:在函数的词法作用域之外对函数内部作用域的引用,当函数的作用域被当作参数传递到另一个非当前作用域并在当前词法作用域之外执时就产生了闭包
let fn1 = function (fn){
fn()
}
let fn2 = function (a){
let b = 2
fn1(function fn3(){
console.log(a+b)
})
}
fn2(1)
// fn2 的词法作用域被当作参数传递到了fn1,当fn2执行完成后内部的作用域并不会消失因为fn1还保持着对它的引用
闭包与循环
for(var i=1;i<=5;i++){
setTimeout(()=>{
console.log(i)
},1000*i)
}
//输出 5个6
//根据eventLoop 延迟函数的回调函数是属于异步事件会在同步事件也就是这里的for循环执行完成后执行
//这个时候i已经变成了6(for循环的结束条件),又因为var是全局声明五个回调函数内对i的引用指向同一
//内存空间
那要怎样才能输出1-5呢,我们知道setTimeout的回调执行是在循环发生之后,但是回调函数的声明却是在循环之中产生的,之所以会出现5个6的情况是因为在声明回调函数的时候始终保持对同一内存空间的引用也就是五个回调函数都指向外部作用域的i,所以如果每个回调函数在声明的时候能保存不同的i的引用,在执行这些回调函数的时候不就可以输出不同的i了,那在每一次循环中创建独立的作用域副本再将作用域传入回调函数中不就可以了
for (var i = 1; i <= 5; i++) {
(() => {
let j = i
console.log('函数声明时保存的变量'+i)
setTimeout(() => {
console.log(j);
}, 1000 * j);
})(i);
}
//执行结果:
函数声明时保存的变量1
函数声明时保存的变量2
函数声明时保存的变量3
函数声明时保存的变量4
函数声明时保存的变量5
1
2
3
4
5
//通过立即执行函数创建作用域副本,在函数声明时保持对该作用域的引用及时循环已经完成了,
//但该作用域内的变量依旧不会被回收,并在回调函数执行时输出结果
for (var i = 1; i <= 5; i++) {
let j = i;
setTimeout(()=>{
console.log(j)
},1000*i)
}
// 通过let也可以形成作用域模块来保存变量
this指向
默认指向,沿函数调用栈指向执行上下文,或者说是谁调用指向谁的作用域
let a = 1
function fn1(){
let a =2
console.log(this.a)
}
fn1()
//输出1
let b = {
a:3,
fn2:function(){
let a = 4
console.log(this.a)
}
}
b.fn2()
// 输出3
强绑定,通过call,bind,apply等方法指定函数的执行上下文,强行改变函数的this指向
例如call方法的原理
var a = 1
function fn1(...arg){
var a =2
console.log(this.a)
console.log(arg)
}
Function.prototype.call1=function(obj,...argument){
// 由于call方法是由fn1调用所以call方法内的this指向fn1
obj._fn = this
// 要改变fn1的this指向只需要改变他的执行上下文即他的调用方即可
obj._fn(...argument)
delete obj._fn
}
let testObj = {a:3}
fn1.call1(testObj,1,13,123)
//输出 (3)[1,13,123]
对象的继承机制
js不像传统的面向对象语言由类复制产生实例,所以所有的实例其实是独立的相当于是类的副本,在js内所有的实例只是新建一个新的对象,然后让这个实例指向类的原型内存空间,所以所有的实例其实是指向同一内存空间
我们通过new关键词来创建实例,直接看new做了些什么操作
/*
params
c:父类
opt:参数
*/
function adminNew(c, opt) {
//创建一个新的对象
let obj = Object.assign({},opt||{})
// 将新对象的原型空间指向父类的原型空间
// tip 所有的实例和父类的原型属性其实是指向同一内存空间
obj.__proto__ = c.prototype
// 将父类的this指向 指向新创建的对象
c.call(obj)
return obj
}
let vueInstance = adminNew(Vue,{data:{test:1}})
console.log(vueInstance)
console.log(vueInstance.data.test)
console.log(vueInstance instanceof Vue)
输出 1
true
构造函数
js本身没有构造函数,函数就是函数,所谓的构造函数不过是被new过的函数,他的实例的constructor指向他 所以他就变成了他实例的构造函数,那么为什么实例的constructor属性会指向该函数呢?由new操作符的原理可知通过new产生的实例和它的父类也就是构造函数的原型属性是指向同一内存空间,而构造函数在被声明时就自动获取了prototype.constructor属性指向自身,所以虽然实例本身没有constructor属性但是实例所属的原型内存有这个属性根据原型链查找规则自然而然的实例的constructor就指向了new他出来的这个函数也就是所谓的构造函数
function Foo(){}
console.log(Foo.prototype.constructor)
let f = new Foo()
console.log(f.constructor)
// 实例的原型属性和构造函数的原型属性指向同一内存空间
// 在构造函数声明的时候prototype.constructor属性便指向本身
// 所以所有和Foo相联的对象的constructor属性都指向Foo
console.log(Object.getPrototypeOf(f)===Foo.prototype)
输出
ƒ Foo(){}
ƒ Foo(){}
true
ES6 class
众所周知es6的class也可以实现继承那和es5的继承有什么区别呢?
ES5 和 ES6 子类 this 生成顺序不同。ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例,ES6 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象