故事的开始
- 一直有这样一个想法,分享交流一些东西,但是由于种种原因迟迟没有开始,今天终于要和大家正式见面了,想了好多名字,最终还是叫了这个很俗的《从入门到放弃》,哈哈,可见对于'程序猿'来说,这是多么痛的领悟!
- 当然分享不是真的让小白放弃,这个系列打算从JS高级、CSS3、框架基本使用(Vue React)、网络后台相关等方面整理一些干货,供初入职场的小白快速掌握面试基本方向,因为基本功真的很重要!
关于JavaScript
作为开篇还是有必要讲一下JavaScript到底和Java有什么关系,为什么叫JavaScript
-
1994年,网景公司(Netscape)发布了Navigator浏览器,这是历史上第一个比较成熟的网络浏览器,轰动一时。但是,这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。网景公司急需一种网页脚本语言,使得浏览器可以与访问者互动。
-
网景公司做出决策,未来的网页脚本语言必须"看上去与Java足够相似",但是比Java简单,使得非专业的网页作者也能很快上手。这个决策实际上将Perl、Python、Tcl、Scheme等非面向对象编程的语言都排除在外了,但是Brendan EichJava对Java一点兴趣也没有,仅仅用了十天的时间就将就JavaScript设计出来了。
-
所以,Javascript语言实际上是两种语言风格的混合产物——(简化的)函数式编程+(简化的)面向对象编程。这是由Brendan Eich(函数式编程)与网景公司(面向对象编程)共同决定的。
-
JS的特点
- 借鉴C语言的基本语法
- 借鉴Java语言的数据类型和内存管理
- 借鉴Scheme语言,将函数提升到"第一等公民"(first class)的地位
- 借鉴Self语言,使用基于原型(prototype)的继承机制
-
JavaScript起初原名叫LiveScript,1995年12月,Netscape 公司与 Sun 公司(Java 语言的发明者和所有者)达成协议,后者允许将这种语言叫做 JavaScript。这样一来,Netscape 公司可以借助 Java 语言的声势,而 Sun 公司则将自己的影响力扩展到了浏览器。
-
好景不长,1996年微软公司在其最新的IE3('百年大计,毁于IE')浏览器中引入了自己对JavaScript的实现JScript,由于市面上存在两个版本的JavaScript,为了确保不同浏览器上运行的JavaScript标准一致,所以几个公司定制了JS的标准命名ECMAScript。
-
ECMAScript是一个标准,而这个标准需要由各个厂商去实现,以下是不同浏览器的实现方式
浏览器 JavaScript实现方式 FireFox SpiderMonkey Internet Explorer JScript/Chakra Chrome v8 -
我们已经知道ECMAScript是JavaScript标准,所以一般情况下这两个词认为是一个意思,但是实际上JavaScript的含义要更大一些,一个完整的JavaScript由ECMAScript、DOM(Document Object Model)、BOM(Browser Object Model)三部分组成。
-
好了,以上就是关于JavaScript一些有趣的历史,十天写出的语言让我们从入门到放弃,事实告诉我们'天才是百分之一的灵感,加百分之九十九的汗水'这句话其实是骗人的,爱迪生的原句其实是两句话,'天才是百分之一的灵感,加百分之九十九的汗水,但那百分之一的灵感往往比百分之九十九的汗水来的重要',是不是扎心了老铁~~没有关系,有了我这本《JS从入门到放弃》,你可以对JS大声说'我劝天公重抖擞,魑魅魍魉哪里走!'
数据类型
分类
原始数据类型(基本数据类型)
- String, Number, Boolean, null, undefined, Symbol(ES6)
- BigInt(ES10)新Chrome已经支持
对象类型(引用数据类型)
- Object
- 在ESMAScript关于类型的定义中,只给出了Object类型,但是实际上我们平时使用的很多类型的变量,并不是由Object构造的,但是它们原型链的终点都是Object,这些类型都属于引用类型
- Array 数组
- Date 日期
- RegExp 正则
- Function 函数
内置对象(包装类型)
- Number, Boolean, String
判断
typeof
| 数据类型 | 返回值 |
|---|---|
| String | string |
| Number | number |
| Boolean | boolean |
| 未定义 | undefined |
| Function | function |
| Symbol | symbol |
| BigInt | bigint |
| Object | object |
| Array | object |
| null | object |
- 可见typeof可以区别String、Number、Boolean、Symbol、BigInt、undefined、Function,但是不能区别null和对象,以及一般对象和数组,所以出现了instanceof运算符
instanceof
- instanceof专门用来判断对象的类型,Object,Array,Function
- instanceof运算符的判断原理
- A instanceof B
- 其实就是看B的原型对象是否出现在A对象的原型链上,如果出现返回true,否则返回false,这个在后续讲原型链的时候细谈
易错点
- undefined和null的区别
- undefined是定义过了,但是还没有赋值,null是定义了也赋值了,value就是null
- undefined == null ---> true
- undefined === null ---> false
- 什么时候给变量赋值为null
var obj = null // 将obj指向一个对象,但该对象此时还没有确定
obj = null // 让a指向的对象成为垃圾对象时
- 基本数据类型与包装类
let num1 = Number(1) // 1 基本数据类型
let num2 = new Number(1) // Number{1} 对象类型
var a = 1
a.b = 2
console.log(a,a.b) // 1 undefined
// 关于引用类型和包装类型的主要区别就是对象的生命周期,以及自动进行的装箱和拆箱的操作,上面第二个就是经典的例子
// 这里就不展开诉述了,如果详细叙述的话又会带出很多其他的内容,(才写到这里我就已经感觉到JS的深不见底了,任何一个问题,如果真要刨根问底,你就能体会到JS的深不见底了,入坑需谨慎~~)
- 原始数据类型的值永远不会改变(MND官方文档原话)
- 变量有指针,基本数据类型也有自己的内存地址,我们都知道引用数据类型保存在堆内存中,变量和基本数据类型保存在栈内存中,引用数据类型有自己的内存地址,变量通过指针指向对应的内存地址,这毫无疑问。其实基本数据类型也有自己的内存地址
var a = 1
var b = 1
b = 2
console.log(a,b) // 1 2 这就说明基本数据类型的值永远不会改变,重新赋值只是指向了新的内存地址
类型转换
- String
- String() 函数
- toString() 方法,此方法不能用于null和undefined
- 加空串,原理同String()
var a = 123
a = a + ''
- Number
- Number() 函数
- parseInt()和parseFloat() 专门对付字符串,特例null ---> 0 ,undefined ---> NaN
- -0 ,* 1 ,/ 1 也可以将其他类型转为Number
- 一元的加,原理同Number()
var a = '123'
a = + a
- 在JS中使用的是64位的双精度浮点数编码,所以它的占位符占1位,指数位占11位,尾数位占52位,会存在精度丢失的问题,这就出现了JS中经典的0.1+0.2不等于0.3的问题了,同时还有最大数字Infinity,非数字NaN,这些都属于Number
- Boolean
- Boolean() 函数
- !! 原理同Boolean()
- 以下是转换的特殊情况
| Number | 除了0和NaN都是true |
|---|---|
| String | 除了空串都是true |
| null | false |
| undefined | false |
变量提升
- 首先我们都知道变量的赋值操作可以分为三个阶段,
- 创建变量
- 初始化变量,也就是申明变量
- 真正的赋值
- 变量提升其实是变量申明提升,比如var a = 1,在这行代码执行之前访问a 变量是undefined,而不会报错,我们知道如果访问一个没有申明的变量时,会沿着作用域链找,如果没有找到会报错,(补充:但是访问一个变量的某个属性时如果没有就不会报错,而是undefined,这就说明 js 是一门动态型语言)
- 回到变量提升,var a = 1其实是三步,先是创建一个变量a ,之后初始化a,也就是申明变量var a ; 最后是a = 1 ;在代码执行之前,js引擎会将所有的变量申明提前解析,这就是我们通常说的预解析,所以不会报错,而是undefined
- 当然不只是var 的变量会提升,函数申明式function同样也会提升,而且函数提升不光会初始化,第三步真正的赋值操作也会提升,所以我们定义一个函数,不管在哪里都可以直接调用这个函数,而不是必须写在函数之后,当然前提是通过函数申明式创建的函数,如果你是通过函数表达式的形式创建,那和var 一个变量的效果其实是一样的,会得undefined,如果调用也会报错,提示'xxx is not a function '
- 同时函数提升也有特殊的地方,关于函数变量提升,在全局变量对象中的函数申明式都会变量提升,但是在局部作用域中,比如函数内部又有一个函数,内部这个函数如果不return或者不调用的话,就不会变量提升,其实js这样的设计,我个人的理解是因为全局变量对象中的代码会很多,如果我们和局部作用域中的函数一样,在有调用语句出现后才会预解析的话,这样效率会变低,但是因为局部作用域中的代码相对较少,所以会这样设计
- 特殊注意点就是如果函数和变量同名时,预解析的结果永远是函数,原因有两种说法,通认为是第一种,但是个人认为没有说服力,第二种解释也能解释通,供大家参考
- 函数优先级高于var
- 顺序解析,先var fn = 1,结果是var fn ,之后function fn () {} ,直接定义函数,赋值 ,如果先是unction fn () {},直接定义了函数,之后再看到var fu = 1 ,不会再看了,结果还是function,所以我认为这种顺序解析也对
- 最后一点,ES6 中 let 和 const 其实也存在变量提升,只是在创建过程中被提升了,但是初始化没有提升,不能提前访问,会报错Cannot access 'a' before initialization,let不能重复申明,在块级作用域中有效,const 定义一个常量,不能修改,其他和let一样
故事的最后
- 今天就先到这里了,主要是变量的基本知识以及面试中常问的预解析变量提升和函数提升。说说第一篇的感受,当我们没有亲自做一件事情时总感觉很简单,不就是怎么样嘛,一堆理由,我也是这样的。但是今天写下第一篇才知道看似简单的东西,总结输出是一个很不容易的事情,敢于开始就是成功了一半,哈哈~
- 最后总结分享这些也是学习的过程,前段深不见底,和后端语言最大的区别就是门槛低,但是深不见底,包罗万象,从本周开始坚持每周更新一篇,欢迎大家留言转发,如有错误请指正,互相学习,谢谢!
清新蛋花汤
- 熟悉我的朋友都知道我个人比较感性,总喜欢感慨一些东西,所以每篇最后给大家来一点小小的鸡汤,其实也不算鸡汤,就叫它清新蛋花汤
学习备考就像黑夜里洗衣服,你不知道洗干净了没有,只有一遍一遍的去洗。等到上了考场那一刻,灯光亮了,你发现只要你真的洗过了,那件衣服光亮如新。而你以后,每次穿上这件衣服都会想起那段岁月。