一、变量的类型和计算
1、JavaScript数据类型
ECMAScript标准规定了7种数据类型,其把这7种数据类型又分为两种:原始类型和对象类型。
原始类型:
- Null:只包含一个值:null
- Undefined:只包含一个值:undefined
- Boolean:包含两个值:true和false
- Number:整数或浮点数,还有一些特殊值(-Infinity、+Infinity、NaN)
- String:一串表示文本值的字符序列
- Symbol:一种实例是唯一且不可改变的数据类型
- BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。
对象类型:
- Object:自己分一类丝毫不过分,除了常用的Object,Array、Function等都属于特殊的对象
2、变量类型
按照存储方式分为值类型和引用类型
- 值类型:undefined、string、number、Boolean
- 引用类型 :数组、对象、null、函数 ( 引用类型有个特点可以无限扩展属性 )
//值类型
var a = 100;
var b = a;
a = 200;
console.log(b)//100
//引用类型
var a = {age:20};
var b = a;
b.age = 20;
console.log(a.age);//21
值类型赋值时相当于把变量赋值一份,所以两个变量啊a,b是隔离的,互相不受影响;
引用类型相当于指向同一个对象,a,b指向同一个对象,那么修改一个变量的属性相当于修改引用对象的属性,所以a,b会互相影响;
typeof undefined;//undefined
typeof 'abc';//string
typeof 123;//number
typeof true;//boolean
typeof {};//object
typeof [];//object
typeof null;//object
typeof console.log;//function
//前面4个是值类型,后面的引用类型
- typeof只能区分值类型,不能区分引用类型;
- typeof null是引用类型是因为null也是一个指针,但是它指向的是一个空对象;
- typeof console.log可以检测出是function类型是因为函数类型是js中一个很重要的存在。
不适用场景
当你用 typeof 来判断引用类型时似乎显得有些乏力了:
typeof [] // object
typeof {} // object
typeof new Date() // object
typeof /^\d*$/; // object
除函数外所有的引用类型都会被判定为object。
另外typeof null === 'object’也会让人感到头痛,这是在JavaScript初版就流传下来的bug,后面由于修改会造成大量的兼容问题就一直没有被修复…
3、变量计算
⑴、强制类型转换
- 字符串拼接
- == 运算符
- if语句
- 逻辑运算
100 == ‘100’//true
0 == ''//true
null == undefined // true
var a = true;
if(a){
//}
var b = 100;
if(b){}
//都会发生强制类型转换
console.log(10 && 0)//0
console.log('' || 'abc')//abc
console.log( !window.abc )//true
//判断一个变量会当做true还是false
var a = 100;
console.log(!!a);
⑵、何时使用===
,何时使用==
仅有这种情况使用==
if(obj.a==null){
// 此时条件相当于obj.a===null||obj.a===undefined,简写形式
// 这是jQuery源码中推荐的写法
}
除此之外,其它情况均建议使用===
4、JS内置函数 - 数据封装类对象
Object、array、bollean、number、string、functiondate、regexp/正则表达式、error
⑴、数值方法
数值对象仅包含了几个任何对象均定义的默认方法
方法名称 | 描述 |
---|---|
constructor() | 返回创建该对象实例的函数。默认是数值对象 |
toExponential() | 强制将数值以指数形式显示 |
toFixed() | 可把 Number 四舍五入为指定小数位数的数字 |
toLocaleString() | 以字符串的形式返回当前对象的值。该字符串适用于宿主环境的当前区域设置 |
toPrecision() | 定义显示一个数多少位数(包括位小数的左和右) |
toString() | 返回该数值的字符串格式 |
valueOf() | 返回数值 |
⑵、布尔方法
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
toSource() | 返回一个包含布尔对象的源字符串;可以使用这个字符串创建一个等价的对象 |
toString() | 按照布尔结果返回“true”或 “fales” |
valueOf() | 返回布尔对象的原始值 |
⑶、字符串方法
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
charAt() | 返回指定位置的字符。 |
charCodeAt() | 返回指定位置字符的数值。 |
concat() | 返回布尔对象的原始值。 |
indexOf() | 返回匹配子字符串第一次出现的位置,如果不存在就返回-1。 |
lastIndexOf() | 返回匹配子字符串最后一次出现的位置,如果不存在就返回-1。 |
localeCompare() | 比较两个字符串,并返回以数字形式表示的比较结果。 |
length() | 返回字符串的长度。 |
match() | 用于匹配正则表达式。 |
replace() | 通过与正则表达式找到子串位置,并替换为新指定的字符串。 |
search() | 执行与一个正则表达式进行的搜索。 |
slice() | 提取并返回一个子串。 |
split() | 将字符串分割成多个子串,并存储进字符串数组。 |
substr() | 返回字符串中指定位置,指定长度的子串。 |
toLocaleLowerCase() | 大写字符转为小写,同时尊重当前语言环境。 |
toLocaleUpperCase() | 小写字符转为大写,同时尊重当前语言环境。 |
toLowerCase() | 大写字符转为小写。 |
toString() | 返回表示该对象的一个字符串。 |
toUpperCase() | 小写字符转为大写。 |
valueOf() | 返回指定对象的原始数值。 |
⑷、HTML字符串格式化工具
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
anchor() | 创建一个HTML锚作为一个超文本的目标。 |
big() | 创建一个以“大”字体表示的字符串,好比置于标签中一样。 |
blink() | 创建一个闪烁的字符串,好比置于标签中一样。 |
bold() | 创建一个粗体显示的字符串,好比置于标签中一样。 |
fixed() | 创建一个打字机字体显示的字符串,好比置于标签中一样。 |
fontcolor() | 创建一个特定字体颜色显示的字符串,好比置于标签中一样。 |
fontsize() | 创建一个特定字体大小显示的字符串,好比置于标签中一样。 |
italics() | 创建一个斜体显示的字符串,好比置于标签中一样。 |
link() | 创建HTML超级链接。 |
small() | 创建一个小字体显示的字符串,好比置于标签中一样。 |
strike() | 创建一个加了删除线显示的字符串,好比置于标签中一样。 |
sub() | 以下标的方式显示,好比置于标签中一样。 |
sup() | 以上标的方式显示,好比置于标签中一样。 |
⑸、数组方法
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
concat() | 返回两个数据经过联接后的数组。 |
every() | 如何数组内的元素均满足某测试函数,那么就返回true。 |
filter() | 原来的数组中能过通过过滤器的元素组成一个新的数组返回。 |
forEach() | 调用一个函数来处理数组中的每个元素。 |
indexOf() | 返回与指定元素相匹配的第一个位置,如果不存在就返回-1 |
join() | 连接数组中所有的元素,返回一个字符串 |
lastIndexOf() | 返回与指定元素相匹配的最后一个位置,如果不存在就返回-1。 |
map() | 调用一个函数处理数组中的每一个元素,将生成的结果组成一个新的数组,并返回 |
pop() | 返回数组中的最后一个元素,并删除。 |
push() | 在数组的最后增加一个元素,并返回新数组的长度 |
reduce() | 对数组中的所有元素(从左到右)调用指定的回调函数。 该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。 |
reduceRight() | 对数组中的所有元素(从右到左)调用指定的回调函数。 该回调函数的返回值为累积结果,并且此返回值在下一次调用该回调函数时作为参数提供。 |
reverse() | 反转数组元素的顺序——第一个成为最后一个,最后成为第一。 |
shift() | 删除数组的第一个元素并返回。 |
slice() | 提取一段数组并返回一个新的数组 |
some() | 如果存在一个元素满足所提供的测试函数,就返回true。 |
toSource() | 代表一个对象的源代码。 |
sort() | 对数组中的元素排序。 |
splice() | 增删数组中的元素。 |
toString() | 返回一个表示数组及其元素的字符串。 |
unshift() | 在数组的首部添加新的元素,并且返回新数组的长度 |
⑹、时期方法
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
Date() | 返回今天的日期及时间。 |
getDate() | 按照本地模式返回指定日期是哪日。 |
getDay() | 按照本地模式返回指定日期是周几。 |
getFullYear() | 按照本地模式返回指定日期是哪一年。 |
getMilliseconds() | 按照本地模式返回指定日期是几毫秒。 |
getMinutes() | 按照本地模式返回指定日期是几分。 |
getMonth() | 按照本地模式返回指定日期的月份。 |
getSeconds() | 按照本地模式返回指定日期是几秒。 |
getTime() | 按照本地模式当前的格林威治时间。 |
getTimezoneOffset() | 以分钟为单位返回时间偏差。 |
getUTCDate() | 按照世界统一时间返回指定日期是几号。 |
getUTCDay() | 按照世界统一时间返回指定日期是周几。 |
getUTCFullYear() | 按照世界统一时间返回指定日的年份。 |
getUTCHours() | 按照世界统一时间返回指定日期是几时。 |
getUTCMilliseconds() | 按照世界统一时间返回指定日期的毫秒数。 |
getUTCMinutes() | 按照世界统一时间返回指定日期的分钟数。 |
getUTCMonth() | 按照世界统一时间返回指定日期的月份。 |
getUTCSeconds() | 按照世界统一时间返回指定日期的秒数。 |
setDate() | 按照本地模式设置日期。 |
setFullYear() | 按照本地模式设置年份。 |
setHours() | 按照本地模式设置小时。 |
setMilliseconds() | 按照本地模式设置毫秒数。 |
setMinutes() | 按照本地模式设置分钟数。 |
setMonth() | 按照本地模式设置月份。 |
setSeconds() | 按照本地模式设置秒数。 |
setTime() | 按照格林威治格式设置毫秒数。 |
setUTCDate() | 按照世界统一时间设置日期。 |
setUTCFullYear() | 按照世界统一时间设置年份。 |
setUTCHours() | 按照世界统一时间设置小时数。 |
setUTCMilliseconds() | 按照世界统一时间设置毫秒数。 |
setUTCMinutes() | 按照世界统一时间设置分钟数。 |
setUTCMonth() | 按照世界统一时间设置月份。 |
setUTCSeconds() | 按照世界统一时间设置秒数。 |
toDateString() | 返回日期的字符串。 |
toLocaleDateString() | 按照本地模式,返回日期的字符串。 |
toLocaleFormat() | 使用格式字符串,将日期转换为一个字符串。 |
toLocaleString() | 使用当前语言环境的约定将日期转换为一个字符串。 |
toLocaleTimeString() | 返回日期的“时间”部分作为一个字符串,使用当前语言环境的约定。 |
toSource() | 返回一个字符串代表一个等价的日期对象的来源,您可以使用这个值来创建一个新的对象。 |
toString() | 返回一个字符串代表指定的日期对象。 |
toTimeString() | 返回日期的“时间”部分以字符串形式。 |
toUTCString() | 使用通用时间约定,将日期转换为一个字符串。 |
valueOf() | 返回日期对象的原始值。 |
⑺、日期静态方法
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
Date.parse( ) | 解析并返回日期和时间的字符串表示的内部毫秒表示日期。 |
Date.UTC( ) | 返回指定的毫秒表示UTC日期和时间。 |
⑻、数学方法
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
abs() | 返回数值的绝对值。 |
acos() | 返回一个数值的arccos值。 |
asin() | 返回一个数值的arcsin值。 |
atan() | 返回一个数值的arctan值。 |
ceil() | 返回大于或等于整数最小的一个数字。 |
cos() | 返回一个数值的cos值。 |
exp() | 返回指数。 |
floor() | 返回小于等于一个数的最大数。 |
log() | 返回一个数值以e为底的对数。 |
max() | 返回最大值。 |
min() | 返回最小值。 |
pow() | 返回以e为底的幂。 |
random() | 返回0和1之间的一个伪随机数。 |
round() | 返回四舍五入后的值。 |
sin() | 返回sin值。 |
sqrt() | 返回一个整数的平方根。 |
tan() | 返回一个数值的tan值。 |
toSource() | 返回字符串“Manth”。 |
⑼、正则表达式方法
如下为相关方法及描述的列表:
方法名称 | 描述 |
---|---|
exec() | 执行一个字符串的搜索匹配。 |
test() | 测试匹配的字符串参数。 |
toSource() | 返回一个对象文字代表指定的对象;您可以使用这个值来创建一个新的对象。 |
toString() | 返回一个字符串代表指定的对象 |
5、JSON
-
JSON指的是JavaScript对象表示法(javascript object notation)
-
JSON是轻量级的文本数据交互格式,并不是编程语言
-
JSON独立于语言存在
-
JSON具有自我描述性,更容易理解
-
JSON可以将JavaScript对象中表示的一组数据转换为字符串,然后就可以在函数之间轻松地传递这个字符串,或者在异步应用程序中将字符串从Web客户机传递给服务器端程序
-
它是基于JavaScript的一个子集。数据格式简单,易于读写,占用带宽小。
-
JSON简单说就是JavaScript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构。
对象:对象在js中表示为”{}”括起来的内容,数据结构为{key:value,key:value,…}的键值对的结构,在面向对象的语言中,key为对象的属性,value为对应的属性值,所以很容易理解,取值方法为对象.key 获取属性值,这个属性值的类型可以是数字、字符串、数组、对象几种。
数组:数组在js中是中括号”[]”括起来的内容,数据结构为[“java”,”javascript”,”vb”,…],取值方式和所有语言中一样,使用索引获取,字段值的类型可以是数字、字符串、数组、对象几种。经过对象、数组2种结构就可以组合成复杂的数据结构了。
6、题目
⑴、题目
- JS中使用
typeof
能得到的哪些类型 - 何时使用
===
何时使用==
- JS中有哪些内置函数
- JS变量按照存储方式区分为哪些类型,并描述其特点
- 如何理解JSON
⑵、知识点
①、变量类型
JS变量最基本的分类就是值类型和引用类型,两者有何区别呢,可以通过例子看出来。
以下是值类型的一个例子
var a = 100
var b = a
a = 200
console.log(b)
以下是引用类型的一个例子
var a = {age:20}
var b = a
b.age = 21
console.log(a.age)
typeof
可以知道一个值类型是什么类型,而对于引用类型,它就无能为力了。但是它可以将引用类型区分出function
。
JS 中的某些表现,就已经体现了函数的特殊意义,例如:对象和数组,JS中没有内置的(不考虑 JS-WEB-API),而函数却内置了很多,例如 Object
Array
Boolean
Number
String
Function
Date
RegExp
Error
。这些函数 JS 本身就有,因为他们是基础数据类型的构造函数
typeof
可以区分类型有number
string
boolean
undefined
(值类型) function
object
(引用类型)
// 特例
typeof null // object 因为 null 也是引用类型。null 就相当于引用类型中的 undefined
那么针对第二个例子,如何将a
的内容复制给b
,并且保证b
的修改不会影响到a
呢?那就需要深度复制,意思就是对a
的属性进行递归遍历,再依次复制。
②、变量计算
组简单的计算,就是数字的加减乘除、字符串的拼接和替换,这个太简单了。但是 JS 在值类型的运算过程中,特别需要注意和利用强制类型转换这一特性,有以下场景:
- 字符串拼接
==
- 逻辑运算(
if
!
||
&&
)
字符串拼接最常见的错误如下,特别要注意。如何规避呢 - 对进行计算的变量通过typeof
来判断类型
var a = 100 + 10 // 110
var b = 100 + '10' // '10010'
接下来,==
也会进行强制类型转换,如
100 == '100' // true
0 == '' // true
null == undefined // true
针对100 == '100'
就是和拼接字符串一样的类型转换,而针对下面两个例子,就是一个逻辑运算上的强制类型转换。所以,所有的地方都要使用===
而不能使用==
,但是 jquery 源码有一个特例,就是obj.a == null
,使用很简洁。
最后,逻辑运算中的强制类型转换,先以if
为例说明
var a = true
if (a) {
// ....
}
var b = 100
if (b) {
// ....
}
var c = ''
if (c) {
// ....
}
所有经过if
判断的变量,都会进行逻辑运算的强制类型转换,转换为true
或者false
。
console.log(10 && 0) // 0
console.log('' || 'abc') // 'abc'
console.log(!window.abc) // true
// 判断一个变量会被当做 true 还是 false
var a = 100
console.log(!!a)
日常开发中,以下变量会被转换为false
- 0
- NaN
- ‘’
- null
- undefined
- false 本身
除了以上几个,其他的都会被转换为true
。除了if
之外,!
||
&&
这三个运算符也会进行同样的转换,跟if
是一个道理。因此,如何快速判断一个变量将会被if
转换为什么呢?————!!a
⑶、答题
①、JS中使用typeof
能得到的哪些类型?
可以通过以下程序进行验证
typeof undefined // undefined
typeof 'abc' // string
typeof 123 // number
typeof true // boolean
typeof {} // object
typeof [] // object
typeof null // object
typeof console.log // function
②、何时使用===
何时使用==
==
会先试图类型转换,然后再比较,而===
不会类型转换,直接比较。如下例子:
1 == '1' // true
1 === '1' // false
0 == false // true
0 === false // false
null == undefined // true
null === undefined // false
根据 jQuery 源码中的写法,只推荐在一个地方用==
,其他地方都必须用===
。这个用==
的地方就是:
if (obj.a == null) { // 这里相当于 obj.a === null || obj.a === undefined ,简写形式
}
③、JS中有哪些内置函数
Object
Array
Boolean
Number
String
Function
Date
RegExp
Error
④、JS变量按照存储方式区分为哪些类型,并描述其特点
- 值类型
undefined
string
number
boolean
- 引用类型
object
function
在 JS 中,所有的引用类型都可以自由设置属性
var obj = {}
obj.a = 100
var arr = []
arr.a = 100
function fn() {}
fn.a = 100
⑤、如何理解JSON
JSON 是什么?从 JS 角度回答,太简单了,console.log(JSON)
得到JSON
只是一个对象,有parse
和stringify
两个方法,使用也非常简单
JSON.stringify({a:10, b:20})
JOSN.parse('{"a":10,"b":20}')
之所以误答,就是怕把这个问题复杂化了,因为 json 也是一种数据格式,这一点和 xml 一样。但是在 JS 的面试题中,如果问到这个问题,直接说明parse
和stringify
两个方法的用法即可,面试官如果有追问,你再去继续回答。
二、原型和原型链
1、构造函数
⑴、构造函数实例
function Foo(name, age){
this.name = name
this.age = age
this.class = 'class-1'
// return this // 默认有这一行
}
var f = new Foo('zhangsan', 20)
// var f1 = new Foo('lisa', 21) // 创建多个对象
⑵、构造函数- 扩展
- var a = {} 其实是 var a = new Object() 的语法糖
- var a = [] 其实是 var a = new Array() 的语法糖
- function Foo{…} 其实是 var Foo = new Function(…)
- 使用 instanceof 判断一个函数是否是一个变量的构造函数
- 使用 instanceof Array 判断一个变量是否为 “数组”
2、原型规则和示例
- 所有的引用类型 ( 数组、对象、函数 ) , 都具有对象特性, 即可自由扩展属性 ( 除了 “nul” 意外 )
- 所有的引用类型 ( 数组、对象、函数 ), 都具有一个 proto ( 隐式原型 )属性, 属性值是一个普通的对象
- 所有的函数, 都有一个 prototype ( 显示原型 ) 属性, 属性值也是一个普通的对象
- 所有的引用类型 ( 数组、对象、函数 ) , proto ( 隐式原型 ) 属性值指向他的构造函数的 prototype ( 显示原型 ) 属性值
- 当试图得到一个对象的某个属性时, 如果这个对象本身没有这个属性, 那么会去找它的 proto ( 即它构造函数的 prototype ) 中寻找
实例:
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;
console.log( obj._proto_ );
console.log( arr._proto_ );
console.log( fn._proto_ );
console.log( fn.prototype )
console.log( obj._proto_ === Object.prototype )
// 构造函数
function Foo(name, age){
this.name = name
}
Foo.prototype.alertName = function(){
alert( this.name )
}
// 创建示例
var f = new Foo('zhangsan')
f.printName = function(){
console.log( this.name )
}
// 测试
f.printName()
f.alertName()
f.toString() // 要去 f._proto_._proto_ 中查找
3、原型链
4、instanceof
用于判断 引用类型 属于哪个 构造函数 的方法
f instanceof Foo 的判断逻辑是:f 的_proto_ 一层一层往上, 能否对应到 Foo.prototype, 再试着判断 f instanceof Object
5、答题
⑴、如何准确判断一个变量是数组类型
只有instanceof
才能判断一个对象是否是真正的数组
var arr = []
arr instanceof Array // true
typeof arr // object,typeof 是无法判断是否是数组的
⑵、如何准确判断一个变量是数组类型
只有instanceof
才能判断一个对象是否是真正的数组
var arr = []
arr instanceof Array // true
typeof arr // object,typeof 是无法判断是否是数组的
扩展:实际应用中,和数组同样重要、起同样作用并且更加灵活的数据结构还是“伪数组”或者“类数据”(jquery 就用到了)。因此,在实际应用中,只需要判断length
属性是否是数字即可。
var arr = []
var likeArr = {
0: 'aaa',
1: 'bbb',
2: 'ccc',
length: 3
}
typeof arr.length === 'number' // true
typeof likeArr.length === 'number' // true
⑶、写一个原型链继承的例子
// 动物
function Animal() {
this.eat = function () {
console.log('animal eat')
}
}
// 狗
function Dog() {
this.bark = function () {
console.log('dog bark')
}
}
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()
// 其实,书中是为了演示了而演示,真正的实际 JS 开发中,根本不推荐这种两层甚至三层的继承。因为工作中项目本身业务就非常复杂,代码设计上就尽量求简,越简单的东西才越容易扩展和改变。
接下来,我将根据自己的开源项目 wangEditor 中的一段源码进行简化,通过实际的业务场景来使用原型和继承
// 构造函数
function DomElement(selector) {
var result = document.querySelectorAll(selector)
var length = result.length
var i
for (i = 0; i < length; i++) {
this[i] = selectorResult[i]
}
this.length = length
}
// 修改原型
DomElement.prototype = {
constructor: DomElement,
get: function (index) {
return this[index]
},
forEach: function (fn) {
var i
for (i = 0; i < this.length; i++) {
const elem = this[i]
const result = fn.call(elem, elem, i)
if (result === false) {
break
}
}
return this
},
on: function (type, fn) {
return this.forEach(elem => {
elem.addEventListener(type, fn, false)
})
}
}
// 使用
var $div = new DomElement('div')
$div.on('click', function() {
console.log('click')
})
⑷、描述 new 一个对象的过程
function Foo(name) {
this.name = name
this.type = 'foo'
}
var foo = new Foo('beijing')
- 创建一个新对象
this
指向这个新对象- 执行代码,即对
this
赋值 - 返回
this
⑸、zepto(或其他框架) 源码中如何使用原型链
解读优秀开源框架的源码或者设计,是成为程序大牛的毕竟之路。当然,解读源码是一件非常枯燥、成本非常高的事儿。因此我强烈推荐,不要去一意孤行闭门造车的解读枯燥的源码,而是通过在网上找到一些优秀的材料,来解读一个框架的设计。
就像一本厚厚的书,如何快速掌握书的内容?———— 知道个大致的故事结构,然后去看看别人(最好是名人)的一些点评,这样虽然花费时间很少,但是你也能比那些闷着头把书看完的人,了解到的东西更多。而且这种方式你可以高效率的阅读很多书,增加你的见识。