什么是作用域,你可以理解为你所声明变量的可用范围,我在某个范围内申明了一个变量,且这个变量能在这个范围内可用, 那么我可以说此范围就是该变量的作用域。
作用域一般分为局部作用域和全局作用域。
在函数体内,局部变量的优先级高于同名的全局变量(函数形参也是局部变量)
作用域链,当我们需要某个变量的值时,我们先去理它最近的作用域去找,如果找不到,就找它的上级作用域,依次类推,直到找到全局,如果全都未定义,那就抛出一个错误。
js中变量的作用域链与定义时的环境有关,与执行时无关。
this指向与申明无关,永远指向距离自己最近的最终调用者。
针对函数调用,我们在一个函数内申明this,我们去看它最终是被谁调用,如果最终的调用前啥都没有,this就会指向window,这里只是针对js非严格模式。
func.call(context,a,b)这一种,如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
obj.method ==> obj.method.call(obj,a,b)
我在上面说,构造函数this会指向new之后的实例(复制出来的副本),但如果在函数里有return,this指向可能会发生变化。
在构造函数中return 一个对象时,this会指向这个对象,但如果return的不是一个对象,那就不会改变this指向,还是指向new 出来的实例。
push和unshift分别为尾部和头部添加元素,pop和shift分别为尾部和头部删除元素。push和unshift返回的是修改后的数组length,pop和shift返回的是被删除的元素。
JSON.stringify()的作用是将 JavaScript 对象转换为 JSON 字符串,而JSON.parse()可以将JSON字符串转为一个对象。
在使用JSON.parse()需要注意一点,由于此方法是将JSON字符串转换成对象,所以你的字符串必须符合JSON格式,即键值都必须使用双引号包裹。
localStorage/sessionStorage默认只能存储字符串,而实际开发中,我们往往需要存储的数据多为对象类型,那么这里我们就可以在存储时利用json.stringify()将对象转为字符串,而在取缓存时,只需配合json.parse()转回对象即可。
在《JavaScript模式》这本书中,new的过程说的比较直白,当我们new一个构造器,主要有三步:
• 创建一个空对象,将它的引用赋给 this,继承函数的原型。
• 通过 this 将属性和方法添加至这个对象
• 最后返回 this 指向的新对象,也就是实例(如果没有手动返回其他的对象)
全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问。
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
执行上下文栈(下文简称执行栈)也叫调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
其中环境记录用于存储当前环境中的变量和函数声明的实际位置;外部环境引入记录很好理解,它用于保存自身环境可以访问的其它外部环境。
全局词法环境组件:
对外部环境的引入记录为null,因为它本身就是最外层环境,除此之外它还记录了当前环境下的所有属性、方法位置。
函数词法环境组件:
包含了用户在函数中定义的所有属性方法外,还包含了一个arguments对象。函数词法环境的外部环境引入可以是全局环境,也可以是其它函数环境,这个根据实际代码而来。
在ES6中唯一的区别在于词法环境用于存储函数声明与let const声明的变量,而变量环境仅仅存储var声明的变量。
函数声明与var声明的变量在创建阶段已经被赋予了一个值,var声明被设置为了undefined,函数被设置为了自身函数,而let const被设置为未初始化。
以全局变量和局部变量来说,函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器会识别并释放它们。而对于全局变量,垃圾回收器很难判断这些变量什么时候才不被需要,所以尽量少使用全局变量。
所谓闭包其实就是一个自带了执行环境(由外层函数提供,即便外层函数销毁依旧可以访问)的特殊函数。
百度百科:
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。
《JavaScript高级编程指南》:
闭包是指有权访问另外一个函数作用域中的变量的函数。
MDN(几年前的解释,现已更新):
闭包是指那些能够访问自由变量的函数。自由变量是指在函数中使用的,但既不是函数arguments参数也不是函数局部变量的变量。闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量。
函数作用域在定义时就已经确定了,而不是调用时确定。
this默认绑定我们可以理解为函数调用时无任何调用前缀的情景。
如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上,如果函数调用前存在多个对象,this指向距离调用自己最近的对象。
当访问一个变量时,解释器会先在当前作用域查找标识符,如果没有找到就去父作用域找,作用域链顶端是全局对象window,如果window都没有这个变量则报错。
当在对象上访问某属性时,首选i会查找当前对象,如果没有就顺着原型链往上找,原型链顶端是null,如果全程都没找到则返一个undefined,而不是报错。
使用call之类的方法改变this指向时,指向参数提供的是null或者undefined,那么 this 将指向全局对象。
1.call、apply与bind都用于改变this绑定,但call、apply在改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFcuntion绑定函数,这也是为什么上方例子中bind后还加了一对括号 ()的原因。
2.bind属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过bind、apply或 call 修改;call与apply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。
3.call与apply功能完全相同,唯一不同的是call方法传递函数调用形参是以散列形式,而apply方法的形参是一个数组。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。
箭头函数中没有this,箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁。箭头函数this还有一个特性,那就是一旦箭头函数的this绑定成功,也无法被再次修改。
所有的对象都有__proto__
属性,但只有函数拥有prototype
属性。
当实例访问某个属性时,会先查找自己有没有,如果没有就通过__proto__
访问自己构造函数的prototype有没有,前面说构造函数的原型是一个对象,如果原型对象也没有,就继续顺着构造函数prototype中的__proto__
继续查找到构造函数Object()的原型,再看有没有,如果还没有,就返回undefined,因为再往上就是null了,这个过程就是我们熟知的原型链,说的再准确点,就是__proto__
访问过程构成了原型链。
原型就是一个包含了诸多属性方法的对象,原型对象的__proto__
指向构造函数Object()
的原型。当一个对象访问某个属性时,它会先查找自己有没有,如果没有就顺着__proto__
往上查找创建自己构造函数的原型有没有,这个过程就是原型链,原型链的顶端是null。
函数均为被普通调用和new调用,所以你可以说函数都是构造函数,也正因如此,ES6才推出了Class类,算是给了构造函数一个真正的名分。
prototype是原型对象,__proto__
是访问器属性,对象就是通过这个家伙访问构造函数的原型对象。
var obj = {
name: undefined
};
console.log('name' in obj);//true
console.log('age' in obj);//false