Javascript基础
数据类型
在javascript
中数据值的类型可以分为两大类,分别是基本类型和引用类型。基本类型都存储在栈内存上,而引用类型通常都存储在堆内存上。
基本类型
基本数据类型有七大类:
-
Number:number类型包括了整数类型和浮点数类型,所有的数字类型都可以用number表示
-
Boolean:boolean类型就只有两个值,true和false。通常用于条件判断
-
String:string类型表示字符串
-
Null:null类型一般用于给一个准备设置为引用类型的数据赋初始值,如
var a = null
,表示变量a
在后面可能会被赋予一个引用类型的值 -
Undefined:undefined类型用于给变量当默认值,如
var a;console.log(a)
,此时会输出undefined
,我们没有在声明变量后给一个初始值时js引擎会给它默认添加undefined
作为默认值undefined 在 JavaScript 中不是一个保留字,它作为一个属性挂载在Windows对象上,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
-
Symbol:symbol类型被设计之初是用于解决命名冲突问题,用它作为标识符永远不会与其他变量冲突。如
console.log(Symbol(1)===Symbol(1))
,输出false。即便传入的变量都为1,获取的结果也是不同的 -
BigInt:BigInt是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
基本数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储
引用类型
除了基本类型以外的类型都是引用类型,如Array、Object、Function。
引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体
类型判断
typeof
typeof
一般用于判断基本数据类型,但是它在判断null
时会被判断为object
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof []); // object
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
instanceof
instanceof
一般用于判断引用数据类型,它的运行原理是判断其在原型链中是否能够找到该类型的原型
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
instanceof
简单实现
function myInstanceof(left, right) {
if (left === null) return false
if (typeof left === "object" || typeof left === "function") {
let leftProto = Object.getPrototypeOf(left)
const rightProto = right?.prototype
while (leftProto) {
if (leftProto === rightProto) {
return true
}
leftProto = Object.getPrototypeOf(leftProto)
}
return false
}
return false
}
constructor
constructor
有两个作用,一是判断数据的类型,二是对象实例通过 constructor
对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor
就不能用来判断数据类型了
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
Object.prototype.toString.call()
Object.prototype.toString.call()
使用 Object 对象的原型方法 toString 来判断数据类型
var a = Object.prototype.toString;
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
判断数组
- 通过Object.prototype.toString.call()做判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
- 通过原型链做判断
obj.__proto__ === Array.prototype;
- 通过ES6的Array.isArray()做判断
Array.isArray(obj);
- 通过instanceof做判断
obj instanceof Array
- 通过Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)
类型转换
== 和 ===
对于 ==
来说,如果对比双方的类型不一样,就会进行类型转换。假如对比 x
和 y
是否相同,就会进行如下判断流程:
- 首先会判断两者类型是否**相同,**相同的话就比较两者的大小;
- 类型不相同的话,就会进行类型转换;
- 会先判断是否在对比
null
和undefined
,是的话就会返回true
- 判断两者类型是否为
string
和number
,是的话就会将字符串转换为number
1 == '1'
↓
1 == 1
- 判断其中一方是否为
boolean
,是的话就会把boolean
转为number
再进行判断
'1' == true
↓
'1' == 1
↓
1 == 1
- 判断其中一方是否为
object
且另一方为string
、number
或者symbol
,是的话就会把object
转为原始类型再进行判断
'1' == { name: 'js' }
↓
'1' == '[object Object]'
其流程图如下:
其他类型到数值类型的转换规则
- Undefined 类型的值转换为 NaN。
- Null 类型的值转换为 0。
- Boolean 类型的值,true 转换为 1,false 转换为 0。
- String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
- Symbol 类型的值不能转换为数字,会报错。
- 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf()方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
其他类型到布尔类型的转换规则
以下这些是假值:
- undefined
- null
- false
- +0、-0 和 NaN
- ""
假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
变量类型
var
在全局作用域下声明的var
变量会有变量提升,同时它会被挂载到window
对象上作为一个属性
console.log(asuhe)
var asuhe = 10
console.log(window)

