js重要知识点(学习)

JavaScript重要知识点梳理

变量类型的转换与比较

题:== 是如何进行比较的?== 与 === 的区别是什么?[]==![] 输出什么?

== 比较流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnVjC6Uk-1605010725370)(media/16045936766013/16046002934990.jpg)]

  1. 判断类型是否一致,一致则判断值的大小是否一致

  2. 不一致则进行隐式转换

  3. 判断两个值是否为 null 和 undefined,如果是就返回 true

  4. 继续判断是否为 string 和 number,如果是,则把 string 转换为 number 再比较大小

  5. 不是则判断其中一方是否为 Boolean,如果是就将 Boolean 转换为 number 再判断

  6. 再判断如果一方是 object,另一方是 string、number、symbol,则把object 转换为原始类型再判断

toString 与 valueOf 转换:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGYXiT4S-1605010725374)(media/16045936766013/16046015806413.jpg)]

=== 的比较流程:

  1. 严格判断,直接判断类型是否相同,不同返回 false

  2. 相同再比较大小,不隐式转换

  3. 如果两边都是引用类型,则判断的两个的引用内存地址是否一致

原型与原型链

在规范里,原型 prototype 被定义为:给其它对象提供共享属性的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5yA3lj1q-1605010725377)(media/16045936766013/16046285291294.jpg)]

由上图我们可以看到:

  • 我们定义了一个 obj 对象,并对它申明了一个 a 属性。但当我们打印obj的时候,发现他有它有 __proto__ 属性, __proto__ 属性的值也是一个对象。(问题:__proto__属性是干嘛的,怎么来的?)这意味着obj对象被隐式(不是自己创建)挂载了另一个对象的引用,放到了obj对象的__proto__属性中。

__proto__属性

obj.__proto__ === Object.getPrototypeOf(obj) // true

提示:Object.getPrototypeOf():返回指定对象的原型(内部[[Prototype]]属性的值)

由上面代码可以知道,__proto__ 属性其实隐式挂载了对象的原型。

__proto__的由来

  • ECMAScript 规范说 prototype 应当是一个隐式引用

  • 通过 Object.getPrototypeOf(obj) 间接访问指定对象的 prototype 对象

  • 通过 Object.setPrototypeOf(obj, anotherObj) 间接设置指定对象的 prototype 对象。

  • 部分浏览器提前开了__proto__ 的口子,使得可以通过 obj.__proto__ 直接访问原型,通过 obj.__proto__ = anotherObj 直接设置原型。

  • ECMAScript 2015 规范只好向事实低头,将 __proto__ 属性纳入了规范的一部分

由此我们可以知道:

  • 表面看对象里存在一个 __proto__ 属性,实际上,它只是开发者工具为了方便让开发者查看原型,故意渲染出来的虚拟节点。虽然跟对象的其它属性并列,但并不在该对象中。

  • __proto__ 属性既不能被 for in 遍历出来,也不能被 Object.keys(obj) 查找出来

  • 当我们访问对象的 obj.__proto__ 属性时,其实访问的是Object.prototype对象上 __proto__ 属性

  • 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的prototype属性值

constructor

constructor属性是对象所独有的,它是一个对象指向一个函数,这个函数就是该对象的构造函数。

  • 每一个对象都有其对应的构造函数,本身或者继承而来。
  • 单从constructor这个属性来讲,只有prototype对象才有。
  • 每个函数在创建的时候,JavaScript 会同时创建一个该函数对应的prototype对象,而函数创建的对象.__proto__ === 该函数.prototype该函数.prototype.constructor===该函数本身
  • 故通过函数创建的对象即使自己没有constructor属性,它也能通过__proto__找到对应的constructor
  • 所以任何对象最终都可以找到其对应的构造函数

原型

JavaScript 中万物皆对象,而对象皆出自构造函数

  • 只有对象类型才有原型概念

  • 普通对象(即使用对象字面量或者Object构造器创建的对象)的原型为__proto__属性,此属性其实是个访问器属性,并不是真实存在的属性

  • 普通函数有2个属性,一个是是__proto__(与普通对象类似),还有一个是函数专有的prototype属性

  • 不是所有的对象都会有原型,比如对象原型Object.prototype的原型Object.prototype.__proto__就指向null

  • 箭头函数虽然属于函数,由Function产生,但是没有prototype属性没有构造器特性,所以也就没有所谓的constructor,就不能作为构造器使用

原型链

__proto__是对象所独有的,并且__proto__是一个对象指向另一个对象,也就是他的原型对象。我们也可以理解为父类对象。它的作用就是当你在访问一个对象属性的时候,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的对象(父类对象)上查找,如果父类对象依旧不存在这个属性,那么就会去其父类的__proto__属性所指向的父类的父类上去查找。以此类推,直到找到 null。而这个查找的过程,也就构成了我们常说的原型链。

作用域与作用域链

作用域是指程序源代码中定义变量的区域:作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

静态作用域与动态作用域的区别

  • 静态作用域:函数的作用域在函数定义的时候就决定了
  • 动态作用域:函数的作用域是在函数调用的时候才决定的
var value = 1;
function foo() {
    console.log(value);
}
function bar() {
    var value = 2;
    foo();
}
bar();
// 结果是 ???

静态作用域执行过程:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。

动态作用域执行过程:
执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。

JavaScript采用的是静态作用域,所以这个例子的结果是 1。

ES6 之前 JS 没有块级作用域。只有全局作用域和函数作用域。(看以下两个例子理解)

if (true) {
    var number = 1
}
console.log(number) // 1
// 把var改成let,输出什么
var a = 100
function fn() {
    var a = 200
    console.log('fn', a)
}
console.log('global', a)
fn()

