你真的学会JavaScript了吗?

你真的学会JavaScript了吗?
请听题,请在评论区或在心中给出如下代码的执行结果:javascript

var arr = [];
for(var i = 0; i < 3; i++) {
    arr.push(function() {console.log(i)});
}
arr[0]();
arr[1]();
arr[2]();
parseInt("678tianlangstudio");
+"678tianlangstudio";
1 + "678tianlangstudio";

写好答案了吗?要公布答案了哦java


0

1ajax

2数据库

NaN编程

678tianlangstudiojson

1NaNwindows

若是你的答案跟上面的任意一个匹配上了,那恭喜你! 能够一块儿往下面看了,由于这是一份全错的答案。数组

想起上高中时有次英语老师拿了张考卷对着答案讲了半天,而后对咱们说:浏览器

这是B卷当作A卷的答案了,咱们从头讲。闭包

今后我就再不相信什么答案了,甚至遇到作不出的题我都怀疑是否是题出错了!慢慢的养成了独立思考的习惯,不知是好是坏。感谢老师苦心一片,至今教诲良言时犹在耳:

答案错了,从新开始

类型

JavaScript是动态类型语言,也就是说在代码编写时不须要声明指定其类型,变量类型在代码运行时才肯定.

基本类型

没有方法、不可变

包括:

  1. undefined
  2. null
  3. boolean
  4. number
  5. string
  6. (symbol)

复杂类型

  1. 对象(包括数组)
  2. 函数

可使用typeof判断数据类型:

typeof "Bill"              // 返回 "string"
typeof 3.14                // 返回 "number"
typeof true                // 返回 "boolean"
typeof false               // 返回 "boolean"
typeof x                   // 返回 "undefined" (假如 x 没有值)
typeof {name:'Bill', age:62} // 返回 "object"
typeof [1,2,3,4]             // 返回 "object" 
typeof null                  // 返回 "object"
typeof function myFunc(){}   // 返回 "function"

须要注意:

typeof null 返回object 历史缘由兼容原来的版本

typeof arr 返回 object 数组也是对象

typeof function testFun() {} 返回 function

类型转换

数据从一个类型转换到另外一个类型

强制类型转换和隐式类型转换

强制就是你得本身编写代码转换数据类型,隐式的就是有执行引擎帮你转换.

const x = 17;
const explicit = String(x); //使用强制类型转换把数字x转换为字符串explicit 
const implicit = x + ""; //引擎看到你要把个数字跟字符串相加,就帮你把数字转为字符串了.测试题最后一行相似.

操做符== 和 ===

== 会把两边的数据转换为同一种类型再比较是否相等,也就是忽略数据类型,好比:

1 == '1';//true

=== 先判断数据类型,数据类型不同就直接false, 不为其类焉问其值, 好比:

1 === `1`; //false

会被隐式转换为false的类型

  1. undefined
  2. null
  3. false
  4. +0, -0, NaN
  5. "" //空字符串

注意:

'false' -> true

会被隐私转换为true的类型

此处省略10000字

太多,由于除了上面的都是^-^

基本数据类型(Primitives) VS 复杂数据类型(Objects)

  1. 基本数据类型都是不可变的. 复杂数据类型可变而且使用索引操做
  2. 基本数据类型值传递,复杂数据类型使用引用传递(作为函数、方法参数时)

原型链

  1. 非基本数据类型都有关联属性和方法,好比:

    Array.prototype.push()

    String.prototype.toUpperCase()

注意:

String是复杂类型,'tianlang'这个是基本数据类型,有些时候它们行为同样,这是由于有自动转化,专业俗语自动装箱. 例如:

1778.toString();//会报错,由于基本类型没有方法。这么粗暴直接冒失的调用执行引擎也很差意思先装个箱
const x = 1788; 
x.toString(); //"1788"  斯文多了,自动装箱成对应的复杂类型走起。
x.__proto__;//Number...

基本数据类型对应的装箱复杂类型:

String()