let
let
声明的变量其作用域会绑定在最近的{}
花括号里面,而且不存在变量提升。
// 作用域绑定
{
let a = 10
console.log("inner",a) // inner 10
}
console.log("outer",a) // ReferenceError
同时对于函数作用域内的同名变量会有暂时性死区
的效果,暂时性死区
和没有变量提升配合起来会屏蔽变量作用域的查找
var a = 10
function foo(){
console.log(a)
let a = 20
}
foo() // ReferenceError
const
const
变量基本特点和let
一致,但是const
所指向的那个值不允许被修改,因此const
在声明之初一定要赋予一个初始值
const a // SyntaxError
const b = 10
b = 20 // TypeError
const c = { n:30 }
c.n = 40
console.log(c.n) // 40
函数
函数声明
函数声明有函数提升的效果,而函数声明表达式则没有
// 因为函数提升,所以函数可以在声明前被调用
foo(1,2) // 3
function foo(a,b){
console.log(a + b)
}
函数声明表达式
函数声明表达式没有函数提升效果,所以必须在赋值完成后才能调用
// 无函数提升
foo(1,2) // TypeError 此时foo为undefined
var foo = function(a,b){
console.log(a*b)
}
foo(1,2) // 2
foo(1, 2) // 3
function foo(a, b) {
console.log(a + b)
}
foo(1, 2) // 3
var foo = function foo(a,b){
console.log(a * b)
}
foo(1, 2) // 2
this指向
在使用function
关键字声明的函数中,this
指向的是其调用者
function foo(){
console.log(this.a)
}
var a = 10
let obj = {
f:foo,
a:20
}
obj.f() // 20
foo() // 10
匿名函数没有自己的this
,匿名函数中的this
是匿名函数定义时的上层作用域的this
let obj = {
f:()=> console.log(this.a),
a:10
}
var a = 20
obj.f() // 20
匿名函数
-
匿名函数只有一个形参时,可以省略(),且在函数体只有一句时可以省略{}并将该语句结果返回
let f = n => n+1 console.log(f(2)) // 3
-
匿名函数没有自己的
this
,因此也不能使用call
、apply
、bind
等方法来改变this
指向 -
由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用
let Student = (a, b) => a + b let s1 = new Student() // TypeError
-
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值
-
箭头函数没有prototype
-
箭头函数不能用作Generator函数,不能使用yeild关键字
作用域 && 作用域链
在ES6以前javascript并没有像其他语言一样拥有块级作用域
的概念,仅有函数作用域和全局作用域这两种作用域。例如:
{
var a = 20
}
console.log(a) // 20
而在ES6新增了let
、const
关键字后,由这两种关键字声明的变量其作用域会绑定在其最近的{}
内。例如:
{
let a = 20
}
console.log(a) // ReferenceError
在 JavaScript 里面,函数、块、模块都可以形成作用域(一个存放变量的独立空间),他们之间可以相互嵌套,作用域之间会形成引用关系,这条链叫做作用域链。
当我们去查找一个变量a时,若这个变量a不在本级作用域中那么它会继续往它的上级作用域中去寻找该变量的声明。若最后找不到就报错。
闭包
闭包就是在一个变量对象里持有另一个变量对象里内容的引用,这时就会产生闭包。常见的表现形式就是,内部函数持有外部函数的变量,我们可以通过返回内部函数去让更外层的作用域能够访问到内部函数的父函数里的变量。
function fun(n, o) {
console.log(o)
return {
fun: function (m) {
return fun(m, n)
}
}
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)
var b = fun(0).fun(1).fun(2).fun(3)
常用对象&&对象方法
Array对象
静态方法
Array.from
Array.from()
从一个可迭代对象或类数组对象中,创建一个新的数组实例。它实现的是浅拷贝。
参数:
arrayLike:一个可迭代对象或类数组对象
mapFn(可选):遍历函数
thisArg(可选):执行遍历函数时的this指针
console.log(Array.from("asuhe")) // ["a", "s", "u", "h", "e"]
console.log(Array.from([2,4,5,6])) // [2,4,5,6]
// 传入遍历函数
function map(x){
return x + 1
}
console.log(Array.from([2,4,5,6],map)) // [3,5,6,7]
// 传入this参数
function Map(x){
console.log(this.a)
return x + 2
}
const obj = { a:666 }
console.log(Array.from([1,2,3],Map,obj)) // 666 666 666 [3,4,5]
Array.isArray
判断一个变量是否为数组
参数:
value:需要判断的变量
console.log(Array.isArray([1,2,3])) // true
console.log(Array.isArray(1)) // false
console.log(Array.isArray({a:666})) // false
console.log(Array.isArray("asuhe")) // false
Array.of
根据传入的参数创建一个数组,浅拷贝
参数:
elementN:传入的参数可以为可变数量,传多少个就创建多长的数组
const obj = {a:666}
const test = Array.of(1,2,'asuhe',obj)
console.log(test) // [1, 2, "asuhe", Object { a: 666 }]
obj.a = 777
console.log(test) // [1, 2, "asuhe", Object { a: 777 }]
实例方法
concat
合并多个参数,返回一个新数组。浅拷贝
参数:
valueN:参数可以为数组,也可以为普通参数。
const array1 = ['a', 'b', 'c']
const array2 = ['d', 'e', 'f']
const array3 = array1.concat(array2,666)
console.log(array3) // ["a", "b", "c", "d", "e", "f",666]
filter
过滤出回调函数返回值为true的元素,返回一个符合条件的新数组。
参数:
callbackFn:遍历的回调
thisArg(可选):回调执行时的this
const arr = [1,2,3,4,5,6,7]
const res = arr.filter((element,index,arr)=>{
return element%2 === 1
})
console.log(res) // [1, 3, 5, 7]
every
当所有的元素都通过了回调函数的条件时,返回一个true的布尔值,否则返回false。
参数:
callbackFn:遍历的回调
thisArg(可选):回调执行时的this
function isOdd(e){
return e%2===1
}
const arr = [1,3,5,7]
console.log(arr.every(isOdd)) // true
some
当数组中有一个元素通过了回调函数的条件时,返回一个true的布尔值,否则返回false。
参数:
callbackFn:遍历的回调
thisArg(可选):回调执行时的this
function isOdd(e){
return e%2===1
}
const arr = [2,3,4,6,8]
console.log(arr.some(isOdd)) // true
reduce
根据遍历函数的返回值,一直传递给下次遍历函数。一般用于计算总和
参数:
callbackFn:遍历的回调
initialValue(可选):回调执行时的this
const initialValue = 0
const arr = [1,2,3,4,5]
const res = arr.reduce((preValue,e) => preValue+e,initialValue )
console.log(res) // 15
Object对象
静态方法
Object.keys
返回一个数组,数组内容为对象中所有的可枚举属性的key值
const flag = Symbol()
const obj = {
a:1,
b:"asuhe",
hhh:666,
[flag]:"sphinx"
}
console.log(Object.keys(obj)) // ["a", "b", "hhh"]
Object.getPrototypeOf
获取一个对象的隐式原型
const obj = {
name:"asuhe",
age:16
}
const objProto = Object.getPrototypeOf(obj)
console.log(objProto === Object.prototype) // true
Object.create
使用传入的对象作为prototype
,创建一个新的对象
const obj = {
name:"asuhe",
age:16
}
const res = Object.create(obj)
console.log(Object.getPrototypeOf(res) === obj) // true
Object.assign
从源对象中,合并源对象的可枚举属性进目标对象,并返回修改后的对象
参数:
target:需要修改的目标对象
source:提供可枚举属性来源的源对象
const target = {
name:"asuhe",
age:16
}
const source = {
gender:"male"
}
const res = Object.assign(target,source)
console.log(res) // { name: "asuhe", age: 16, gender: "male" }
console.log(res === target) // true
逻辑中断
&&
&&
逻辑与,当前一个条件判断为真时,才会继续执行后一个条件,并返回表达式的值
console.log(1+2 && 3+4) // 7
console.log(0 && 2 + 4) // 0
||
||
逻辑或,当前一个条件判断为假时,才会继续执行后一个条件,并返回表达式的值
console.log( 0 || 3 + 3) // 6
console.log( 1 + 4 || 4 + 5) // 5
? :
? :
三目运算符,当条件值为true时,返回第一个表达式的值,否则返回第二个表达式的值
console.log( 1 ? 1+2 : 3+4 ) // 3
console.log( 0 ? 1+2 : 3+4) // 7
原型链
- 所有对象都有一个隐式原型
__proto__
- 所有函数对象都有一个原型
prototype
- 以某函数为构造函数,
new
出实例的隐式原型__proto__
都会指向该函数的原型prototype
- 所有函数对象都是
Function
的实例,包括Function
自身 - 所有函数对象的
prototype
都是Object
的实例
function Foo(a,b){
return a+b
}
const obj = new Foo()
console.log(obj instanceof Object) // true
console.log(obj instanceof Foo) // true
console.log(Foo instanceof Object) // true
console.log(Foo instanceof Function) // true
异步编程
event-loop
事件循环模型示意图
Promise
Task && Microtask
MDN中Task(宏任务)的描述:
A task is any JavaScript code which is scheduled to be run by the standard mechanisms such as initially starting to run a program, an event callback being run, or an interval or timeout being fired. These all get scheduled on the task queue.
MDN中Microtask(微任务)的描述:
At first the difference between microtasks and tasks seems minor. And they are similar; both are made up of JavaScript code which gets placed on a queue and run at an appropriate time. However, whereas the event loop runs only the tasks present on the queue when the iteration began, one after another, it handles the microtask queue very differently.
两者不同之处的MDN描述:
There are two key differences.
First, each time a task exits, the event loop checks to see if the task is returning control to other JavaScript code. If not, it runs all of the microtasks in the microtask queue. The microtask queue is, then, processed multiple times per iteration of the event loop, including after handling events and other callbacks.
Second, if a microtask adds more microtasks to the queue by calling
queueMicrotask()
, those newly-added microtasks execute before the next task is run. That’s because the event loop will keep calling microtasks until there are none left in the queue, even if more keep getting added.