全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。

作用域注意点:

  • window对象的所有属性拥有全局作用域

  • 内层作用域可以访问外层作用域,反之不行

  • var声明的变量,在除了函数作用域之外,在其他块语句中不会创建独立作用域

  • let和const声明的变量存在块语句作用域,且不会变量提升

  • 同作用域下不能重复使用let、const声明同名变量,var可以,后者覆盖前者

  • for循环的条件语句的作用域与其循环体的作用域不同,条件语句块属于循环体的父级作用域

    // 以下语句使用let声明不报错,说明为不同作用域
    for (let i = 0; i < 5; i++) {
      let i = 5
    }
    

作用域链

作用域链也就是所谓的变量查找的范围

在当前作用域引用变量时,如果没有此变量,则会一路往父级作用域查找此变量,直到全局作用域,如果都没有,在非严格情况下会自动声明,所以是undefined,在严格条件下则会报错。

var a = 100
function fn() {
    var b = 200
    console.log(a)
    console.log(b)
}
fn()

执行上下文

执行 JavaScript 代码的环境

先看两个例子:

var foo = function () {
    console.log('foo1');
}
foo();  // foo1
var foo = function () {
    console.log('foo2');
}
foo(); // foo2
function foo() {
    console.log('foo1');
}
foo();  // foo2
function foo() {
    console.log('foo2');
}
foo(); // foo2

第一个例子中的变量提升,和第二个例子中的函数提升

JS 脚本执行之前,会先解析,解析的时候会先创建一个 全局执行上下文 环境。先把代码中即将执行的(内部函数的不算,因为你不知道函数何时执行)变量、函数声明都拿出来。变量先暂时赋值为undefined,函数则先声明好可使用,然后再开始正式执行程序。

下面执行的结果:

console.log(a)  // undefined
var a = 100

fn('zhangsan')  // 'zhangsan' 20
function fn(name) {
    age = 20
    console.log(name, age)
    var age
}

console.log(b); // 这里报错
// Uncaught ReferenceError: b is not defined
b = 100;

在解析阶段会将函数与变量提升,且函数的优先级比var声明的变量高:

console.log(foo) // foo(){}
function foo() {}
var foo = 5
// 另一个例子
var foo = 5
function foo() {} // 函数声明在解析阶段已经结束,在执行阶段没有效果
console.log(foo) // 5

执行上下文注意点:

  • 执行上下文在运行时确定,随时可能改变
  • 创建执行上下文时,会绑定当前this
  • 在var声明之前,调用var声明的变量时值为undefined

闭包

闭包可以使内部函数访问到外部函数的作用域,当函数被创建时即生成闭包。

  • 当你从函数内部返回一个内部函数时,返回的函数将会保留当前闭包,即当前词法环境

  • 闭包的作用域链包含着它自己的作用域,以及包含它父级函数的作用域和全局作用域

function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
var f1 = F1()
var a = 200
f1()

应用场景:使函数外部可以拿到函数内部的变量

promise

Promise 是异步编程的一种解决方案

使用方式:

const promise = new Promise(function(resolve, reject) {
  // 执行异步操作
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Promise对象有三种状态

  • pending: 等待中,或者进行中,表示还没有得到结果

  • resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行

  • rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行

这三种状态不受外界影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆。
resolve和reject都为一个函数,他们的作用分别是将状态修改为resolved和rejected。

promise中的 then:
Promise对象中的then方法,可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行。

function fn(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve();
        } else {
            reject();
        }
    }).then(function() {
        console.log('参数是一个number值');
    }, function() {
        console.log('参数不是一个number值');
    })
}

fn('hahha');
fn(1234);

then方法的执行结果也会返回一个Promise对象。因此我们可以进行then的链式执行,这也是解决回调地狱的主要方式。

var fn = function(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve(num);
        } else {
            reject('TypeError');
        }
    })
}

fn(2).then(function(num) {
    console.log('first: ' + num);
    return num + 1;
})
.then(function(num) {
    console.log('second: ' + num);
    return num + 1;
})
.then(function(num) {
    console.log('third: ' + num);
    return num + 1;
});

// 输出结果
first: 2
second: 3
third: 4

事件循环 eventLoop

我们都知道JS是单线程语言,代码是一行一行执行的。但碰到异步的时候就需要注意代码的执行顺序,会有所不一样。

setTimeout(function(){
    console.log('定时器开始啦')
});

new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log('执行then函数啦')
});

console.log('代码执行结束');

前端异步场景

  • 定时 setTimeout setInterval
  • process.nextTick(callback)——》node.js版的"setTimeout"
  • 网络请求,如 Ajax 加载

我们的程序运行时会有同步任务与异步任务,那程序需要有一个执行机制去处理这些任务,这个机制就是Event Loop(事件循环)。如图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccR6ivSN-1605010725388)(media/16045936766013/16046441690548.jpg)]

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。

  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。

  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。

  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

用代码解释:

let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:(response) => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');
  • ajax进入Event Table,注册回调函数success

  • 执行console.log(‘代码执行结束’)

  • ajax事件完成,回调函数success进入Event Queue

  • 主线程从Event Queue读取回调函数success并执行

更细的任务定义:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

事件循环的顺序,决定js代码的执行顺序:
进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

setTimeout(function() {
    console.log('setTimeout');
})

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');

分析:

  • 这段代码作为宏任务,进入主线程

  • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue

  • 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue

  • 遇到console.log(),立即执行

  • 整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行

  • 第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行

  • 结束

事件循环,宏任务,微任务的关系如图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnw3OYaf-1605010725394)(media/16045936766013/16046451954797.jpg)]

console.log('1');
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值