Number()

Boolean()

Object()

(Symbol())

Java里也有这个概念,但JavaScript除了名字跟Java没半点关系.

  1. 每一个对象都持有一个原型的引用
  2. 跟对象关系越近的原型上定义的属性或方法优先级越高

做用域(Scope)

做用域就是变量生效的范围

使用var定义的变量有效范围是从定义开始到所在函数结束.

使用const,let 定义的变量有效范围是从定义开始到所在块结束.

定义提高:

​ 这个须要先说下程序执行过程:

  1. 执行引擎读取整个程序脚步
  2. 解析判断是否有语法错误,若是有错误报错退出执行
  3. 把函数保存到内存中
  4. 声明使用var定义的变量(注意:只有声明没有初始化赋值)

  5. .......

    这就是为何,咱们能够先调用一个函数后对这个函数进行定义,能够先使用一个使用var定义的变量,后面才使用var定义变量而不会报错。由于再执行时把函数定义的代码和var定义提高了.注意看3,4.

全局对象

能够把全局对象想象成一颗大树,在程序中定义的变量也好函数也好其实都挂在一个全局对象上。

在浏览器运行环境中,全局对象是window

在Node.js运行环境中,全局对象是global

闭包

咋一看,不知道是作什么的,再一看仍是不能从名字看出这货具体是作什么的.看下定义:

能够用来获取父函数中定义的变量的函数.

小蒙怡情大蒙伤身,仍是看下代码,回头看咱们前面提到的测试题1:

var arr = [];
for(var i = 0; i < 3; i++) {
    arr.push(function() {console.log(i)});
}
arr[0]();
arr[1]();
arr[2]();

输出的结果是:

3

3

3

意不意外?

要一次性理解这个可能有点难,咱们先来个简单的:

function makeHelloFunction() {
  var message = 'Hello!'

  function sayHello() {
    console.log(message)
  }

  return sayHello
}

const sayHello = makeHelloFunction()
console.log('typeof message:', typeof message) //undefind 由于message是定义在函数makeHelloFunction内部的,函数外边是不能访问的.
// but the function sayHello still references a variable called message

sayHello() //sayHello方法却能够打印出message的值,由于sayHello函数是定义在makeHelloFunction内部的.

看了上面的例子是否是对这货为何叫闭包有了些许的感悟。由于ES6前只能使用var定义变量,而这货定义的变量做用域原本就比较宽还有定义提高就更容易形成变量的做用域污染了.

什么是变量的做用域污染呢?

你定义了个变量name叫张三, 你的同事或者你在其它地方无心识的又定义了name叫李四,后来就变成了你觉得的张三不知道怎么就变成了李四了.

为了定义新变量name叫李四时不影响原来的张三,因而咱们能够把李四关起来定义,关起来也就是封起来。就像上面的演示代码,只有在函数中定义的函数sayHello才能访问到函数中定义的变量message,message对外部是不可见的,也就不会影响外部原来定义的变量.

上例中使用sayHello时还须要先调用makeHelloFunction建立,若是每次都这样岂不是挺麻烦的?

咱们可使用当即执行函数定义方式,就是来个小括号,后面的小括号里还能传递参数.就项这个样子:

const sayHello = (function makeHelloFunction() {//这里的函数名makeHelloFunction没有用了,能够删除掉
  var message = 'Hello!'

  function sayHello() {
    console.log(message)
  }

  return sayHello
})()
console.log('typeof message:', typeof message) //undefind 由于message是定义在函数makeHelloFunction内部的,函数外边是不能访问的.
// but the function sayHello still references a variable called message

sayHello() //sayHello方法却能够打印出message的值,由于sayHello函数是定义在makeHelloFunction内部的.

到此是否是还不知道为啥子测试1里输出的是3,3,3而不是0,1,2? 不要紧,能够先让它输出0,1,2.

