资讯专栏INFORMATION COLUMN

JavaScript——基础篇

yzd / 2722人阅读

摘要:这个过程中发生了绑定,举例如下小明小明,优先级这里不再一一举例对比优先级,直接给出结论绑定显示绑定隐式绑定默认绑定,有兴趣的同学可以实际比对一下。

把知识串一串,连成线,形成体系,从此走上大神之路啦,道路可能会曲折一点,但是咸鱼也要翻一翻身撒~

一、变量提升

何为变量提升?

</>复制代码

  1. 在JavaScript中,函数及变量的声明都将被提升到函数的最顶部 (函数声明的优先级高于变量声明的优先级)

这样就造成了一种不同于其他语言的现象,初看甚至觉得有些诡异:变量可以先使用再声明。举个栗子:

</>复制代码

  1. x = 1;
  2. console.log(x); // 1
  3. var x;

</>复制代码

  1. var name = "World!";
  2. (function () {
  3. if (typeof name === "undefined") {
  4. var name = "Jack";
  5. console.log("Goodbye " + name);
  6. } else {
  7. console.log("Hello " + name);
  8. }
  9. })();
  10. // 输出为 Goodbye Jack

为什么会出现这样情况呢?

</>复制代码

  1. 在JavaScript中,变量声明与赋值的分离,如 var a = 2 这个代码是分两步进行的,编译阶段之行变量声明 var a,在执行阶段进行赋值 a = 2,于是便造成了了变量声明提前情况的发生。

解析:对于第二个例子,由于存在变量提升,所以变量声明先于if判断,所以此时 name = undefined,于是便输出了 Goodbye Jack

二、隐式转换

前段时间,前端各大博客被一道题刷屏了

</>复制代码

  1. ++[[]][+[]]+[+[]]==10?

这道题怎么去解决呢,这就涉及到了JS的隐式转换相关的知识了。

简述隐式转换规则

对于原始类型:Undefined、Null、Boolean、Number、String

1,加号运算符(+):若后面的是数字,会直接相加得出结果,如 1 + 1 = 2;若后面的是字符类型,则会进行字符拼接,如 1 + "1" = "11"。
2,减号运算符(-):若后面的是数字,会直接相减得出结果;若后面的字符,则会将其转为数字类型,然后相减得出结果。
3,==运算负责:

undefined == null,结果为true

String == Boolean,需要将两个操作数同时转化为Number

String/Boolean == Number,需要将 String/Boolean 转为 Number

对于对象类型:Object
当对象与一个非对象进行比较等操作时,需要先将其转化为原始类型:首先调用 valueOf(),若结果是原始类型,则返回结果;若结果不是原始类型,则继续调用toSring(),返回其结果,若结果依然不是原始类型,则会抛出一个类型错误。

这里有一道很火的面试题,就是利用对象的类型转换原理:

</>复制代码

  1. a == 1 && a == 2 && a == 3
  2. //答案:
  3. var a = {num : 0};
  4. a.valueOf = function() {
  5. return ++a.num;
  6. }

以上大概为基础的隐式转换规则,可能不太完善,欢迎大家留言补充。好,有了这些准备后,让我们再来看下一开始的题目,让我们来逐步拆解:

</>复制代码

  1. 1,根据运算符的优先级,我们可以得到:(++[[]][+[]])+[+[]]
  2. 2,根据隐式转换,我们得到:(++[[]][0])+[0]
  3. 3,再次简化:(++[]) + [0]
  4. 4,这个时候就很明朗了,最终划为字符拼接 "1" + "0" = "10";
三,闭包 什么是闭包?

</>复制代码

  1. 简单的讲,闭包就是指有权访问另一个函数作用域中的变量的函数。

    MDN 上面这么说:闭包是一种特殊的对象,是函数和声明该函数的词法环境的组合。

产生一个闭包

</>复制代码

  1. function func() {
  2. var a = 1;
  3. return function fn() {
  4. console.log(a);
  5. }
  6. }
  7. func()(); // 1

这里函数func在调用后,其作用域并没有被销毁,依然可以被函数fn访问,所以输出为1。
这里有道很经典的面试题

