** 1.词法作用域 **
前提:程序中一段源代码执行前会经历三个步骤,统称为编译 - 分词/词法分析: - 解析/语法分析 - 代码生成
** 1.1 ** 词法作用域:定义在词法阶段的作用域,作用域是由书写代码时函数声明的位置决定的。
核心内容:就是找到所有的声明,然后用合适的作用域将它们关联起来
** 2 ** 函数作用域和块作用域:任何声明再某个作用域内的变量都附属于这个作用域
** 3 ** 提升
** 1>变量声明提升:**
a=2;
var a;
console.log(a); //2
console.log(a);
var a=2; //undefined
代码都要先先进行编译再运行,所以对于var a=2,js会看成两个声明,一个是定义声明:编译阶段执行,第二个是赋值声明:留在原地等待执行阶段,所以对于上面的代码其实可以看成:
var a;
a=2;
console.log(a);
var a;
console.log(a);
a=2;
** 2>函数变量声明提升**
foo();
bar();
var foo=function bar() {
console.log("1111");
}
foo被提升,即函数声明会被提升,但是函数表达式还停留在原地,对undefined进行操作
var foo;
foo();//TypeError
bar();//ReferenceError 具名函数也无法在赋值之前在作用域使用
foo=function bar() {
console.log("1111");
}
** 3>优先级 ** 函数声明与变量声明都会被提升,函数首先被提升,其次是变量
foo();
var foo;
function foo() {
console.log("2");
}
foo=function () {
console.log("2");
}
var foo尽管出现在function foo() 声明之前,但它是重复的声明,因此被忽略,后面的函数声明可以覆盖前面的 js引擎理解为:
function foo() {
console.log("1");
}
foo();
foo=function () {
console.log("2");
}
重复声明是一份非常糟糕的方式,会出现一些想不到的情况:
foo(); //b
var a=true;
if(a){
function foo(){
console.log("a");
}
}else{
function foo(){
console.log("b");
}
}
函数声明被提升到全局作用域,然后后一个覆盖前一个,造成输出不是想要的
** 4.闭包 **
1>定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
function foo() {
var a="闭包";
function bar() {
console.log(a);
}
return bar;
}
var baz=foo();
baz();
- 神奇之处:因为bar持有对该作用域的引用,使得foo作用域一直存活,没有被垃圾回收
- 上述代码只是为了解释如何使用与理解闭包而在结构上做了修饰,回归到日常
function wait(message) {
setTimeout(function timer() {
console.log(message);
}, 1000);
}
wait("hello word");
深入到引擎原理,内置的工具函数setTimeout持有对一个参数的引用,引擎调用这个函数(timer)
2>循环与闭包
for(var i=0;i<=5;i++){
setTimeout(() => {
console.log(i);
}, 1000);
}
想当然的会以为应该会输出0-6,但是但实际是输出了6次6,上述代码可以理解为下面这样:
var i;
for(i=0;i<=5;i++){
}
执行6次:
setTimeout(() => {
console.log(i);
}, 1000);
原因是:i被封闭在一个共享的全局作用域中,实际只有一个i,所有函数共享一个i,所以我们需要在循环的过程中都产生一个闭包作用域 改成下面两种就ok了:
for(var i=0;i<=5;i++){ for(var i=0;i<=5;i++){
(function(){ (function(j){
var j=i; setTimeout(() => {
setTimeout(() => { console.log(j);
console.log(j); }, 1000);
}, 1000); })(i);
})(); }
}
改成let会更简单(let 可以用来劫持块作用域,本质就是一个块转换成一个可以被关闭的作用域)
for(let i=0;i<=5;i++){
setTimeout(() => {
console.log(i);
}, 1000);
}
3>模块
function fooModule() {
var cool="cool";
var numArray=[1,2,3];
function doSomething() {
console.log(cool);
}
function doAnother() {
console.log(numArray.join("!"));
}
return {
doSomething:doSomething,
doAnother:doAnother
}
}
var foo=fooModule();
foo.doSomething();
foo.doAnother();
** 5.动态作用域 **
js并不具有动态作用域,只有词法作用域,但是this机制在某种程度很像动态作用域
- 词法作用域:写代码或者说定义时确定
- 动态作用域:运行时确定
function foo() {
console.log(a);
}
function bar() {
var a=3;
foo();
}
var a=2;
bar();
1>关于this
- 为什么使用this
function speak() {
console.log(this.name);
}
var me={
name:"ghp"
}
speak.call(me);
如果不使用this的话,就需要通过传参来解决一些问题,this提供了一种更好的方式来隐式“传递”一个对象的引用
function speak(object) {
console.log(object.name);
}
var me={
name:"ghp"
}
speak(me);
- this调用 完全取决于调用位置,调用位置如何决定this的绑定对象 1>默认绑定(函数直接使用,不带任何修饰的函数引用进行调用,默认绑定到全局)--非strict
function foo() {
console.log(this.a);
}
var a=2;
foo();
2>隐式绑定(会把函数调用中的this绑定到上下文对象)
function foo() {
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
obj.foo();
最终调用的是window,所以this指向全局变量
function foo() {
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
var a="windows a"
var bar=obj.foo;
bar();
如果函数传入语言内置的函数:
function foo() {
console.log(this.a);
}
var obj={
a:2,
foo:foo
}
var a="windows a"
setTimeout(obj.foo, 1000);
其实就是
function setTimeout(fn,delay){
fn(); //所以是"windows a"
}
3>
- 显式绑定 使用call()和apply()等来改变this的绑定对象
function foo() {
console.log(this.a);
}
var obj={
a:2
}
foo.call(obj);//this强制绑定到obj
- API调用的"上下文" 第三方库的许多函数,以及js中的内置函数,都提供了一个可选参数,确保你的回调函数使用指定的this(forEach 第一个参数,放函数,第二个参数,可选,是第一个参数函数的this,不填默认window)
[11,21,31].forEach(item,obj);
function item(el) {
console.log(el,this.id);
}
var obj={
id:"12"
}
4>new绑定
new的机制与面向类的语言完全不同
js中的构造函数,只是被new操作符调用的普通函数而已
- 创建一个全新的对象
- 新对象会被执行原型连接
- 新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个对象 new 调用foo时,构造一个新对象并且绑定到foo调用的this上
function foo(a) {
this.a=a;
}
var obj=new foo(1);
console.log(obj.a);
5>优先级 new绑定>显示绑定>隐式绑定>默认绑定 6>绑定例外
- 显示绑定中,把null与undefined作为绑定对象,应用默认绑定规则
function foo() {
console.log(this.a);
}
var obj={
a:1
};
var a="window a";
console.log(foo.call(null));
function foo() {
console.log(this.a);
}
var a=2;
var o={
a:3,
foo:foo
}
var p={
a:4
}
o.foo(); //3
(p.foo=o.foo)();//2 引用直接调用,默认绑定
p.foo();//4
7>软绑定
8>箭头函数x 并不会使用上面的标准绑定规则,根据当前的词法作用域来决定this,箭头函数会继承外层函数调用的this绑定
var obj = {
age:18,
getAge:function getAge() {
console.log(this.age);
}
}
obj.getAge() //18
var obj = {
age:18,
getAge:()=>{
console.log(this.age);
}
}
obj.getAge() //外层没有函数,指向window:undefined
** 6.对象 **
1>类型
6中主要类型(string,number,boolean,null,undefined,object)
- 简单基本类型(不是对象) string,number,boolean,null,undefined
- 复杂基本类型(对象的子类型) function ,array
- 内置对象 String,Number,Boolean,Object,Function,Array,Data,RegExp,Error 疑问:
var star="i am ghp";
console.log(star.length);
可以这么用时因为引擎自动把字面量转换成String对象,所以可以访问属性与方法 2>类型
var myObect={
a:2;
}
myObect.a; 属性访问
myObect["a"] 键访问
在对象中,属性名永远是字符串
var myObect={
}
myObect[true]="111";
console.log(myObect["true"]===myObect[true]); //true
3>数组
var myObect=[1,2,3];
myObect.baz="12";
console.log(myObect.length);//3
myObect[3]="12";
myObect.forEach((item)=>{
console.log(item);
})
console.log(myObect.length); //4
数组与对象都根据其对应的行为与用途做了优化 最好用对象来存储键值对 数组来存储数值下标/值对 4>复制
- 深复制(由于 Object.assign(..) 就是使用 = 操作符来赋值,所 以源对象属性的一些特性(比如 writable)不会被复制到目标对象。)
//目标对象 后面可以跟多个源对象,遍历一个或者多个源对象的所有可以枚举的自有键,复制到目标对象,最后返回目标对象
var anotherObject = {
c: "111"
}
var myObject = {
a: 2,
b: anotherObject
};
var newObject = Object.assign({}, myObject);
console.log(newObject);
5>属性描述符
var myObject={
a:2
}
Object.getOwnPropertyDescriptor(myObject,"a");
//{value: 2, writable: true(可写), enumerable: true(可枚举), configurable: true(可配置)}
var myObject = {
}
Object.defineProperty(myObject, "a",{
value: 2,
writable: false,
configurable: true,
enumerable: true
});
myObject.a="111";
console.log(myObject.a);//2
冻结(Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们 的值。)
var myObject = {
}
Object.defineProperty(myObject, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
});
Object.freeze(myObject);
myObject.a = "111";
console.log(myObject.a);
get(从返回值的角度来说,这两个引用没有区别——它们都返回了 undefined。然而,尽管乍 看之下没什么区别,实际上底层的 [[Get]] 操作对 myObject.b 进行了更复杂的处理。)
var str={
a:undefined
}
console.log(str.a);//undefined
console.log(b); //ReferenceError: b is not defined
console.log(str.b); //undefined