var arr = [];
for(var i = 0; i < 3; i++) {
    arr.push((function(j) {
       return function() {
             console.log(j)
       }
    })(i));//这里咱们用了闭包和当即执行函数捕获当前变量i
}
arr[0]();
arr[1]();
arr[2]();

这下明白了吧,能够运行下这段代码看下效果,若是仍是不明白也能够加群讨论.

函数是一等公民

那何时, 人分三流九等,士农工商。可是人生而平等嘛,怎么体现平等呢?基本的权利你们应该都有吧.就像函数,虽然长得跟普通对象啊数字啊不同,可是收到的待遇倒是差很少地。能够定义变量把一个函数赋值给它,也能够把函数作为另外一个函数的参数使用,这个就厉害了,能够实现不少高级的功能.也称这种参数是函数的函数为高阶函数.

同步?异步?单线程?

  • JavaScript是单线程同步执行的语言.

  • 若是一个函数执行的时间比较长就会引发页面的卡顿,好比在运行个这样的函数,再去点击页面里的按钮你会发现没得反应了:

    function hang(seconds = 5) {
    const doneAt = Date.now() + seconds * 1000
    while(Date.now() < doneAt) {}
    }
  • 可是有些函数是能够异步执行的,好比:

    • setTimeout()
    • XMLHttpRequest(), jQuery.ajax(), fetch()
    • 调用数据库

    是否是很好奇JavaScript怎么便是同步单线程的语言又支持异步呢?这主要是内部维护了一个任务队列,若是关于这块您有什么想法也能够给加群给你们分享.

    回调

​ 原来处理异步代码的方式是添加回调函数,异步代码执行完成后会触发回调函数。好比这样:

function login(req, res, callback) {
  User.findOne({email: req.body.email}, function(err, user) {
    if (err) return callback(err)

    user.comparePassword(req.body.password, (err, isMatch) => {
      if (err) return callback(err)
      if (!isMatch) return res.status(401).send('Incorrect password')

      // add relevant data to token
      const payload = {id: user._id, email: user.email}

      jwt.sign(payload, config.secret, {}, function(err, token) {
        if (err) return callback(err)

        user.token = token
        user.save((err) => {
          if (err) return callback(err)
          res.json({token})
        })
      })
    })
  })
}

异步函数多了,咱们须要一层一层的嵌套回调函数,就成了回调黑洞,这样的代码读起来麻烦,维护起来也麻烦,一不当心就不知道哪里少敲了个括号. 因而就引入了Promise编程模型,减小回调嵌套,就像这个样子:

fetch(url)
  .then(function(res) {
    return res.json()
  })
  .then(function(json) {
    return ({
      importantData: json.importantData,
    })
  })
  .then(function(data) {
    console.log(data)
  })
  .catch(function(err) {
    // handle error
  })

是否是清爽了不少?

后来ES2017又新增了async/await关键字用于支持异步编程,就像这样:

async function login(req, res, callback) {
  try {
    const user = await User.findOne({email: req.body.email})
    const isMatch = await user.comparePassword(req.body.password)

    if (!isMatch) return res.status(401).send('Incorrect password')

    const payload = {id: user._id, email: user.email}
    const token = await jwt.sign(payload, config.secret, {})

    user.token = token
    const success = await user.save()

    res.json({token})
  } catch (err) {
    callback(err)
  }
}

是否是跟Rust Async有点像? 学语言嘛,这也是为何我老是劝新同窗要学一门语言学通再学其它的。语言嘛总有相通之处,虽不能一通百通但也是有大量可复用之处的.

this

初接触Javascript会以为this真是飘忽不定,特别是在事件处理时使用到this,经常搞不清它这个纠结指的是那个;

这里总结几条规则:

  1. 函数中的this指向函数的调用时所在对象,如:

    obj.fun();// fun中的this指向boj

  2. 若是没有对象那在严格模式下this就指向全局对象windows或者global

  3. 可使用bind,call, apply显示绑定this到某个对象.

该煮饭了,有时间再单独写篇this ,欢迎关注

相关文章
相关标签/搜索