</>复制代码

  1. function fun(n,o){
  2. console.log(o);
  3. return {
  4. fun: function(m){
  5. return fun(m,n);
  6. }
  7. };
  8. }
  9. var a = fun(0); // ?
  10. a.fun(1); // ?
  11. a.fun(2); // ?
  12. a.fun(3); // ?
  13. var b = fun(0).fun(1).fun(2).fun(3); // ?
  14. var c = fun(0).fun(1); // ?
  15. c.fun(2); // ?
  16. c.fun(3); // ?

</>复制代码

  1. undefined
    0
    0
    0
    undefined, 0, 1, 2
    undefined, 0
    1
    1

哈哈,有点绕,有兴趣的同学可以简单看下。

四,深、浅克隆

在实际开发或面试中,我们经常会碰到克隆的问题,这里我们简单的总结下。

浅克隆

浅克隆就是复制对象的引用,复制后的对象指向的都是同一个对象的引用,彼此之间的操作会互相影响

</>复制代码

  1. var a = [1,2,3];
  2. var b = a;
  3. b[3] = 4;
  4. console.log(a, b);
  5. // [1,2,3,4] [1,2,3,4]

实际开发中,若需要同步对象的变化,往往用的就是浅克隆,直接复制对象引用即可。

深克隆

开发过程中,我们往往需要断开对象引用,不影响原对象,这个时候我们就用到深克隆了,有如下方法:

方法一

</>复制代码

  1. JSON.parse(JSON.stringify()),对于大多数情况都可以用这种方法解决,一步到位。但是若对象中存在正则表达式类型、函数类型等的话,会出现问题:会直接丢失相应的值,同时如果对象中存在循环引用的情况也无法正确处理

</>复制代码

  1. let a = {name: "小明"};
  2. let b = JSON.parse(JSON.stringify(a));
  3. b.age = 18;
  4. console.log(a, b);
  5. // {name: "小明"} {name: "小明", age: 18}

方法二

</>复制代码

  1. 对于数组,我们可以利用Array的slice和concat方法来实现深克隆

</>复制代码

  1. let a = [1,2,3];
  2. let b = a.slice();
  3. b.push(4);
  4. console.log(a, b);
  5. // [1,2,3] [1,2,3,4]
  6. let a1 = [1,2,3];
  7. let b1 = a.concat(4);
  8. b1.push(5);
  9. console.log(a, b);
  10. // [1,2,3] [1,2,3,4,5]

方法三

</>复制代码

  1. jQuery中的extend复制方法:$.extend(true, target, obj)

</>复制代码

  1. let a = {name: "小明"};
  2. let b = {}
  3. $.extend(true, b, a);
  4. b.age = 18;
  5. console.log(a, b);
  6. // {name: "小明"} {name: "小明", age: 18}
五、this指向

关于this指向的问题,这里是有一定判断方法的:

</>复制代码

  1. 位置:this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里调用
    规则:默认绑定、隐式绑定、显式绑定、new绑定

我们在实际判断的时候,需要将二者结合起来。

1,默认规则

</>复制代码

  1. var name = "小明";
  2. function print() {
  3. console.log(this.name); // "小明"
  4. console.log(this); //window对象
  5. }
  6. print();
  7. // "小明"

解析:print()直接使用不带任何修饰的函数引用进行的调用,这个时候只能使用默认绑定规则,即this指向全局对象,所以此题输出为:"小明"

2,隐式绑定

</>复制代码

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a:2,
  6. foo:foo
  7. }
  8. obj.foo() // 2

解析:当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。所以此题的this被绑定到obj,于是this.a和obj.a是一样的。

这里有两点点需要注意:
1,对象属性引用链中只有上一层或者说最后一层在调用位置中起作用,举例如下:

</>复制代码

  1. function foo() {
  2. console.log(this.a);
  3. }
  4. var obj2 = {
  5. a: 10,
  6. foo: foo
  7. }
  8. var obj1 = {
  9. a: 2,
  10. obj2: obj2
  11. }
  12. obj1.obj2.foo(); // 10

2,隐式丢失:被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上。举例如下:

</>复制代码

  1. var a = "hello world";
  2. function foo(){
  3. console.log(this.a)
  4. }
  5. var obj = {
  6. a:1,
  7. foo:foo
  8. }
  9. var print = obj.foo;
  10. print(); // hello world

解析:虽然print是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,所以此时print()其实是一个不带任何修饰的函数调用,应用了隐式绑定。

3,显示绑定

利用call(),apply(),bind()强制绑定this指向的我们称之为显示绑定,举例如下:

