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";
写好答案了吗?要公布答案了哦
0
1
2
NaN
678tianlangstudio
1NaN
如果你的答案跟上面的任意一个匹配上了,那恭喜你! 可以一起往下面看了,因为这是一份全错的答案。
想起上高中时有次英语老师拿了张考卷对着答案讲了半天,然后对我们说:
这是B卷看成A卷的答案了,我们从头讲。
从此我就再不相信什么答案了,甚至遇到做不出的题我都怀疑是不是题出错了!慢慢的养成了独立思考的习惯,不知是好是坏。感谢老师苦心一片,至今教诲良言时犹在耳:
JavaScript是动态类型语言,也就是说在代码编写时不需要声明指定其类型,变量类型在代码运行时才确定.
没有方法、不可变
包括:
可以使用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'
-> true
此处省略10000字
太多,因为除了上面的都是-
非基本数据类型都有关联属性和方法,比如:
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没半点关系.
作用域就是变量生效的范围
使用
var
定义的变量有效范围是从定义开始到所在函数结束.
使用
const
,let
定义的变量有效范围是从定义开始到所在块结束.
这个需要先说下程序执行过程:
执行引擎读取整个程序脚步
解析判断是否有语法错误,如果有错误报错退出执行
把函数保存到内存中
声明使用var定义的变量(注意:只有声明没有初始化赋值)
…
这就是为什么,我们可以先调用一个函数后对这个函数进行定义,可以先使用一个使用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) {}
}
但是有些函数是可以异步执行的,比如:
是不是很好奇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有点像? 学语言嘛,这也是为什么我总是劝新同学要学一门语言学通再学其它的。语言嘛总有相通之处,虽不能一通百通但也是有大量可复用之处的.
初接触Javascript会觉得this
真是飘忽不定,特别是在事件处理时使用到this
,常常搞不清它这个
纠结指的是那个
;
这里总结几条规则:
函数中的this
指向函数的调用时所在对象,如:
obj.fun();// fun中的this指向boj
如果没有对象那在严格模式下this
就指向全局对象windows
或者global
可以使用bind
,call
, apply
显示绑定this
到某个对象.
该煮饭了,有时间再单独写篇this
,欢迎关注