</>复制代码

  1. function foo() {
  2. console.log(this.a);
  3. }
  4. var obj = {
  5. a:1
  6. }
  7. foo.call(obj); // 1

这里有一点需要注意:显示绑定依然无法解决上面提到的丢失绑定问题。举例如下:

</>复制代码

  1. var a = "hello world";
  2. function foo(){
  3. console.log(this.a)
  4. }
  5. var obj = {
  6. a:1,
  7. foo:foo
  8. }
  9. var print = obj.foo;
  10. print.bind(obj)
  11. print(); // hello world

这里有关call、apply、bind的具体用法就不再一一阐述了,后面的部分会详细讲解。

4,new绑定

这是最后一条this的绑定规则,使用new来调用函数,或者说发生构造函数调用时,会执行下面的操作:

创建(或者说构造)一个全新的对象

这个新对象会被执行[[Prototype]]连接

这个新对象会绑定到函数调用的this

如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

这个过程中发生了this绑定,举例如下:

</>复制代码

  1. function Person(name) {
  2. this.name = name;
  3. }
  4. var p = new Person("小明");
  5. console.log(p.name); // 小明
5,优先级

这里不再一一举例对比优先级,直接给出结论:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定,有兴趣的同学可以实际比对一下。
常规this指向判断流程:

函数是否在new中调用(new绑定) ? 如果是的话this绑定的就是新创建的对象

函数是否通过call、apply、bind(显示绑定) ? 如果是的话,this绑定的是指定的对象

函数是否在某个上下文对象中被调用(隐时绑定) ? 如果是的话,this绑定的是那个上下文对象

如果都不是的话,使用默认绑定

六、call、apply、bind 1,call()

定义:

</>复制代码

  1. 使用一个指定的this值和多带带给出的一个或多个参数来调用一个函数

语法:

</>复制代码

  1. fun.call(thisArg, arg1, arg2, ...)

参数:

</>复制代码

  1. thisArg:(1) 不传,或者传nullundefined, 函数中的this指向window对象
    (2) 传递另一个函数的函数名,函数中的this指向这个函数的引用,并不一定是该函数执行时真正的this
    (3) 值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象,如 StringNumberBoolean4)传递一个对象,函数中的this指向这个对象

    arg1, arg2, ...:指定的参数列表

举例如下:

</>复制代码

  1. var obj = {a: "小明"};
  2. function print() {
  3. console.log(this);
  4. }
  5. print.call(obj); // {a: "小明"}

实现call方法:

</>复制代码

  1. Function.prototype.selfCall = function(context, ...args) {
  2. let fn = this;
  3. context || (context = window);
  4. if (typeof fn !== "function") throw new TypeError("this is not function");
  5. let caller = Symbol("caller");
  6. context[caller] = fn;
  7. let res = context[caller](...args);
  8. delete context[caller];
  9. return res;
  10. }
2,apply()

apply()方法与call()方法相似,区别在于:call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

举例如下:

</>复制代码

  1. var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687];
  2. Math.max.apply(Math, arr); // 687
  3. Math.min.call(Math, ...arr); // -67
3,bind()

定义:

</>复制代码

  1. bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

语法:

</>复制代码

  1. function.bind(thisArg[, arg1[, arg2[, ...]]])

参数:

</>复制代码

  1. thisArg:调用绑定函数时作为this参数传递给目标函数的值
    arg1, arg2, ...:当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。

举例如下:

</>复制代码

  1. function print() {
  2. console.log(this);
  3. }
  4. let obj = {name: "小明"};
  5. let fn = print.bind(obj);
  6. fn(); // {name: "小明"}
七、Promise 1,什么是Promise?

</>复制代码

  1. Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一
2,创建Promise

方法一:new Promise

</>复制代码

  1. // 声明Promise后会立即执行
  2. var promise = new Promise(function(resolve, reject) {
  3. resolve("Hello");
  4. })
  5. console.log(promise); // Promise{: "Hello"}

方法二:直接创建

</>复制代码

  1. var promise = Promise.resolve("Hello");
  2. console.log(promise); // Promise{: "Hello"}
3,Promise状态

promise相当于一个状态机,具有三种状态:

pending

fulfilled

rejected

(1) promise 对象初始化状态为 pending

(2) 当调用resolve(成功),会由pending => fulfilled

(3) 当调用reject(失败),会由pending => rejected

</>复制代码

  1. 注:promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变
4,Promise API

1,Promise.prototype.then()

</>复制代码

  1. then() 方法返回一个 Promise 。它最多需要有两个参数:Promise 的成功 (onFulfilled) 和 失败情况 (onRejected) 的回调函数。

举例如下:

</>复制代码

  1. var promise = new Promise((resolve, reject) => {
  2. // 成功
  3. resolve("hello");
  4. });
  5. promise.then((res) => {
  6. console.log(res); // hello
  7. return Promise.reject("error");
  8. }).then((success) => {
  9. console.log("success", success);
  10. }, (err) => {
  11. console.log("error", err); // error error
  12. });

2,Promise.resolve()

</>复制代码

  1. Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。但如果这个值是个thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);如果传入的value本身就是promise对象,则该对象作为Promise.resolve方法的返回值返回;否则以该值为成功状态返回promise对象。

举例如下:

</>复制代码

  1. var promise = Promise.resolve("hello");
  2. promise.then((res) => {
  3. console.log(res);
  4. });
  5. // hello
  6. // Promise {: undefined}

此时promise的状态为题fulfilled

3,Promise.reject()

</>复制代码

  1. Promise.reject(reason)方法返回一个带有拒绝原因reason参数的Promise对象。

举例如下:

</>复制代码

  1. var promise = Promise.reject("error");
  2. promise.then((res) => {
  3. console.log("success", res);
  4. }, (res) => {
  5. console.log("error", res); // error error
  6. });

4,Promise.race()

</>复制代码

  1. Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

举例如下:

</>复制代码

  1. var promise1 = new Promise(function(resolve, reject) {
  2. setTimeout(resolve, 500, "one");
  3. });
  4. var promise2 = new Promise(function(resolve, reject) {
  5. setTimeout(resolve, 100, "two");
  6. });
  7. Promise.race([promise1, promise2]).then(function(value) {
  8. console.log(value); // two
  9. });

5,Promise.all()

</>复制代码

  1. Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

举例如下:

</>复制代码

  1. var promise1 = Promise.resolve(3);
  2. var promise2 = 42;
  3. var promise3 = new Promise(function(resolve, reject) {
  4. setTimeout(resolve, 100, "foo");
  5. });
  6. Promise.all([promise1, promise2, promise3]).then(function(values) {
  7. console.log(values); // [3, 42, "foo"]
  8. });

6,Promise.prototype.finally()

</>复制代码

  1. finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
    这避免了同样的语句需要在then()和catch()中各写一次的情况。

举例如下:

</>复制代码

  1. var promise = Promise.resolve("Hello");
  2. promise.then((res) => {
  3. console.log(res); // Hello
  4. }).finally((res) => {
  5. console.log("finally"); // finally
  6. })

7,Promise.prototype.catch()

</>复制代码

  1. catch() 方法返回一个Promise,并且处理拒绝的情况,捕获前面then中发送的异常

只要Promsie状态更改为reject或者抛出异常,都会进入catch方法。举例如下:

</>复制代码

  1. var promise1 = Promise.reject("Hello");
  2. promise1.then((res) => {
  3. console.log("success" + res);
  4. }).catch((res) => {
  5. console.log("catch " + res); // catch Hello
  6. })
八、Event Loop 1,前言

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

2,宏任务与微任务

在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。

宏任务:

script全部代码

setTimeout

setInterval

setImmediate (Node独有)

I/O

UI rendering (浏览器独有)

微任务:

process.nextTick (Node独有)

Promise

Object.observe

MutationObserver

3,浏览器的Event Loop

浏览器中的事件循环机制是什么样子呢?不废话,直接上图:

过程如下:

执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);

全局Script代码执行完毕后,调用栈Stack会清空;

检查微任务队列是否为空,若不为空,则取出位于队首的回调任务,放入调用栈Stack中执行,队列长度减1。如此循环往复,直至微任务队列为空

微任务队列为空后,检查宏任务队列是否为空,若不为空,则取出宏队列中位于队首的任务,放入Stack中执行,队列长度减1。如此循环往复,直至宏任务队列为空

举例如下:

</>复制代码

  1. console.log("script start");
  2. setTimeout(function() {
  3. console.log("setTimeout");
  4. }, 0);
  5. Promise.resolve().then(function() {
  6. console.log("promise1");
  7. }).then(function() {
  8. console.log("promise2");
  9. });
  10. console.log("script end");

答案如下:

</>复制代码

  1. script start、script end、promise1、promise2、setTimeout

解析:
step1

</>复制代码

  1. console.log("script start");

Stack Queue: [console]

Macrotask Queue: []

Microtask Queue: []

</>复制代码

  1. 打印结果:1

step2

</>复制代码

  1. setTimeout(function() {
  2. console.log("setTimeout");
  3. }, 0);

setTimeout属于宏任务,所以:

Stack Queue: [setTimeout]

Macrotask Queue: [callback1]

Microtask Queue: []

step3

</>复制代码

  1. Promise.resolve().then(function() {
  2. console.log("promise1");
  3. }).then(function() {
  4. console.log("promise2");
  5. });

promise属于微任务,所以有:
Stack Queue: [promise]

Macrotask Queue: [callback1]

Microtask Queue: [callback2]

step4

</>复制代码

  1. console.log("script end");

同步任务,直接执行

</>复制代码

  1. 打印结果:script end

step5
遍历微任务队列:Microtask Queue: [callback2],执行其函数

</>复制代码

  1. 打印顺序依次为:promise1、promise2

step6
微任务队列为空后,遍历宏任务队列:Macrotask Queue: [callback1],执行其回调函数

</>复制代码

  1. 打印结果:setTimeout

所以最终结果为:script start、script end、promise1、promise2、setTimeout

九、总结

由于时间比较仓促,本次总结还存在着许多遗漏,如JS原型,node环境下的Event Loop,函数柯里化等,也有许多理解不到位的情况,日后会逐渐完善与补充。

</>复制代码

  1. 注:如果文章中有不准确的地方,欢迎大家留言交流。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104793.html

相关文章

  • 【连载】前端个人文章整理-从基础到入门

    摘要:个人前端文章整理从最开始萌生写文章的想法,到着手开始写,再到现在已经一年的时间了,由于工作比较忙,更新缓慢,后面还是会继更新,现将已经写好的文章整理一个目录,方便更多的小伙伴去学习。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 个人前端文章整理 从最开始萌生写文章的想法,到着手...

    madthumb 评论0 收藏0
  • 基础巩固:JavaScript基础总结(基本概念)

    摘要:基础巩固基础总结使用已经好几年了,由于工作主要是做服务端开发,在工作中逐渐发现的使用范围原来越广泛。这里要注意,务必将基础部分掌握牢靠,磨刀不误砍柴功,只有将基础部分掌握并建立起系统的知识体系,在后面学习衍生的其他模式才能游刃有余。 基础巩固:JavaScript基础总结 使用JavaScript已经好几年了,由于工作主要是做服务端开发,在工作中逐渐发现JavaScript的使用范围原...

    YuboonaZhang 评论0 收藏0
  • JavaScript 基础知识】一关于 JavaScript 一些知识点的总结 —— 持续更新

    摘要:中基础数据类型数据类型名称数据类型说明只有一个值,即,声明变量的初始值。只有一个值,即,表示空指针,的值是派生的值。由零或多个位字符组成只有两个值,即和该类型使用来表示整数和浮点数。中的对象其实就是一组数据和功能的集合。 JavaScript 中基础数据类型 数据类型名称 数据类型说明 Undefined 只有一个值,即 undefined ,声明变量的初始值。 Nul...

    felix0913 评论0 收藏0
  • Javascript 异步编程-基础

    摘要:在前端这个领域里面,请求非常常见,相信每一个前端都写过下面的代码前提引入上面这段代码中的和被称为回调函数。多个请求希望有一个共同的回调响应继续使用最初的方法,假设有多个请求,希望在全部完成后执行回调函数。异步编程延迟对象篇 在前端这个领域里面,ajax请求非常常见,相信每一个前端er都写过下面的代码: // 前提引入jquery $.ajax({ type: get, ...

    huaixiaoz 评论0 收藏0
  • Javascript CI(1)- Jasmine 基础学习

    jasmine 简介 Jasmine 是一个含有丰富的断言库的测试框架。目前我用的最新的版本是:2.6 基础篇 命令行中环境中使用jasmine 安装 npm install -g jasmine //这里采用全局安装,好处是直接cmd就能用,也可以采用本地安装 初始化配置文件 jasmine init 生成的配置文件如下jasmine.json: { spec_dir: spec, //s...

    yanwei 评论0 收藏0

发表评论

0条评论

yzd

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<