JavaScript基础

本文最后更新于:1 年前

JavaScript基础

五大浏览器内核

五大主浏览器 内核
IE Trident
Chrome Webkit Blink
Safari Webkit
Firefox Gecko
Opera Presto

image.png

单线程

JS引擎是单线程,但是他会模拟多线程

轮转时间片

短时间之内轮流执行多个任务的片段

  1. 任务一 任务二
  2. 切分任务一 任务二
  3. 随机排列这些任务片段,组成队列
  4. 按照这个队列顺序将任务片段送进JS进程
  5. JS线程执行一个又一个的任务片段

命名规范

不能以数字开头,可以字母_$开头,非开头部分可以写字母_$数字
不能使用关键字、保留字,注意要语义化、结构化
推荐小驼峰或者驼峰命名

JS值

原始值(基本类型数据)、引用值(引用类型数据)

原始值

Number、String、Boolean、Undefined、null、Symbol、BigInt

引用值

Array、Object、Function

运算

普通运算

加法

字符串拼接

任何数据类型的值 + 字符串都是字符串

1
2
3
c = 'str' + 1 + 1 //str11
c = 'str' + NaN //strNaN
c = 'str' + true //strtrue
运算符优先级

括号运算 > 普通运算 >赋值

1
c = 1 + 1 + 'str' + (1 + 1) //2str2

除法

NaN非数,数字类型

1
2
3
4
5
6
c = 2 / 1 //2
c = 0 / 0 //NaN
c = 'a' / 'b' //NaN
c = NaN / NaN //NaN
c = NaN / 1 //NaN
c = 1 / NaN //NaN

**Infinity **无穷,数字类型

1
2
c = 1 / 0 //Infinity
c = -1 / 0 //-Infinity

取余(模)

1
2
3
c = 6 % 4 //2
c = 7 % 3 //1
c = 0 % 3 //0

交换值

ab值交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//方法一
let c,
a = 1,
b = 2
c = a
a = b
b = c

//方法二
let a = 1
b = 2
a = a + b //a=3
b = a - b //3-2=1
a = a - b //3-1=2
console.log(a, b)

++,–

a++先打印后运算

1
2
3
let a = 1
console.log(a++) //1 先打印后运算
console.log(a) //2

++a先运算后打印

1
2
3
let a = 1
console.log(a) //1
console.log(++a) //2 先运算后打印
1
2
3
4
let a = 5,
b
b = a++ + 1 //b = 1 + a++ 1 + 5
console.log(a, b) //6 , 6 运算 赋值完成之后才打印,所以a是6
1
2
3
4
let a = 5,
b
b = a-- + --a //b = --a + a-- 4 + 4
console.log(a, b) //3 , 8
1
2
3
4
let a = 5,
b
b = --a + --a //4 + 3
console.log(a, b) //3 , 7
1
2
3
4
let a = 5,
b
b = --a + a++ //4 + 4
console.log(a, b) //5 , 8

比较运算

< > = <= == === != !==

纯数字比较就是

number - number

数字和字符串比较

string先转化成number 在进行比较

1
let bool = 1 > '2' //false

字符串和字符串比较

字符串转换对应ASCII字码表对应的数值,多个字符的,从左到右一次对比,直到比较出大小为止

1
let bool = '3.5' > '11' //true

相等

相等是不看数据类型,全等要看数据类型是否相等

1
2
let bool = 1 == '1' //true
let bool = 1 === '1' //false

不相等

NaN与任何东西(包括自己)都不相等

1
let bool = NaN === NaN //false

逻辑运算

与&& 或|| 非!

undefined,null,NaN,"",0,false除以上以外全部都是真
“ “ 有空格不是空字符串,是真

&&

遇到真就往后走,遇到假或者到最后就返回当前的值

1
2
3
4
5
6
let a = 1 && 2 //2
let b = 1 && 2 && undefined && 10 //undefined
//1 && 1 返回1 真
//0 && 1 返回0 假
//1 && 0 返回0 假
//0 && 0 返回0 假

||

遇到假就往后走,遇到真或者走到最后就返回当前的值

1
2
3
4
5
let c = 0 || null || 1 || 0 //1
//1 || 1 返回1 真
//0 || 1 返回1 真
//1 || 0 返回1 真
//0 || 0 返回0 假

循环

for循环

1
2
3
for(let i=0;i<10;i++){
console.log(i)
}

步骤

1
2
3
4
5
6
7
8
9
//1、声明变量i=0
//2、if(i < 10){
// console.log(i)
//}
//3、i++
//2、if(i < 10){不满足条件停止循环
//console.log(i);
//}
//3、i++

拆解步骤

1
2
3
4
5
let i=0;
for(;i < 10;){
console.log(i);
i++;
}

for循环转换为while循环

1
2
3
4
5
let i = 0
while (i < 10) {
console.log(i)
i++
}

打印0-100的数,( ) 只能有一个,不能写比较,{ } 不能出现 i++ i–

1
2
3
4
let i = 101
for (; i--; ) {
console.log(i)
}

10的n次方

1
2
3
4
5
6
let n = 5,
num = 1
for (let i = 0; i < n; i++) {
num *= 10
}
console.log(num)

n的阶乘

1
2
3
4
5
6
let n = 5,
num = 1
for (let i = 1; i <= n; i++) {
num *= i
}
console.log(num)

789打印出987

1
2
3
4
5
6
let num = 789
let a = num % 10 //9
let b = ((num - a) / 10) % 10 //8
// let b = ((num - a) % 100) / 10
let c = (num - a - b * 10) / 100 //9
console.log('' + a + b + c)

打印三个数中最大的数

1
2
3
4
5
6
7
8
9
10
let a = 1,
b = 2,
c = 3
if (a > b) {
if (a > c) console.log(a)
else console.log(c)
} else {
if (b > c) console.log(b)
else console.log(c)
}

打印100以内的质数(仅仅能被1和自己整除的数)

1
2
3
4
5
6
7
8
let c = 0
for (let i = 2; i < 100; i++) {
for (let j = 1; j <= i; j++) {
if (i % j == 0) c++
}
if (c == 2) console.log(i)
c = 0
}

类型转换

typeof

如何判断一个变量的类型呢,js提供了_typeof_运算符,用来检测一个变量的类型。

Type Result
Undefined “undefined”
Null “object” (see below)
Boolean “boolean”
Number “number”
BigInt (new in ECMAScript 2020) “bigint”
String “string”
Symbol (new in ECMAScript 2015) “symbol”
Function object (implements [[Call]] in ECMA-262 terms) “function”
Any other object “object”
1
2
3
4
5
6
7
8
9
10
11
console.log(typeof undefined) //undefined
console.log(typeof null) //object //null类型是object历史遗留问题
console.log(typeof true) //boolean
console.log(typeof 1) //number
console.log(typeof 123456789n) //bigint
console.log(typeof '123') //string
console.log(typeof Symbol('Sym')) //symbol
console.log(typeof function () {}) //function
console.log(typeof []) //object
console.log(typeof {}) //object
console.log(typeof NaN) //number

Object可以理解为引用类型, object/array都是Object引用类型

1
2
3
console.log(typeof (1 - '1')) //number
console.log(typeof ('1' - 1)) //number
console.log(typeof ('1' - '1')) //number
1
2
3
4
// console.log(a) //a is not defined
console.log(typeof a) //undefined
console.log(typeof typeof a) //string 数据类型typeof之后再typeof都是string
console.log(typeof typeof 123) //string

封装typeof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function myTypeof(val) {
let type = typeof val
let toStr = Object.prototype.toString
let res = {
'[object Array]': 'array',
'[object Object]': 'object',
'[object Number]': 'number',
'[object String]': 'string',
'[object Boolean]': 'boolean',
}
if (val === null) {
return 'null'
} else if (type === 'object') {
let ret = toString.call(val)
return res[ret]
} else {
return type
}
}

显示类型转换

Number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// let a = '123'
// console.log(typeof Number(a) + '--' + Number(a)) //number--123
// let a = true
// console.log(typeof Number(a) + '--' + Number(a)) //nnumber--1
// let a = false
// console.log(typeof Number(a) + '--' + Number(a)) //nnumber--0
// let a = null
// console.log(typeof Number(a) + '--' + Number(a)) //nnumber--0
// let a = undefined
// console.log(typeof Number(a) + '--' + Number(a)) //nnumber--NaN
// let a = 'a'
// console.log(typeof Number(a) + '--' + Number(a)) //nnumber--NaN
// let a = '1a'
// console.log(typeof Number(a) + '--' + Number(a)) //nnumber--NaN
// let a = '3.14'
// console.log(typeof Number(a) + '--' + Number(a)) //nnumber--3.14

parseInt

1
2
3
4
5
6
7
8
9
10
11
12
// let a = '123'
// console.log(typeof parseInt(a) + '--' + parseInt(a)) //nnumber--123
// let a = '1.23'
// console.log(typeof parseInt(a) + '--' + parseInt(a)) //nnumber--1
// let a = '1.99'
// console.log(typeof parseInt(a) + '--' + parseInt(a)) //nnumber--1
// let a = true
// console.log(typeof parseInt(a) + '--' + parseInt(a)) //nnumber--NaN
// let a = null
// console.log(typeof parseInt(a) + '--' + parseInt(a)) //nnumber--NaN
// let a = undefined
// console.log(typeof parseInt(a) + '--' + parseInt(a)) //nnumber--NaN
1
2
3
4
let a = '10'
console.log(parseInt(a, 16)) //16 //第二个参数取值2-36
console.log(parseInt('abc123')) //NaN
console.log(parseInt('123abc1')) //123

parseFloat

1
2
3
4
let num = parseFloat('2.345')
let num2 = parseFloat('2')
console.log(num.toFixed(2)) //2.35 四舍五入保留两位小数
console.log(num2.toFixed(2)) //2.00 保留两位小数

toString

null和undefined没有toString方法

1
2
3
4
5
6
// let str = null
// console.log(str.toString())//Cannot read properties of null (reading 'toString')
// let str = undefined
// console.log(str.toString()) //Cannot read properties of undefined (reading 'toString')
let str = '123'
console.log(str.toString()) //123

Boolean

undefined、null、NaN、” “、0是false,其他都是true

1
console.log(Boolean(1)) //ture

隐式类型转换

a未定义,但是typeof a 是字符串 ‘’undefined’’

1
2
console.log(a) //a is not defined
console.log(typeof a) //undefined
1
2
3
let a = '1'
a++ //Number(a) 进行了隐式类型转换
console.log(a) //124
1
2
3
4
5
6
7
8
9
console.log(-true) - 1
console.log(typeof -true) //number
console.log(+undefined) //NaN
console.log(typeof +undefined) //number

console.log(!!' ') //true
console.log(!!'') //false
console.log(typeof ' ') //string
console.log(typeof '') //string

字符串和数字

+时,会隐式类型转换成字符串进行拼接,
-、* 、/、%和>、< 比较时,会隐式类型转换成number类型计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let a = '2' + 1
console.log(a) //21
console.log(typeof ('2' + 1)) //string

console.log('2' - 1) //1
console.log(typeof ('2' - 1)) //number
console.log(typeof (1 - '2')) //number

let b = 'b' + 1
console.log(b) //b1
console.log(typeof b) //string

let b2 = 'b' - 1
console.log(b2) //NaN
console.log(typeof b2) //number
let b3 = 'b' * 1
console.log(b3) //NaN
console.log(typeof b3) //number

字符串和字符串

比较时,会转成ASCII码在进行比较

1
2
3
let a = 'a' > 'b'
console.log(a) //false
console.log(typeof a) //boolean

全等于不进行数据类型转换

1
2
3
4
let a = 1 === '1'
let b = 1 == '1'
console.log(b) //true
console.log(a) //false
1
2
3
let a1 = 2 > 1 > 3
let a2 = 2 > 1 == 1
console.log(a1, a2) //false true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a1 = undefined < 0
let a2 = undefined > 0
let a3 = undefined == 0
console.log(a1, a2, a3) //false false false

let b1 = null < 0
let b2 = null > 0
let b3 = null == 0
console.log(b1, b2, b3) //false false false

let c1 = undefined == null //true
let c2 = NaN == 0
let c3 = NaN == NaN
console.log(c1, c2, c3) //true false false

布尔值和数字

1
2
3
4
let a = false + 1
console.log(a) //1
let b = true + 1
console.log(b) //2
1
2
3
4
let b = false == 1
console.log(b) //false
let a = false !== 1
console.log(a) //true

isNaN

判断数据类型是否为非数,先将数据进行隐式转换成number在进行NaN判断

1
2
3
4
5
console.log(isNaN(NaN)) //true
console.log(isNaN('123')) //false
console.log(isNaN('a')) //true
console.log(isNaN(null)) //false null转成数字是0
console.log(isNaN(undefined)) //true

函数

函数命名规则

不能数字开头,中间可以数字、字母、_ 、$,推荐小驼峰命名法

函数声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//函数声明
function test() {
let a = 1,
b = 2
console.log(a, b)
}

//函数表达式
//匿名函数表达式 函数字面量
let test1 = function () { //匿名函数
let a = 1,
b = 2
console.log(a, b)
}

函数字面量(函数表达式)是忽略函数名

1
2
3
4
let test = function a() {
return 'a'
}
console.log(typeof a) //undefined

形参/实参

1
2
3
4
5
function test(a, b) {//此处a,b是形参
console.log(test) //打印函数
console.log(arguments) //打印参数
}
test(1, 2, 3)//此处1,2,3是实参
1
2
3
4
5
6
7
8
9
//一个函数被调用时,累加他的实参
function sum() {
let a = 0
for (let i = 0; i < arguments.length; i++) {
a += arguments[i]
}
console.log(a)
}
sum(1, 2, 3)

在实参传了值的情况下,函数内部可以改变实参的值

1
2
3
4
5
6
function test(a, b) {
a = 3
console.log(arguments[0]) //3
console.log(a, b) //3 2
}
test(1, 2)

在实参没有传值的情况下,函数内部给形参赋值是没有用的

1
2
3
4
5
6
function test2(a, b) {
b = 3
console.log(arguments[1]) //undefined
//如果实参没有传对应参数,就没有对应的映射 所以是 undefined
}
test2(1)

形参和arguments对象不是同一个东西,但是有映射关系。

1
2
3
4
5
6
7
function test(a, b) {
a = 3 //存在栈内存
console.log(arguments[0]) //3 存在堆内存
//形参和arguments不是同一个东西,但是有映射关系。
//如果实参没有传对应参数,就没有对应的映射 所以是 undefined
}
test(1, 2)

初始化参数

参数不传默认值是 undefined,形参和arguments谁不是undefined就默认选谁
传一个参数给第二位形参,第一位形参设置默认值,

1
2
3
4
5
6
7
8
9

function test(a = 1, b) { //形参赋值是ES6,低版本浏览器不支持
console.log(a)
console.log(b)
//形参和arguments谁不是undefined就默认选谁
// a 1 默认选择a=1
// arguments[0] undefined
}
test(undefined, 2)

兼容写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//或运算法(不推荐)
//不推荐这种写法,如果实参传0,默认为false,会执行后边的
function test2(a, b) {
a = arguments[0] || 1
b = arguments[1] || 2
console.log(a, b) //1 6
}
test2(undefined, 6)

//typeof方法
function test2(a, b) {
if (typeof arguments[0] !== 'undefined') {
a = arguments[0]
} else {
a = 1
}
console.log(a, b) //1 3
}
test2(undefined, 3)

//三元运算法
function test3(a, b) {
a = typeof arguments[0] !== 'undefined' ? arguments[0] : 1
console.log(a, b) //1 2
}
test3(undefined, 2)

默认参数作用域与暂时性死区

因为参数是按顺序初始化的,所以后定义默认值的参数可以引用先定义的参数

1
2
3
4
function makeKing(name = 'Henry', numerals = name) {
return `King ${name} ${numerals}`
}
console.log(makeKing()) // King Henry Henry

参数初始化顺序遵循“暂时性死区”规则,即前面定义的参数不能引用后面定义的

1
2
3
4
function makeKing(name = numerals, numerals = 'VIII') {
return `King ${name} ${numerals}`
}
console.log(makeKing()) //Cannot access 'numerals' before initialization

参数也存在于自己的作用域中,它们不能引用函数体的作用域:

1
2
3
4
5
6
// 调用时不传第二个参数会报错
function makeKing(name = 'Henry', numerals = defaultNumeral) {
let defaultNumeral = 'VIII'
return `King ${name} ${numerals}`
}
console.log(makeKing())//defaultNumeral is not definedcat makeKing

n的阶层,使用递归,不能用for循环

1
2
3
4
5
6
//n的阶层,使用递归,不能用for循环
// 规律 n! = n * (n-1)!
function fact(n) {
if (n <= 1) return 1
return n * fact(n - 1)
}

arguments 对象其实还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。
阶乘函数一般定义成递归调用的,就像上面这个例子一样。只要给函数一个名称,而且这个名称不会变,这样定义就没有问题。但是,这个函数要正确执行就必须保证函数名是 fact,从而导致了紧密耦合。arguments.callee 就可以让函数逻辑与函数名解耦

1
2
3
4
function fact(num) {
if (num <= 1) return 1
return num * arguments.callee(num - 1)
}

这个重写之后的 fact()函数已经用 arguments.callee 代替了之前硬编码的 fact。 这意味着无论函数叫什么名称,都可以引用正确的函数。
斐波拉去数列

1
2
3
4
5
6
function fb(n) {
if (n <= 0) return 0
if (n <= 2) return 1
return fb(n - 1) + fb(n - 2)
}
console.log(fb(2))

预编译

预编译流程

1.检查通篇的语法错误
1.5预编译的过程
2.解释一行,执行一行

变量提升

函数声明先提升,其次变量
函数声明是整体提升

1
2
3
4
test()
function test() {
console.log(1) //1
}

var变量只有声明提升,赋值不提示

1
2
console.log(a) //undefined
var a = 1
1
2
console.log(a) //undefined
var a
1
console.log(a) //a is not defined

let变量赋值都不提升

1
2
console.log(a) //Cannot access 'a' before initialization
let a = 1
1
2
3
4
5
6
7
//打印的是函数a
console.log(a) //ƒ a(a) {var a = 1;var a = function () {}}
function a(a) {
var a = 1
var a = function () {}
}
var a = 2

暗示全局变量

全局作用域下,未声明或者var声明的变量都会挂载到window下

1
2
3
4
5
6
7
8
var a = 1
b = 2
console.log(window.a) //1
//在全局下不管是否var a=window.a b=window.b
// window={
// a:1,
// b=2
// }

函数内未声明的变量直接挂载到window下

1
2
3
4
5
6
7
8
9
10
function test() {
var a = b = 1 //a先声明后赋值一个1,然后赋值给b,b未声明
//b 未声明直接挂载到window 全局域下
}
test()
//访问对象不存在的属性会 undefined
console.log(window.a) //undefined
console.log(window.b) //1
//访问对象不存在的变量会 not defined
console.log(a) //a is not defined

AO activation object 活跃对象,函数上下文

先提升变量声明(不含赋值),然后实参给形参赋值,再提升函数声明,,最后执行函数。按照此顺序执行代码

  1. 寻找形参和变量声明

  2. 实参赋值给形参

  3. 找函数声明并赋值函数体

  4. 执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function test(a) {
    console.log(1, a)
    var a = 1
    console.log(2, a)
    function a() {}
    console.log(3, a)
    var b = function () {}
    console.log(4, b)
    function d() {}
    }
    test(2)
    // 1 ƒ a() {}
    // 2 1
    // 3 1
    // 4 ƒ () {}

    // //预编译
    // AO = {
    // a: undefined // 1.寻找形参和变量声明 ->
    // 2 // 2.把实参赋值给形参 ->
    // function a() {} // 3.找函数声明并赋值函数体 ->
    // 1 // 4.执行函数

    // b: undefined // 1.寻找形参和变量声明 ->
    // function () {} // 4.执行函数

    // d: undefined // 1.寻找形参和变量声明 ->
    // function d() {} // 3.找函数声明并赋值函数体
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    function test(a, b) {
    console.log(1, a)
    c = 0
    var c
    a = 5
    b = 6
    console.log(2, b)
    function b() {}
    function d() {}
    console.log(3, b)
    }
    test(1)
    // 1 1
    // 2 6
    // 3 6

    // AO={
    // a: undefined // 1.寻找形参和变量声明 ->
    // 1 // 2.实参赋值给形参 ->
    // 5 // 4.执行函数

    // b: undefined // 1.寻找形参和变量声明 ->
    // function d() {} // 3.找函数声明并赋值函数体
    // 6 // 4.执行函数

    // c: undefined // 1.寻找形参和变量声明 ->
    // 0 // 4.执行函数

    // d: undefined // 1.寻找形参和变量声明 ->
    // function d() {} // 3.找函数声明并赋值函数体
    // }

    GO global object 全局上下文

    GO相当于是window
    先变量声明提升,再函数提升,最后执行

  5. 找变量声明

  6. 找函数声明并赋值函数体

  7. 执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var a = 1
    function a() {
    console.log(2)
    }
    console.log(a) //1
    // GO = {
    // a: undefined, // 1.找变量 ->
    // function a() {} // 2.找函数声明并赋值函数体 ->
    // 1 // 3.执行
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    console.log(a, b) //function a(){}  undefined  
    function a() {}
    var b = function () {} //b在执行之前还没有赋值,所以打印undefined

    // GO = {
    // b: undefined // 1.寻找变量声明 =>
    // function () {} // 3.执行
    // a: undefined // 1.寻找变量声明 =>
    // function a() {} //2.找函数声明并赋值函数体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function test() {
    var a = (b = 1)
    console.log(b)
    }
    test() //1
    // GO = {
    // b: 1, //4.执行
    // }
    // AO = {
    // a: undefined //1.寻找变量
    // 1 //4.执行(往上找到GO中的b)
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    var b = 3
    console.log(1, a)
    function a(a) {
    console.log(2, a)
    var a = 2
    console.log(3, a)
    function a() {}
    var b = 5
    console.log(4, b)
    }
    a(1)
    // 1 ƒ a(a) {
    // console.log(2, a)
    // var a = 2
    // console.log(3, a)
    // function a() {}
    // var b = 5
    // console.log(4, b)
    // }
    // 2 ƒ a() {}
    // 3 2
    // 4 5

    // GP = {
    // b: undefined // 1.找变量
    // 3 // 3.执行
    // a: undefined // 1.找变量
    // function a(){...} // 2.找函数声明并赋值函数体
    // }
    // AO = {
    // a: undefined // 1.寻找形参和变量声明
    // 1 // 2.实参赋值给形参
    // function a() {} // 3.找函数声明并赋值函数体
    // 2 // 4.执行
    // b: undefined // 1.寻找形参和变量声明
    // 5 // 4.执行
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    a = 1
    function test() {
    console.log(1, a)
    a = 2
    console.log(2, a)
    var a = 3
    console.log(3, a)
    }
    test()
    var a
    // 1 undefined //就近原则,函数内部声明了a,所以不去全局找a
    // 2 2
    // 3 3

    // GO = {
    // a: undefined, // 1.找变量
    // 1 // 3.执行
    // test: function test() {...} // 1.找函数声明并赋值函数体
    // }
    // AO = {
    // a: undefined, // 1.找变量
    // 2 // 4.执行
    // 3 // 4.执行
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function test() {
    console.log(b) //undefined
    if (a) {
    // a undefined
    var b = 2
    }
    c = 3
    console.log(c) //3
    }
    var a
    test()
    a = 1
    console.log(a) //1

    // GO = {
    // a: undefined, //1
    // 1 //3
    // test: undefined, //1
    // function test() {} //2
    // c: undefined, //1
    // 3 //3
    // }
    // AO = {
    // b: undefined, //1
    // c: undefined //1
    // 3 //4
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function test() {
    return a
    a = 1
    function a() {}
    var a = 2
    }
    console.log(test())
    // ƒ a() {}

    // GO = {
    // a: undefined,//1
    // 1 //3
    // test:undefined //1
    // function test() {...} //2
    // }
    // AO = {
    // a: undefined, //1
    // function a() {} //3
    // 1 //4
    // 2 //4
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function test() {
    a = 1
    function a() {}
    var a = 2
    return a
    }
    console.log(test()) //2
    // GO = {
    // a: undefined, //1
    // 1 //3
    // test:undefined //1
    // function test() {}
    // }
    // AO = {
    // a: undefined,//1
    // function a() {} //3
    // 1 //4
    // 2 //4
    // }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    a = 1
    function test(e) {
    function e() {}
    arguments[0] = 2
    console.log(e) //2
    if (a) {
    // a 1
    var b = 3
    }
    var c
    a = 4
    var a
    console.log(b) //undefined
    f = 5
    console.log(c) //undefined
    console.log(a) //4
    }
    var a
    test(1)
    console.log(a) //1
    console.log(f) //5
    // 2
    // undefined
    // undefined
    // 4
    // 1
    // 5

    // GO = {
    // a: undefined,//1
    // 1 //3
    // test: undefined //1
    // function test() {} //2
    // f: undefined, //1
    // 5 // 4
    // }
    // AO = {
    // e: undefined, //1
    // 1 //2
    // function e() {} //3
    // 2 //4
    // b: undefined, //1
    // 3 //4
    // c: undefined, //1
    // a: undefined, //1
    // 4 //4 函数体内有声明,就近原则
    // f: 5 //4 在GO中找到的f
    // }

    作用域/作用域链

    函数也是一种对象类型,是引用类型,引用值,有对象.name,.length,.protoytpe等属性
    对象中有些属性是我们无法访问的,JS引擎内部固定有的隐式属性 [[scope]]

函数创建时,生成的一个JS内部隐式属性
函数存储作用域的容器,作用域链
AO,函数的执行期上下文,
GO,全局的执行期上下文
函数执行完成之后,AO是要销毁的,AO是一个即时的存储容器

在函数被定义的时候就已经形成了作用域,作用域链和GO
在函数执行的那一刻才生成自己的AO

外部函数为什么不能访问到内部函数的变量?

1
2
3
4
5
6
7
8
9
10
11
12
function a() {
function b() {
let b = 2
}
let a = 1
b()
console.log(b)
}
a()
//ƒ b() {
// let b = 2
// }

a的AO里面b等于function b() {},内部方法执行后作用域链就切断了,所以只能打印b的函数体不能访问b里边的变量
外部函数没有内部函数的AO环境,但是内部函数在执行的时候引用了外部函数的AO上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function a() {
function b() {
function c() {}
c()
}
b()
}
a()
//a定义:a.[[scope]] -> 0 : GO
//a执行:a.[[scope]] -> 0 : GO
// 1 : GO
//b定义:b.[[scope]] -> 0 : a -> AO
// 1 : GO
//b执行:b.[[scope]] -> 0 : b -> AO
// 1 : a -> AO
// 2 : GO
//c定义:c.[[scope]] -> 0 : b -> AO
// 1 : a -> AO
// 2 : GO
//c执行:c.[[scope]] -> 0 : c -> AO
// 1 : b -> AO
// 2 : a -> AO
// 3 : GO

//c结束:c.[[scope]] -> 0 : b -> AO
// 1 : a -> AO
// 2 : GO
//b结束:b.[[scope]] -> 0 : a -> AO
// 1 : GO
// c.[[scope]] X
//a结束:a.[[scope]] -> 0 : GO
// b.[[scope]] X

闭包

当内部函数被返回到外部并保持时,一定会产生闭包,闭包会产生原来的作用域链不释放,
过度的闭包可能会导致内存泄漏或加载过慢

1
2
3
4
5
6
7
8
9
10
11
12
13
function test1() {
function test2() {
var b = 2
console.log(a) //1
}
var a = 1
return test2 //test1执行结束,test1自己的AO销毁,返回test2,
//当test1赋值到全局的test3时,test2被挂载到全局GO
}
var c = 3
var test3 = test1() //此处返回的是函数test2。test2中包含test1的AO,可以访问到test1内部的变量
test3() //test3执行结束时,test2也结束,test2的AO销毁,但是test1的AO未断开,
//再执行test3仍可以操作test1的变量

什么是闭包

函数嵌套函数,内部函数就是闭包。就是能够访问其他函数内部变量的函数
闭包可以做数据缓存

普通闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function test() {
let n = 100
function add() {
n++
console.log(n)
}
function reduce() {
n--
console.log(n)
}
return [add, reduce] //返回两个函数
}
let arr = test()
arr[0]()
arr[1]()
arr[1]()
//101
//100
//99

add和reduce函数属于同级,两个的AO互不干扰,不能互相访问。但是都有上一级test的AO,可以同时访问test的变量

对象闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function test() {
let num = 0
let compute = {
add: function () {
num++
console.log(num)
},
minus: function () {
num--
console.log(num)
},
}
return compute
}
let compute = test()
compute.add() //1
compute.add() //2
compute.minus() //1

构造函数闭包

构造函数被实例化时,内部产生一个this,最后隐式返回this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Compute() {
let num = 10
this.add = function () {
num++
console.log(num)
}
this.minus = function () {
num--
console.log(num)
}
//return this //构造函数被实例化时,内部产生一个this,最后隐式返回this
}
let compute = new Compute()
compute.add() //11
compute.add() //12
compute.minus() //11

闭包中的this对象

在闭包中使用 this 会让代码变复杂。
如果内部函数没有使用箭头函数定义,则 this 对象会在运行时绑定到执行函数的上下文。
如果在全局函数中调用,则 this 在非严格模式下等于 window,在严格模式下等于 undefined。
如果作为某个对象的方法调用,则 this 等于这个对象。
匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。
不过,由于闭包的写法所致,这个事实有时候没有那么容易看出来。

1
2
3
4
5
6
7
8
9
10
window.identity = 'The Window'
let object = {
identity: 'My Object',
getIdentityFunc() {
return function () {
return this.identity
}
},
}
console.log(object.getIdentityFunc()()) // 'The Window'

为什么匿名函数没有使用其包含作用域(getIdentityFunc())的 this 对象呢?
每个函数在被调用时都会自动创建两个特殊变量:this 和 arguments。内部函数永远不可能直接访问外部函数的这两个变量。但是,如果把 this 保存到闭包可以访问的另一个变量中, 则是行得通的。

1
2
3
4
5
6
7
8
9
10
11
window.identity = 'The Window'
let object = {
identity: 'My Object',
getIdentityFunc() {
let that = this
return function () {
return that.identity
}
},
}
console.log(object.getIdentityFunc()()) // 'My Object'

在定义匿名函数之前,先把外部函数的 this 保存到变量 that 中。然后在定义闭包时,就可以让它访问 that,因为这是包含函数中名称没有任何冲突的一个变量。即使在外部函数返回之后,that 仍然指向 object,所以调用 object.getIdentityFunc()() 就会返回”My Object”

立即执行函数

自动执行,执行完成之后立即释放
立即执行函数 IIFE - immediately-invoked function
()括号包起来的都叫表达式,一定是表达式才能被执行符号执行

1
2
3
4
5
(function () {
})()

((function () {
})()) //W3C推荐

立即执行函数一定是表达式,非表达式不能执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//表达式
(function test1() {
console.log(1)
})()
//1

//非表达式
function test() {
console.log(1);
}()
//Uncaught SyntaxError: Unexpected token ')'

function test() {
console.log(1)
}(2)
//不报错,默认是一个函数和表达式

立即执行函数执行完后立即销毁

1
2
3
   (function test() {})()
// test.name
// VM561:1 Uncaught ReferenceError: test is not defined at <anonymous>:1:1

普通函数执行完后不释放

1
2
3
function test() {}
// test.name
// 'test'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//普通函数赋值不执行
let test = function () {
console.log(1)
}
console.log(test)
// ƒ () {
// console.log(1)
// }

//立即执行函数,马上执行被销毁
let test1 = (function () {
console.log(2)
})()
console.log(test1) //undefined
//2
//undefined

立即执行函数可带参数

1
2
3
(function (a, b) {
console.log(a + b) //6
})(2, 4) //后边的小括号可带实参,函数执行后立即销毁

立即执行函数的返回值要赋值给变量才能访问

1
2
3
4
let num = (function (a, b) {
return a + b
})(2, 4)
console.log(num) //6

函数声明变成表达式的其他方法 + - ! || && ,函数变成表达式之后,函数名自动忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 1 && (function () {
console.log(1) //1
})()

undefined || (function () {
console.log(1) //1
})()

-(function () {
console.log(1) //1
})()

!(function () {
console.log(1) //1
})()
1
2
3
4
5
var a = 10
if (function b() {}) {
a += typeof b //(function b() {})是表达式,函数名会被忽略,所以typeof b是undefined
}
console.log(a) //10undefined

将以下代码修改,打印出0到9的数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function test() {
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function () {
document.write(i + ' ')
} //不是立即执行函数,i=10时,就存了10个匿名函数
}

// var i = 0
// for (; i < 10; ) {
// arr[i] = function () {
// document.write(i + ' ')
// } //不是立即执行函数,i=9时,存了10个匿名函数
// }
// i++

// console.log(i)//10
return arr //return时,形成了闭包,i已经等于10了,匿名函数公有test的AO,
}
var myArr = test()
console.log(myArr)
// [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
for (var j = 0; j < 10; j++) {
myArr[j]() //循环j的时候,拿到的值是最后一次i的值
}
//10 10 10 10 10 10 10 10 10 10

修改后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//方法一,第二个循环里的立即执行函数给它内部的匿名函数传参
function test() {
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function (num) {
document.write(num + ' ')
}
}
return arr
}
var myArr = test()
console.log(myArr)
// [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
for (var j = 0; j < 10; j++) {
myArr[j](j)
}
//0 1 2 3 4 5 6 7 8 9

//方法二,在第一个循环添加一个立即执行函数,将循环每一次的i保存并传给它内部的匿名函数
function test1() {
var arr = []
for (var i = 0; i < 10; i++) {
(function (j) {
arr[j] = function () {
document.write(j + ' ')
}
})(i)
}
return arr
}
var myArr = test1()
console.log(myArr)
// [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
for (var j = 0; j < 10; j++) {
myArr[j]()
}
// 0 1 2 3 4 5 6 7 8 9

//方法三,直接使用立即执行函数
function test2() {
for (var i = 0; i < 10; i++) {
(function () {
document.write(i + ' ')
})()
}
}
test2() //0 1 2 3 4 5 6 7 8 9

修改以下代码,使之点击对应的li,打印对应li下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var uLi = document.querySelectorAll('li')
for (var i = 0; i < uLi.length; i++) {
uLi[i].onclick = function () { //.onclick就相当于是return,每点击一下就形成了闭包
console.log(i) //不管点击哪一个li都是5
}
}
</script>

用立即执行函数包裹点击事件,并将外部的i作为实参传入匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
var uLi = document.querySelectorAll('li')
for (var i = 0; i < uLi.length; i++) {
;(function (j) {
uLi[j].onclick = function () {
console.log(j)
}
})(i) //用立即执行函数包裹点击事件,并将外部的i作为实参传入匿名函数
}
</script>

逗号运算

逗号运算默认只输出最后一个

1
2
let num = (1 + 1, 2 + 2, 1 - 1)
console.log(num) // 0
1
2
3
4
5
6
7
var fn = (function test1() {
return 1
},
function test2() {
return '2'
})()
console.log(typeof fn) //string

对象

创建对象

对象字面量

在对象内部,都是键值对来存储数据

1
2
3
4
5
6
7
let obj = {
name: '张三',
sex: '男',
}
//对象字面量
obj.name = '李四'
console.log(obj)

构造函数

对象和构造函数不是同一个东西,对象是通过实例化构造函数(new Object())而创造的对象实例

1
2
let obj = new Object() //与对象字面量相等
obj.name = '张三'

构造函数实例化之后this指向实例化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Test() {
//new的时候构造函数内部产生成this,生成原型链
// let this = {
// __proto__: Test.prototype
// }
this.name = '123'
}
let test = new Test()
// new 之前this指向window
// AO = {
// this: window
// }

// new 之后this接收属性和原型生成原型链
// AO = {
// this:{
// name:'123', //接收属性
// __proto__: Test.prototype //生成原型链
// }
// }
//new的时候产生GO
// GO = {
// Test: function test() {...}
// test:{ //new 之后
// name:'123', //接收属性
// __proto__: Test.prototype //生成原型链
// }
// }

工厂模式

这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name, age, job) {
let o = new Object()
o.name = name
o.age = age
o.job = job
o.sayName = function () {
console.log(this.name)
}
return o
}
let person1 = createPerson('Nicholas', 29, 'Software Engineer')
let person2 = createPerson('Greg', 27, 'Doctor')

实例化原理

自定义构造函数(构造函数模式)

建议大驼峰,区别于普通函数
构造函数模式和工厂模式的区别

  • 没有显式地创建对象。
  • 属性和方法直接赋值给了 this。
  • 没有 return。

在对象实例化之前,this指向window,实例化对象之后,this指向实例化的那个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//类似构造工厂,此时没有实例化,this指向window
function Teacher() {
this.name = '张三' //此处this没有指向,谁new之后才指向谁
this.sex = '男'
this.smoke = function () {
console.log('I am smoking')
}
}
let teacher1 = new Teacher() //实例化对象之后,此时this指向teacher1
let teacher2 = new Teacher() //此时this指向teacher2
teacher1.name = '李四'
//实例化的两个对象修改属性互不干扰
console.log(teacher1, teacher2)
// Teacher {name: '李四', sex: '男', smoke: ƒ}
// Teacher {name: '张三', sex: '男', smoke: ƒ}

也可以传参,解决创建多个类似对象的问题

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
console.log(this.name)
}
}
let person1 = new Person('Nicholas', 29, 'Software Engineer')
let person2 = new Person('Greg', 27, 'Doctor')
person1.sayName() // Nicholas
person2.sayName() // Greg

也可以传对象,进行配置,方便以后维护和使用。vue也是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(opt) {
this.name = opt.name
this.age = opt.age
this.job = opt.job
this.sayName = function () {
console.log(this.name)
}
}
let person1 = new Person({
name: '张三',
age: '男',
job: '老师',
})
let person2 = new Person({
name: '张三2',
age: '女',
job: '老师',
})
console.log(person1, person2)
//Person {name: '张三', age: '男', job: '老师', sayName: ƒ}
//Person {name: '张三2', age: '女', job: '老师', sayName: ƒ}
1
2
3
4
5
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data,
})
构造函数中的this

第一步,保存一个空的this对象
第二步,执行构造函数内部的代码,将数据存入空this对象
第三步,构造函数隐式的返回this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Car(color, brand) {
//实例化之后内部产生的this
// this={ //第二步,执行构造函数内部的代码,将数据存入this(添加属性)
// color:color,
// brand:brand
// }
this.color = color
this.brand = brand

//return this; //第三步,隐式的返回this,所以GO才能拿到car1这个对象的this
}
//Car被实例化时,相当于普通函数被执行,就有自己的AO
let car1 = new Car('red', 'Benz') //相当于返回构造之后的this
// GO = {
// Car:(function)
// car1:{ //实例化对象后就产生car1变量
// color:'red',
// brand:'Benz'
// }
// }
// AO = {
// this: {
// color:color,
// brand:brand
// } //第一步,实例化时就产生AO和空的this对象。第二步执行后才将数据存入
// }
console.log(car1.color)
不用new和this构造函数
1
2
3
4
5
6
7
8
9
function Car(color, brand) {
let me = {}
me.color = color
me.brand = brand
return me
}
let car = Car('red', 'Mazda')
console.log(car.color) // red
console.log(car.brand) // Mazda
显示返回一个值

返回原始值没有用

1
2
3
4
5
6
7
8
function Car() {
this.color = 'red'
this.brand = 'Mazda'
// return 123 //Car {color: 'red', brand: 'Mazda'}
return 'abc' //Car {color: 'red', brand: 'Mazda'}
}
let car = new Car()
console.log(car)

返回引用值就会覆盖

1
2
3
4
5
6
7
8
9
function Car() {
this.color = 'red'
this.brand = 'Mazda'
// return ['1'] //['1']
// return {} //{}
return function name() {} //ƒ name() {}
}
let car = new Car()
console.log(car)

计算加法乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Compute() {
let args = arguments,
res
this.plus = function () {
res = 0
loop('add', res)
}
this.times = function () {
res = 1
loop('mul', res)
}
function loop(method, res) {
for (let i = 0; i < args.length; i++) {
let item = args[i]
if (method === 'add') {
res += item
} else if (method === 'mul') {
res *= item
}
}
console.log(res)
}
}
let compute = new Compute(2, 4, 6)
compute.plus() //12
compute.times() //48
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Compute() {
let res = 0
this.plus = function () {
loop(arguments, 'add', res)
}
this.times = function () {
res = 1
loop(arguments, 'mul', res)
}
function loop(args, method, res) {
for (let i = 0; i < args.length; i++) {
let item = args[i]
if (method === 'add') {
res += item
} else if (method === 'mul') {
res *= item
}
}
console.log(res)
}
}
let compute = new Compute()
compute.plus(2, 4, 6) //12
compute.times(3, 5, 7) //105
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function Car(opt) {
this.brand = opt.brand
this.color = opt.color
this.displacement = opt.displacement
}
function Person(opt) {
this.name = opt.name
this.age = opt.age
this.income = opt.income
this.selectCar = function () {
let myCar = new Car(opt.carOpt)
console.log(
this.name +
'挑选了排量' +
myCar.displacement +
'的' +
myCar.color +
myCar.brand,
)
}
}
let jone = new Person({
name: '约翰',
age: 29,
income: '20k',
carOpt: {
brand: '马自达',
color: '红色',
displacement: '2.0',
},
})
jone.selectCar()

输入字符串,判断多少字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let getBytes1 = function (str) {
let bytes = 0
for (let i = 0; i < str.length; i++) {
let pos = str.charCodeAt(i)
if (pos <= 255) {
bytes++
} else {
bytes += 2
}
}
return bytes
}
console.log(getBytes1('你好! 123')) //11
function getBytes2(str) {
let bytes = str.length
for (let i = 0; i < str.length; i++) {
let pos = str.charCodeAt(i)
if (pos > 255) {
bytes++
}
}
return bytes
}
console.log(getBytes2('你好! 123')) //10

包装类

原始值没有自己的方法和属性,但是可以进行包装变成对象
string是原始值,没有length属性
包装系统内置三种构造函数new Numbernew Stringnew Boolean
包装成对应对象之后可以为其添加属性,也能进行计算

1
2
3
4
5
6
7
let a = new Number(1)
console.log(a) //Number {1}
a.A = '1'
console.log(a) //Number {1, A: '1'}
let a1 = a + 1
console.log(a1) //2
console.log(a) //Number {1, A: '1'}
1
2
3
4
5
6
7
8
let test = new Number(undefined)
console.log(test) //Number {NaN}
let test1 = new Number(null)
console.log(test1) //Number {0}
let test2 = new String(undefined)
console.log(test2) //String {'undefined'}
let test3 = new String(null)
console.log(test3) //String {'null'}

undefined、null是不可以设置任何属性和方法

1
2
console.log(null.length) //Cannot read properties of null (reading 'length')
console.log(undefined.length) //Cannot read properties of undefined (reading 'length')

包装类过程

如果不是对象

  • 先判断定义类型是什么
  • 然后系统自动转化成对应对象
  • 最后无法保存对象,就只能删掉那个属性
    1
    2
    3
    4
    5
    //JS包装类
    let a = 123 //原始值 -> 数字
    a.len = 3
    //new Number(123).len=3; //系统自动转化成对应对象,但是没法保存 只能删掉这个属性
    console.log(a.len) //undefined //再次访问的时候就是undefined
    string是原始值,没有length属性。
    str能够使用length是因为内部已经进行了包装类new String(str).length
    1
    2
    3
    4
    let str = 'abc'
    //str能够使用length是因为内部已经进行了包装new String(str).length
    //new String(str).length
    console.log(new String(str).length) //3
    数组有length属性,可以进行截断
    1
    2
    3
    4
    5
    6
    let arr = [1, 2, 3, 4, 5]
    arr.length = 3
    console.log(arr) // [1, 2, 3]
    let arr1 = [1, 2, 3, 4, 5]
    arr1.length = 6
    console.log(arr1) // [1, 2, 3, 4, 5, empty]
    string不可以被截断,
    因为开始判断是原始值,然后进行包装类,后来因为不能保存,就删掉,结果就是打印无变化
    1
    2
    3
    4
    let str = 'abc'
    str.length = 1 //new String(str).length=1 是原始值,进行包装类
    //delete 无法保存就删除掉
    console.log(str) //abc 最后打印没有变化
    修改以下代码,打印出string
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let name = 'abc'
    name += 10 //'abc10'
    let type = typeof name //'string'
    if (type.length === 6) {
    //true
    type.text = 'string' //new String(type).text='string'原始值包装类,但是无法保存
    //delete
    }
    console.log(type.text) //undefined
    提前包装类,转换成String对象,之后就可以添加属性
    1
    2
    3
    4
    5
    6
    7
    8
    let name = 'abc'
    name += 10 //'abc10'
    let type = new String(typeof name) //直接包装类,打印出字符串 string
    if (type.length === 6) {
    //true
    type.text = 'string' //type已经是string对象可以添加属性
    }
    console.log(type.text) //string
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function Test(a, b, c) {
    let d = 1
    this.a = a
    this.b = b
    this.c = c
    function f() {
    d++
    console.log(d)
    }
    this.g = f
    //return this; 隐式return this 形成了闭包。
    //AO里含有d,所以之后就能访问到
    }
    let test1 = new Test()
    test1.g() //2
    test1.g() //3
    let test2 = new Test()
    test2.g() //2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var x = 1,
    y = (z = 0)
    function add(n) {
    return (n = n + 1)
    }
    y = add(x)
    function add(n) {
    return (n = n + 3)
    }
    z = add(x)
    console.log(x, y, z) //1 4 4
    // GO = {
    // x: 1,
    // y: 0,
    // z: 0,
    // add:function add(n) {return n=n+3} //重名覆盖了
    // }
    下列哪些可以打印1 2 3 4 5
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    	//可以
    function foo1(x) {
    console.log(arguments)
    return x
    }
    foo1(1, 2, 3, 4, 5)

    //不可以
    // function foo2(x) {
    // console.log(arguments)
    // return x
    // }(1, 2, 3, 4, 5)

    //可以
    (function foo3(x) {
    console.log(arguments)
    return x
    })(1, 2, 3, 4, 5)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function b(x, y, a) {
    a = 10
    console.log(arguments[2]) //10
    }
    b(1, 2, 3)
    function c(x, y, a) {
    arguments[2] = 10
    console.log(a) //10
    }
    c(1, 2, 3)

    原型prototype

原型对象prototype其实是构造函数(function对象)的一个属性,但是他也是一个对象
只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。

1
2
3
function Test() {}
//prototype
console.log(Test.prototype) //{constructor: ƒ}

prototype是定义构造函数构造出的每个对象的公共祖先,所有被该构造函数构造出来的对象都可以继承原型上的属性和方法
对象自己身上有的就不会访问原型上的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Handphone(color, brand) {
this.color = color
this.brand = brand
this.screen = '18:9'
}
Handphone.prototype.rom = '64G'
Handphone.prototype.ram = '6G'
Handphone.prototype.screen = '16:9'
let hp1 = new Handphone('red', '小米')
let hp2 = new Handphone('black', '红米')
console.log(hp1.rom) //64D
console.log(hp2.ram) //6G
console.log(hp1.screen) //18:9 //自己身上有的就访问自己的,不会访问祖先的
console.log(hp2.screen) //18:9

推荐固定的值或者方法写在原型上,需要变化或者传参配置的值写在构造函数内部,这样可以减少代码冗余

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   function Handphone(color, brand) {
this.color = color
this.brand = brand
}
// Handphone.prototype.rom = '64G'
// Handphone.prototype.ram = '6G'
// Handphone.prototype.screen = '16:9'
// Handphone.prototype.call = function () {
// console.log('I am calling')
// }
//开发中一般写在一起
Handphone.prototype = {
rom: '64G',
ram: '6G',
screen: '16:9',
call: function () {
console.log('I am calling')
},
}
let hp1 = new Handphone('red', '小米')
let hp2 = new Handphone('black', '红米')
hp2.call() //I am calling

通过实例化对象不能增删改祖先prototype上的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Test() {}
Test.prototype.name = 'TEST'
let test = new Test()
console.log(Test.prototype) //{name: 'TEST', constructor: ƒ}
test.job = 123
console.log(Test.prototype, test)
//{name: 'TEST', constructor: ƒ}
//Test {job: 123}
test.name = test
console.log(Test.prototype, test)
//{name: 'TEST', constructor: ƒ}
//Test {job: 123, name: Test}
delete test.name
console.log(Test.prototype, test)
//{name: 'TEST', constructor: ƒ}
//Test {job: 123}

构造器constructor

constructor默认指向构造函数本身
在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自 Object。
构造函数有一个 prototype 属性引用其原型对象,而这个原型对象也有一个constructor 属性,引用这个构造函数。换句话说,两者循环引用

1
2
3
4
5
6
7
8
9
10
11
function Handphone(color, brand) {
this.color = color
this.brand = brand
}
console.log(Handphone.prototype) //{constructor: ƒ}
// {constructor: ƒ}
// constructor: ƒ Handphone(color, brand)
// [[Prototype]]: Object

console.log(Handphone.prototype.constructor === Handphone)

可以通过原型内部的constructor更改构造函数的constructor

1
2
3
4
5
6
7
8
9
10
11
12
function Telephone() {}
function Handphone(color, brand) {
this.color = color
this.brand = brand
}
Handphone.prototype = {
constructor: Telephone,
}
console.log(Handphone.prototype)
// {constructor: ƒ}
// constructor: ƒ Telephone()
// [[Prototype]]: Object

proto( [[Prototype]] )

proto__只是一个容器,实例对象通过__proto__这个键名指向原型对象prototype
构造函数的prototype === 实例的__proto

1
2
3
function Car() {}
let car = new Car()
console.log(Car.prototype === car.__proto__) //true

**__proto__是实例化以后的结果,__proto__属于实例化对象**
构造函数被实例化时会产生一个this,this默认有一个__proto__属性,指向他的原型。如果this中没有找到对应的属性,就会沿着__proto__找原型里的属性

1
2
3
4
5
6
7
8
9
function Car() {
// var this = {
// __proto__: Car.prototype, //产生的this里边默认有__proto__属性,指向他的原型
// 如果this中没有对应的属性,就会根据__proto__向上找原型里的属性
// }
}
Car.prototype.name = 'Benz'
let car = new Car() //构造函数被实例化时,内部产生一个this
console.log(car.name) //Benz

__proto__只是一个内部属性,也可以改

1
2
3
4
5
6
7
8
9
function Person() {}
Person.prototype.name = '张三'
let p1 = {
name: '李四',
}
let person = new Person()
console.log(person.name) //张三
person.__proto__ = p1
console.log(person.name) //李四

实例化前后赋值prototype

实例化之前赋值prototype可以修改

1
2
3
4
5
Car.prototype.name = 'Mazda'
Car.prototype.name = 'Benz'
function Car() {}
let car = new Car()
console.log(car.name) //Benz

实例化之后赋值prototype可以修改

1
2
3
4
5
Car.prototype.name = 'Mazda'
function Car() {}
let car = new Car()
Car.prototype.name = 'Benz'
console.log(car.name) //Benz

实例化后重写prototype

实例化之前重写可以修改prototype属性

1
2
3
4
5
Car.prototype.name = 'Benz'
function Car() {}
Car.prototype = { name: 'Mazkda' }
let car = new Car()
console.log(car.name) //Mazkda

实例化之后重写的prototype属性不能影响实例化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Car.prototype.name = 'Benz'
function Car() {}
let car = new Car()
Car.prototype = {
//这是实例化之后重写的,不能影响实例的值
name: 'Mazkda',
}
console.log(car.name) //Benz
//实例化时
// function Car() {
// let this = {
// __proto__: (Car.prototype = {
// name: 'Benz',
// }),
// }
// }

实例化对象的constructor指向的是构造函数,constructor保存的是实例化之前的东西
实例化之后再重写prototype(属于未实例化/实例化之前)就会被放到.prototype.constructor
所以实例化之后再重写prototype,此时重写的是实例化之前Car.prototype.constructor的prototype,对实例化对象无影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Car.prototype.name = 'Benz'
function Car() {}
var car = new Car()
//相当于与上一次实例化之后在重写prototype(属于未实例化/实例化之前)
//(属于未实例化/实例化之前)就被放到Car.prototype.constructor里
Car.prototype = {
name: 'Mazkda', //此时重写的是实例化之前Car.prototype.constructor的prototype
}
console.log(car)
// Car {}
// [[Prototype]]: Object
// name: "Benz"
// constructor: ƒ Car() //constructor保存的是实例化之前的东西
// arguments: null
// caller: null
// length: 0
// name: "Car"
// prototype: {name: 'Mazkda'}
console.log(car.name) //Benz
//实例化对象的constructor指向的是Car构造函数
console.log(car.constructor.prototype) //{name: 'Mazkda'}
// function Car() {
// let this = {
// __proto__: (Car.prototype = {
// name: 'Benz',
// }),
// }
// }
//Car.prototype.constructor -> Car() -> prototype -> name : Benz
1
2
3
4
5
6
7
8
9
10
11
12
function test() {
let a = 1
function plus1() {
a++
console.log(a)
}
return plus1
}
let plus = test()
plus() //2
plus() //3
plus() //4
1
2
3
4
5
6
7
8
9
10
11
12
function test() {
let a = 1
function plus1() {
a++
console.log(a)
}
window.plus = plus1
}
test()
plus() //2
plus() //3
plus() //4
1
2
3
4
5
6
7
8
9
10
11
let plus = (function () {
let a = 1
function plus1() {
a++
console.log(a)
}
return plus1
})()
plus() //2
plus() //3
plus() //4
1
2
3
4
5
6
7
8
9
10
11
(function () {
let a = 1
function plus1() {
a++
console.log(a)
}
window.plus = plus1
})()
plus() //2
plus() //3
plus() //4

原型链

沿着__proto__( [[Prototype]] )去找原型里的属性,一层一层继承原型的属性这条链就是原型链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Professor.prototype.tSkill = 'JAVA'
function Professor() {}
let professor = new Professor()

Teacher.prototype = professor
function Teacher() {
this.mSkill = 'JS'
}
let teacher = new Teacher()

Student.prototype = teacher
function Student() {
this.pSkill = 'HTML'
}
let student = new Student()

console.log(student)
// Student {pSkill: 'HTML'}
// [Prototype]]: Professor
// mSkill: "JS"
// [[Prototype]]: Professor
// [[Prototype]]: Object
// tSkill: "JAVA"
// constructor: ƒ Professor()
// [[Prototype]]: Object

原型链的顶端是Object.prototype
Object.prototype里边保存了toString方法
Object.prototype.__proto__是null

1
console.log(Object.prototype.__proto__ === null) //true

如果是引用值,子级可以更改/添加父级或者祖先的属性(不推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Professor.prototype.tSkill = 'JAVA'
function Professor() {}
let professor = new Professor()

Teacher.prototype = professor
function Teacher() {
this.mSkill = 'JS'
this.success = {
alibaba: '10',
}
}
let teacher = new Teacher()

Student.prototype = teacher
function Student() {
this.pSkill = 'THML'
}
let student = new Student()

student.success.baidu = '1'
student.success.alibaba = '1'
console.log(teacher, student)
// Professor {mSkill: 'JS', success: {…}}
// mSkill: "JS"
// success: {alibaba: '1', baidu: '1'}
// [[Prototype]]: Professor

// Student {pSkill: 'THML'}
// pSkill: "THML"
// [[Prototype]]: Professor
// mSkill: "JS"
// success: {alibaba: '1', baidu: '1'}
// [[Prototype]]: Professor

如果是原始值,子级不能修改/增加父级或者祖先的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Professor.prototype.tSkill = 'JAVA'
function Professor() {}
let professor = new Professor()

Teacher.prototype = professor
function Teacher() {
this.mSkill = 'JS'
this.students = 500
}
let teacher = new Teacher()

Student.prototype = teacher
function Student() {
this.pSkill = 'THML'
}
let student = new Student()

student.students++
console.log(teacher, student)
// Professor {mSkill: 'JS', students: 500}
// mSkill: "JS"
// students: 500
// [[Prototype]]: Professor

// Student {pSkill: 'THML', students: 501}
// pSkill: "THML"
// students: 501
// [[Prototype]]: Professor
// mSkill: "JS"
// students: 500
// [[Prototype]]: Professor

this谁用指向谁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Car() {
this.brand = 'Benz'
}
Car.prototype = {
brand: 'Mazda',
intro: function () {
console.log('我是' + this.brand)
},
}
let car = new Car()
//实例化时构造函数内部产生空this,然后执行代码将数据给this。
//构造函数中有brand就用自己的,没有intro就在原型上找
// function Car() {
// let this={
// barnd:'Benz'
// }
// }
// Car.prototype = {
// brand: 'Mazda',
// intro: function () {...},
// }
car.intro() //我是Benz
Car.prototype.intro() //我是Mazda. //this谁用指向谁

不是所有的对象都继承于Object.prototype。
Object.create(null)创建的实例对象不继承Object.prototype。

1
2
3
4
5
6
7
8
9
let obj = Object.create(null)
obj.num = 1
console.log(obj) //{num: 1} //不继承Object.prototype
// obj.toString() //obj.toString is not a function //也没有toString方法
let obj1 = {
count: 2,
}
obj.__proto__ = obj1 //没有继承Object.prototype的情况下,手动设置__proto__是无用的,无法访问
console.log(obj.count) //undefined

原始值是没有属性,所以undefined和null没有toString方法
undefined和null不能经过包装类,还没有原型

1
2
3
let num = 1
console.log(num.toString()) //打印出字符串 1
//这里原始值可以使用toString是因为进行了包装类new Number(1)所以才有toString()

1
2
3
4
5
6
7
8
9
10
let num = 1
let obj = {}
let obj2 = Object.create(null)
//document.write会将数据隐式转换成string
document.write(num) //1
document.write(obj) //[object Object] //对象类型的Object
// document.write(obj2) //Cannot convert object to primitive value不能将对象转为为原始值
console.log(obj2)
// {}
// No properties //没有内容
1
2
3
4
5
6
7
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call('a') // '[object String]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call([1, 2, 3]) // '[object Array]'
Object.prototype.toString.call({ name: 1 }) // '[object Object]'

Number.prototype.toString.call(1) //'1'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function Foo() {
getName = function () {
console.log(1)
}
return this
}
Foo.getName = function () {
console.log(2)
}
Foo.prototype.getName = function () {
console.log(3)
}
var getName = function () {
console.log(4)
}
function getNane() {
console.log(5)
}
//预编译
//1、变量声明
//2、函数声明
//3、运行 -> 赋值
Foo.getName() //2 getName只是Foo上的属性,和Foo()没有关系
getName() //4 预编译时就有了
// GO={
// getName:undefined//1
// function getName() {console.log(5)} //2
// function () {console.log(4)} //3
// }
Foo().getName() //1 Foo()中的getName是全局 替换掉GO中的
// GO={
// getName:function getName() {console.log(1)}
// }
getName() //1 全局中已经是1了
new Foo.getName() //2 点运算优先级大于new,new在这里不起作用
new Foo().getName() //3 括号优先级大于点,但是括号前有new是一起执行的,最后执行点
//找构造函数里的this.getName(),没有就在原型上找
new new Foo().getName() //3 同上两个先实例化构造函数,然后执行点,第一个new不起作用

继承

最下边的对象会继承原型链上所有的属性,都可以通过__proto__找到。
同时有一个问题,在子级prototype修改或者添加属性时,会影响到整个原型链上的属性

圣杯模式

加一个空的中间缓存对象,将父级prototype赋值给缓存对象的prototype,然后将缓存对象的实例对象赋值给子级prototype。因为实例化对象不能修改prototype上的值,所以缓存对象的实例对象赋值给子级prototype,这样子级是无法修改prototype的。解决继承和隔离问题,避免原型链的全局污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Teacher() {
this.name = 'Mr.L'
this.tSkill = 'JAVA'
}
Teacher.prototype = {
pSkill: 'JS',
}
let t = new Teacher()

function Student() {
this.name = 'Mr.W'
}

function Buffer() {} //添加一个中间缓存构造函数
Buffer.prototype = Teacher.prototype //父级的原型赋值给缓存的原型
let buffer = new Buffer()

Student.prototype = buffer //缓存的实例对象赋值给子级原型
Student.prototype.age = 19
let s = new Student()

console.log(t) //Teacher {name: 'Mr.L', tSkill: 'JAVA'}
console.log(s)
// Student {name: 'Mr.W'}
// name: "Mr.W"
// [[Prototype]]: Object
// age: 19
// [[Prototype]]: Object
// pSkill: "JS"
// [[Prototype]]: Object
封装圣杯模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function inherit(Target, Origin) {
function Buffer() {}
Buffer.prototype = Origin.prototypr
Target.prototype = new Buffer()
Target.prototype.constructor = Target //构造器指向自己
Target.prototype.supper_class = Origin //继承源
}

Teacher.prototype.name = 'Mr.Z'
function Teacher() {}
function Student() {}
inherit(Student, Teacher)
let s = new Student()
let t = new Teacher()
console.log(s)
console.log(t)
企业模块化

这个模块化把圣杯模式写成闭包,然后用立即执行函数赋值给一个变量,需要时才执行。减少全局污染,代码更加优雅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let inherit = (function () {
let Buffer = function () {}
return function (Target, Origin) {
Buffer.prototype = Origin.prototype
Target.prototype = new Buffer()
Target.prototype.constructor = Target
Target.prototype.super_class = Origin
}
})()
Teacher.prototype.name = 'Mr.Z'
function Teacher() {}
function Student() {}
inherit(Student, Teacher)
let s = new Student()
let t = new Teacher()
console.log(s)
console.log(t)

call/apply

call/apply可以更改this指向,call/apply调用时this指向指定的那个对象
call和apply则是立即调用,但是call的后续参数同原函数,apply则为包含所有原函数参数的类数组
区别就是call直接传字符串,apply传数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Car(brand, color) {
this.brand = brand
this.color = color
this.run = function () {
console.log('running')
}
}
let newCar = {
displacement: 2.0,
}
Car.call(newCar, 'Benz', 'red')
console.log(newCar) //{displacement: 2, brand: 'Benz', color: 'red', run: ƒ}
Car.apply(newCar, ['Benz2', 'red2'])
console.log(newCar) //{displacement: 2, brand: 'Benz2', color: 'red2', run: ƒ}
let car = new Car('Ben1', 'red1')
console.log(car) //Car {brand: 'Ben1', color: 'red1', run: ƒ}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Compute() {
this.plus = function (a, b) {
console.log(a + b)
}
this.minus = function (a, b) {
console.log(a - b)
}
}
function FullCompute() {
Compute.apply(this)
this.mul = function (a, b) {
console.log(a * b)
}
this.div = function (a, b) {
console.log(a / b)
}
}
let compute = new FullCompute()
compute.plus(1, 1) //2
compute.minus(1, 1) //0
compute.mul(1, 1) //1
compute.div(1, 1) //1

apply改变this指向,使其构造函数Person可以使用Car内部的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Car(brand, color, displacement) {
this.brand = brand
this.color = color
this.displacement = displacement
this.info = function () {
return '排量为' + this.displacement + '的' + this.color + this.brand
}
}
function Person(opt) {
Car.apply(this, [opt.brand, opt.color, opt.displacement])
this.name = opt.name
this.age = opt.age
this.say = function () {
console.log(this.age + '岁的' + this.name + '买了一辆' + this.info())
}
}
let p = new Person({
brand: '奔驰',
color: '红色',
displacement: '3.0',
name: '张三',
age: '25',
})
p.say() //25岁的张三买了一辆排量为3.0的红色奔驰

链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let sched = {
wakeup: function () {
console.log('Running')
return this
},
morning: function () {
console.log('Going')
return this
},
eventing: function () {
console.log('Walking')
return this
},
night: function () {
console.log('Sleeping')
return this
},
}
sched.wakeup().morning().eventing().night()
// Running
// Going
// Walking
// Sleeping

对象属性与遍历

动态属性

设置动态属性,属性名用中括号表示
JS引擎最早也是使用obj['name']处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let myLang = {
No1: 'HTML',
No2: 'CSS',
No3: 'JavaScript',
myStudyingLang: function (num) {
console.log(this['No' + num])
},
}
myLang.myStudyingLang(1) //HTML
myLang.myStudyingLang(2) //CSS

obj = {
name: '123',
}
console.log(obj['name']) //123

遍历

1
2
3
4
let arr = [1, 2, 3, 4, 5]
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let car = {
brand: 'Bena',
color: 'red',
displacement: '3.0',
lang: '5',
width: '2.5',
}
for (let key in car) {
// console.log(car.key) //undefined //car.key -> car['key'] -> undefined
console.log(key + ':' + car[key])
// brand:Bena
// color:red
// displacement:3.0
// lang:5
// width:2.5
}
let arr = [1, 2, 3, 4, 5]
for (i in arr) {
console.log(arr[i])
}
// 1
// 2
// 3
// 4
// 5

判断属性是否在对象里

1
2
3
4
5
6
let car = {
brand: 'Benz',
color: 'red',
}
//car['displacement'] //判断displacement是否在对象里
console.log('displacement' in car) //false

判断属性是否在构造函数里( in 不排除原型)

1
2
3
4
5
6
7
8
9
function Car() {
this.brand = 'Benz'
this.color = 'red'
}
Car.prototype = {
displacement: '3.0',
}
let car = new Car()
console.log('displacement' in car) //true

for in 会把原型链上的属性打印出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Car() {
this.brand = 'Benz'
this.color = 'red'
this.displacement = '3.0'
}
Car.prototype = {
lang: 5,
width: 2.5,
}
Object.prototype.name = 'Object'
let car = new Car()
for (let key in car) {
console.log(key + ':' + car[key])
}
// brand:Benz
// color:red
// displacement:3.0
// lang:5
// width:2.5
// name:Object

hasOwnProperty

hasOwnProperty只打印自身的属性,会排除原型
对象.hasOwnProperty(属性名)会排除自身以外的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Car() {
this.brand = 'Benz'
this.color = 'red'
this.displacement = '3.0'
}
Car.prototype = {
lang: 5,
width: 2.5,
}
Object.prototype.name = 'Object'
let car = new Car()
for (let key in car) {
if (car.hasOwnProperty(key)) {
console.log(key + ':' + car[key])
}
}
// brand:Benz
// color:red
// displacement:3.0

instanceof

可通过 instanceof 操作符来判断对象的具体类型
检测某个对象是不是另一个对象的实例(判断A对象的原型里有没有B的原型)

1
2
3
4
5
6
7
8
9
10
function Car() {}
let car = new Car()
function Person() {}
let p = new Person()
console.log(car instanceof Car) //true
console.log(p instanceof Car) //false
console.log(p instanceof Object) //true
console.log([] instanceof Array) //true
console.log([] instanceof Object) //true
console.log({} instanceof Object) //true
判断一个数据是否为数组
1
2
3
4
let a = []
console.log(a.constructor) //ƒ Array() { [native code] }
console.log(a instanceof Array) //true
console.log(Object.prototype.toString.call(a)) //[object Array]

Object.prototype.toString.call()

toString原本是Object.prototype里边的一种方法,但是使用call()把this指向换了

1
2
3
4
5
6
7
8
let a = []
// Object.prototype.toString.call(a)
Object.prototype = {
toString: function () {
// this.toString() //原本是this指向Object
a.toString() //使用了call(),改变了this指向
},
}

为什么会打印出[object Array]

1
2
3
4
5
6
7
let arr = new Array(1, 2, 3)
console.log(arr) //[1, 2, 3]
//调用arr本身的toString方法
console.log(arr.toString()) //1,2,3
console.log(Object.prototype.toString()) //[object Object]
//调用Object原型上的toString,并改变this指向
console.log(Object.prototype.toString.call(arr)) //[object Array]

判断是否数组(推荐使用Object.prototype.toString.call())

1
2
3
4
5
6
7
8
9
10
let a = []
let str = Object.prototype.toString,
trueTip = '[object Array]'

if (str.call(a) === trueTip) {
console.log('是数组')
} else {
console.log('不是数组')
}
//是数组

callee/caller

callee

callee执行到arguments时所指向的函数是谁,cellee就返回谁

1
2
3
4
5
6
7
8
9
10
function test(a, b, c) {
console.log(arguments.callee) //arguments所对应的函数是谁callee就返回谁
console.log(test.length) //3 形参length
console.log(arguments.callee.length) //3 形参length
console.log(arguments.length) //2 实参length
}
test(1, 2)
// ƒ test(a, b, c) {
// console.log(arguments.callee)
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function test1() {
console.log(1, arguments.callee)
function test2() {
console.log(2, arguments.callee)
}
test2()
}
test1()
// 1 ƒ test1() {
// console.log(1, arguments.callee)
// function test2() {
// console.log(2, arguments.callee)
// }
// test2()
// }
// 2 ƒ test2() {
// console.log(2, arguments.callee)
// }

在立即执行函数中使用

1
2
3
4
5
6
7
8
9
10
	// function sum(n) {
// if (n <= 1) return 1
// return n + sum(n - 1)
// }
// console.log(sum(100)) //5050
let sum = (function (n) {
if (n <= 1) return 1
return n + arguments.callee(n - 1)
})(100)
console.log(sum) //5050
caller

caller返回调用当前函数的函数引用

1
2
3
4
5
6
7
8
9
10
test1()
function test1() {
test2()
}
function test2() {
console.log(test2.caller)//返回当前谁调用test2
}
// ƒ test1() {
// test2()
// }

typeof

typeof可能返回的值有哪些?
object(null)、boolean、number、string、undefined、function、symbol、bigint

1
2
3
4
5
6
7
8
9
console.log(typeof function () {}) //function
console.log(typeof null) //object
console.log(typeof {}) //object
console.log(typeof 1) //number
console.log(typeof 'abc') //string
console.log(typeof true) //boolean
console.log(typeof undefined) //undefined
console.log(typeof Symbol()) //symbol
console.log(typeof 2n) //bigint

this指向

全局this -> window
构造函数的this指向实例化对象
预编译函数this -> window
call/apply 改变this指向,this 指向指定的那个对象

函数内部的this

普通函数(未实例化)内部的this默认指向window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function test(b) {
this.d = 3 //window.d=3
let a = 1
function c() {}
}
test(123)
console.log(d) //3
console.log(this.d) //3
console.log(window.d) //3
// AO = {
// arguments: [123],
// this:window
// b:undefined
// 123
// a:undefined
// c:function c() {}
// }

构造函数内部的this

构造函数实例化之后this指向实例化对象

预编译函数this

预编译函数this指向window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Test() {
//new的时候构造函数内部产生成this,然后生成__proto__被赋值构造函数的prototype,生成原型链
// let this = {
// __proto__: Test.prototype
// }
this.name = '123'
}
let test = new Test()
// new 之前this指向window
// AO = {
// this: window
// }

// new 之后this接收属性和原型对象,生成原型链
// AO = {
// this:{
// name:'123', //接收属性
// __proto__: Test.prototype //生成原型链
// }
// }
//new的时候产生GO
// GO = {
// Test: function test() {...}
// test:{ //new 之后
// name:'123', //接收属性
// __proto__: Test.prototype //生成原型链
// }
// }

call/apply

call/apply调用时this 指向指定的那个对象
apply 方法的第一个参数是作为函数上下文的对象,此时函数里的 this 便指向了构造函数Person。
所以实例对象p可以借用构造函数Person里边的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person() {
this.name = '张三'
this.age = 18
}
function Programmer() {
Person.apply(this)
this.work = 'Programmer'
}
let p = new Programmer()
console.log(p) //Programmer {name: '张三', age: 18, work: 'Programmer'}

// function Person(name, age) {
// this.name = name
// this.age = age
// }
// function Programmer(name, age) {
// Person.apply(this, [name, age])
// this.work = 'Programmer'
// }
// let p = new Programmer('张三', 18)
// console.log(p) //Programmer {name: '张三', age: 18, work: 'Programmer'}
1
2
3
4
5
6
7
8
9
10
function foo() {
bar.apply(null, arguments) //相当于bar.call() -> bar(arguments)
// bar.call()
}
function bar() {
console.log(arguments)
}
foo(1, 2, 3, 4, 5)
//Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]

1
2
3
4
5
6
7
8
9
10
   let f =
(function f() {
return '1'
},
function g() {
return 2
})
//f=( f(), g() ) -> f=g()
console.log(f) //ƒ g() {return 2}
console.log(typeof f) //function
1
2
3
4
5
6
7
8
9
let f = (function f() {
return '1'
},
function g() {
return 2
})()
//f=( f(), g() )() -> f=g()() -> f()=g()
console.log(f) // 2
console.log(typeof f) //number
1
2
3
4
console.log(undefined == null) //true
console.log(undefined === null) //false
console.log(isNaN('100')) //false
console.log(parseInt('1a') == 1) //true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function isNaN1(num) {
let res = Number(num)
if (res == NaN) return true
else return false
}
console.log(isNaN1('abc')) //false
// console.log(NaN == NaN) //false

function isNaN2(num) {
let res = Number(num) + ''
if (res == 'NaN') return true
else return false
}
console.log(isNaN2('abc')) //true
// console.log('NaN' == 'NaN') //true
1
2
3
4
5
6
7
//两个空对象不相等
console.log({} == {}) //false //因为引用值对比的是地址,所以不相等
//如何让两个空对象相等
let obj1 = {}
obj2 = obj1
console.log(obj1 == obj2) //true
//空对象先赋值给一个变量,这个变量赋值给另一个变量就相等了
1
2
3
4
5
6
7
8
9
var a = '1'
function test() {
var a = '2'
this.a = '3' //这里this默认是全局window
console.log(a)
}
test() //2
new test() //2
console.log(a) //3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 5
function test() {
a = 0
console.log(a)
console.log(this.a)
var a
console.log(a)
}
test() // 0 5 0
//0和0是自己有的,this.a是window上的
new test() //0 undefined 0
//0和0是自己有的,实例化之后内部产生的this,但是没有给this加上a这个属性

// var b = 5
// function test2() {
// b = 0
// console.log(this)
// var b
// }
// new test2() //test2 {} //没有任何属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Test(a, b, c) {
let d = 0
this.a = a
this.b = b
this.c = c
function e() {
d++
console.log(d)
}
this.f = e
//new之后
// let this={
// f:function () {d++;console.log(d)}
// }
// return this //隐式return this形成闭包
}
// AO = {
// d: undefined, //1
// 0 //4
// }
let test1 = new Test()
test1.f() //1
test1.f() //2
let test2 = new Test()
test2.f() //1

三目运算符

普通三目运算

1
2
3
4
5
6
7
8
9
10
let a = 5
// if (a > 0) {
// console.log('大于0')
// } else {
// console.log('小于等于0')
// }
// //大于0

a > 0 ? console.log('大于0') : console.log('小于等于0')
//大于0

三目运算自带return功能

1
2
3
let a = 5
let str = a > 0 ? '大于0' : '小于等于0'
console.log(str) //大于0

三目运算嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = 5,
str = ''
// if (a > 0) {
// if (a > 3) {
// str = '大于3'
// } else {
// str = '小于等于3'
// }
// } else {
// str = '小于等于0'
// }
// console.log(str) //大于3
str = a > 0 ? (a > 3 ? '大于3' : '小于等于3') : '小于等于0'
console.log(str) //大于3
1
2
let str = 89 > 9 ? ('89' > '9' ? '通过了' : '内层未通过') : '外层未通过'
console.log(str) //内层未通过

判断是否闰年
1、整除4并且不能整除100,
2、整除400

1
2
3
4
5
6
7
8
9
10
11
12
let year = window.prompt('请输入年份')
function isLeapYear(year) {
// if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
// return '是润年'
// } else {
// return '不是闰年'
// }
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
? '闰年'
: '不是闰年'
}
console.log(isLeapYear(year))

对象克隆

拷贝/复制/克隆 clone

普通克隆是将值赋值给i另一个新的对象,两个对象指向的同一个内存地址

1
2
3
4
5
6
7
8
9
10
11
12
let person1 = {
name: '张三',
age: 18,
sex: 'male',
height: 180,
weight: 140,
}
let person2 = person1
person2.name = '李四'
console.log(person1, person2)
// {name: '李四', age: 18, sex: 'male', height: 180, weight: 140}
// {name: '李四', age: 18, sex: 'male', height: 180, weight: 140}

浅拷贝

遍历对象,将每一个属性和值分别赋值给一个空对象。
但是浅拷贝没有处理引用值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let person1 = {
name: '张三',
age: 18,
sex: 'male',
height: 180,
weight: 140,
}
let person2 = {}
for (let key in person1) {
person2[key] = person1[key]
}
person2.name = '李四'
console.log(person1, person2)
// {name: '张三', age: 18, sex: 'male', height: 180, weight: 140}
// {name: '李四', age: 18, sex: 'male', height: 180, weight: 140}
浅拷贝的问题
  • 没有处理引用值,引用值还是指向同一个地址。
  • 如果原型上还有其他属性,浅拷贝也会拷贝下来
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    Object.prototype.num = 1
    let person1 = {
    name: '张三',
    age: 18,
    sex: 'male',
    height: 180,
    weight: 140,
    son: {
    first: 'Jenney',
    },
    }
    let person2 = {}
    //浅拷贝
    for (let key in person1) {
    person2[key] = person1[key]
    }
    person2.name = '李四'
    person2.son.second = 'Lucy'
    console.log(person1, person2)
    // age: 18
    // height: 180
    // name: "张三"
    // sex: "male"
    // son: {first: 'Jenney', second: 'Lucy'}
    // weight: 140
    // [[Prototype]]: Object
    // num: 1

    // age: 18
    // height: 180
    // name: "李四"
    // num: 1
    // sex: "male"
    // son: {first: 'Jenney', second: 'Lucy'}
    // weight: 140
    // [[Prototype]]: Object
    // num: 1
    封装浅拷贝
    传入拷贝源和目标对象,考虑排除原型上的对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    Object.prototype.num = 1
    let person1 = {
    name: '张三',
    age: 18,
    sex: 'male',
    height: 180,
    weight: 140,
    son: {
    first: 'Jenney',
    },
    }
    let person2 = {}
    //浅拷贝
    clone(person1, person2)
    person2.name = '李四'
    person2.son.second = 'Lucy'
    console.log(person1, person2)
    function clone(origin, target) {
    for (let key in origin)
    if (origin.hasOwnProperty(key)) {//排除原型,只打印自身属性
    target[key] = origin[key]
    }
    }
    // age: 18
    // height: 180
    // name: "张三"
    // sex: "male"
    // son: {first: 'Jenney', second: 'Lucy'}
    // weight: 140
    // [[Prototype]]: Object
    // num: 1

    // age: 18
    // height: 180
    // name: "李四"
    // sex: "male"
    // son: {first: 'Jenney', second: 'Lucy'}
    // weight: 140
    // [[Prototype]]: Object
    // num: 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Object.prototype.num = 1
    let person1 = {
    name: '张三',
    age: 18,
    sex: 'male',
    height: 180,
    weight: 140,
    son: {
    first: 'Jenney',
    },
    }
    //浅拷贝
    let person2 = clone(person1)
    person2.name = '李四'
    person2.son.second = 'Lucy'
    console.log(person1, person2)
    function clone(origin, target) {
    let tar = target || {}
    for (let key in origin)
    if (origin.hasOwnProperty(key)) {//排除原型,只打印自身属性
    tar[key] = origin[key]
    }
    return tar
    }

    深拷贝

    方法一(递归)
    利用递归方法进行深拷贝,遇到原始值才退出循环
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    Object.prototype.num = 1
    let person1 = {
    name: '张三',
    age: 18,
    sex: 'male',
    height: 180,
    weight: 140,
    children: {
    first: {
    name: '张一',
    age: 12,
    },
    second: {
    name: '张二',
    age: 10,
    },
    },
    car: ['Benz', 'Mazda'],
    }
    let person2 = deepClone(person1)
    person2.name = '李四'
    person2.children.third = {
    name: '张三',
    age: 9,
    }
    person2.car.push('BYD')
    console.log(person1, person2)
    //深拷贝
    function deepClone(origin, target) {
    let targets = target || {}
    toStr = Object.prototype.toString
    arrType = '[object Array]'

    for (var key in origin) {
    if (origin.hasOwnProperty(key)) {
    if (typeof origin[key] === 'object' && origin[key] !== null) {
    if (toStr.call(origin[key]) === arrType) {
    targets[key] = []
    } else {
    targets[key] = {}
    }
    deepClone(origin[key], targets[key])
    } else {
    targets[key] = origin[key]
    }
    }
    }
    return targets
    }
    // age: 18
    // car: (2) ['Benz', 'Mazda']
    // children:
    // first: {name: '张一', age: 12}
    // second: {name: '张二', age: 10}
    // [[Prototype]]: Object
    // height: 180
    // name: "张三"
    // sex: "male"
    // weight: 140

    // age: 18
    // car: (3) ['Benz', 'Mazda', 'BYD']
    // children:
    // first: {name: '张一', age: 12}
    // second: {name: '张二', age: 10}
    // third: {name: '张三', age: 9}
    // [[Prototype]]: Object
    // height: 180
    // name: "李四"
    // sex: "male"
    // weight: 140
    方法二(JSON 不推荐)
    利用JSON.stringify将数据转换成字符串(原始值),再用JSON.parse解析字符串转换成对象。但是无法拷贝其他引用类型、拷贝函数、循环引用等情况
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    Object.prototype.num = 1
    let person1 = {
    name: '张三',
    age: 18,
    sex: 'male',
    height: 180,
    weight: 140,
    children: {
    first: {
    name: '张一',
    age: 12,
    },
    second: {
    name: '张二',
    age: 10,
    },
    },
    car: ['Benz', 'Mazda'],
    }

    let str = JSON.stringify(person1)
    let person2 = JSON.parse(str)

    person2.name = '李四'
    person2.children.third = {
    name: '张三',
    age: 9,
    }
    person2.car.push('BYD')
    console.log(person1, person2)
1
2
3
4
5
6
7
function test() {
console.log(foo) //undefined
var foo = 2
console.log(foo) //2
console.log(a) //a is not defined
}
test()
1
2
3
4
5
6
7
8
9
10
11
12
function a() {
var test
test()
function test() {
console.log(1)
}
}
a() //1
// AO = {
// test: undefined, //1
// function test() {} //2
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var name = '222'
var a = {
name: '111',
say: function () {
console.log(this.name)
},
}
var fun = a.say //var fun=function() {console.log(this.name)}
fun() ///222 //this指向window
a.say() //111 //对象内部的方法this指向a
var b = {
name: '333',
say: function (fun) {
fun() //+function() {console.log(this.name)}();
},
}
b.say(a.say) //222 //this指向window
b.say = a.say //function() {console.log(this.name)}
b.say() //333
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function test() {
var marty = {
name: 'marty',
printNmae: function () {
console.log(this.name)
},
}
var test1 = {
name: 'test1',
}
var test2 = {
name: 'test2',
}
var test3 = {
name: 'test3',
}
test3.printNmae = marty.printNmae
marty.printNmae.call(test1) //test1
marty.printNmae.apply(test2) //test2
marty.printNmae() //marty
test3.printNmae() //test3
}
test()
1
2
3
4
5
6
7
8
9
10
11
12
var bar = {
a: '1',
}
function test() {
bar.a = 'a'
Object.prototype.b = 'b'
return function inner() {
console.log(bar.a) //a
console.log(bar.b) //b
}
}
test()()

数组

数组基础

数组字面量声明数组

1
let arr1 = []

Array内置构造函数声明数组(不推荐)

1
let arr = new Array() 
1
2
3
4
let arr1 = new Array(2) //如果只填一个纯数字就是数组长度
console.log(arr1.length) //2
let arr2 = new Array('2') //非纯数字就是数组元素
console.log(arr2.length) //1

不使用new声明函数(不使用)

1
let arr = Array()

所有数组都继承于Array.prototype属性。
实际上数组就是对象的另一种形式

1
2
3
4
5
6
7
8
9
10
11
let arr = [1, 2, 3, 4]
// index:0, 1, 2, 3
// index数组元素的下标(索引值)
let obj = { 0: 1, 1: 2, 2: 3, 3: 4 }
let obj1 = {
name: 'a',
}
//obj1.name -> obj1['name']

console.log(arr[2]) //3
console.log(obj[2]) //3

稀松数组

数组不一定每一位都有值,也可以没有值,但是最后一位没有值,默认不计等于没有最后一位

1
2
3
let arr = [, 1, 2, , ,]
console.log(arr) //[empty, 1, 2, empty × 2]
console.log(arr.length) //5

使用构造函数不可以创建稀松数组。因为字面量创建数组是已经成型的数组,构造函数创建数组是传参数

1
let arr=new Array(1,,2)//Unexpected token ','

数组方法

数组的方法是继承数组原型上的方法Array.prototype
修改原数组:push/unshift pop/shift reverse splice sort
新建数组:concat toString slice join/split

push

在数组最后添加元素,返回值是执行了方法以后数组的长度

1
2
3
4
let arr = [1, 2, 3]
let a = arr.push(2, 2)
console.log(a) //5
console.log(arr) //[1, 2, 3, 2, 2]

手写push方法

1
2
3
4
5
6
7
8
9
let arr = [1, 2, 3]
Array.prototype.myPush = function () {
for (let i = 0; i < arguments.length; i++) {
this[this.length] = arguments[i]
}
return this.length
}
arr.myPush(1, 2)
console.log(arr) //[1, 2, 3, 1, 2]

push原理

1
2
3
4
Array.prototype.push = function (element) {
this[this.length] = element
this.length++
}

unshift

在数组最前面添加元素,返回值是执行了方法以后数组的长度

1
2
3
4
let arr = [1, 2, 3]
let a = arr.unshift('2', '4')
console.log(a) //5
console.log(arr) //['2', '4', 1, 2, 3]

手写unshift方法1,使用splice方法

1
2
3
4
5
6
7
8
9
10
11
let arr = ['d', 'e', 'f']
Array.prototype.myUnshift = function () {
let pos = 0
for (let i = 0; i < arguments.length; i++) {
this.splice(pos, 0, arguments[i])
pos++
}
return this.length
}
arr.myUnshift('a', 'b', 'c')
console.log(arr) //['a', 'b', 'c', 'd', 'e', 'f']

手写unshift方法2,将类数组arguments转换成数组,然后使用concat方法

1
2
3
4
5
6
7
let arr = ['d', 'e', 'f']
Array.prototype.myUnshift = function () {
let argArr = Array.prototype.slice.call(arguments)
return argArr.concat(this)
}
let newArr = arr.myUnshift('a')
console.log(newArr) //['a', 'd', 'e', 'f']

pop

删除数组最后一位,返回删除的值。pop没有参数

1
2
3
4
let arr = [1, 2, 3]
let a = arr.pop()
console.log(a) //3
console.log(arr) //[1, 2]

shift

删除现在数组第一位,返回删除的值

1
2
3
4
let arr = ['a', 'b', 'c']
let a = arr.shift()
console.log(a) //a
console.log(arr) //['b', 'c']

reverse

reverse数组倒序,返回值是倒序后的数组

1
2
3
4
let arr = ['a', 'b', 'c']
let a = arr.reverse()
console.log(a) //['c', 'b', 'a']
console.log(arr) //['c', 'b', 'a']

splice

splice(开始项下标,剪切长度,剪切位置开始添加数据)

1
2
3
4
5
6
let arr = ['a', 'b', 'c']
arr.splice(1, 2)
console.log(arr) //['a']
let arr1 = ['a', 'b', 'c']
arr1.splice(1, 1, 1, 2, 3)
console.log(arr1) //['a', 1, 2, 3, 'c']
1
2
3
4
5
6
7
let arr = ['a', 'b', 'd']
arr.splice(2, 0, 'c')
console.log(arr) // ['a', 'b', 'c', 'd']

let arr1 = ['a', 'b', 'd']
arr1.splice(-1, 0, 'c')
console.log(arr1) // ['a', 'b', 'c', 'd']

sort

sort按照ASCII码排序,返回排序以后的数组

1
2
3
4
5
6
7
8
9
let arr = [-1, -5, 0, 8, 2]
arr.sort()
console.log(arr) // [-1, -5, 0, 2, 8]
let arr1 = ['b', 'z', 'h', 'i', 'a']
arr1.sort()
console.log(arr1) //['a', 'b', 'h', 'i', 'z']
let arr2 = [27, 49, 5, 7]
arr2.sort()
console.log(arr2) //[27, 49, 5, 7]
自定义排序
1
2
3
4
sort(function (a, b) {
//(内部用的冒泡排序)
//return 返回值
})

参数 a,b
返回值:1、负值,a就排在前
2、正值,b就排在前
3、0 ,保持不动

升序
1
2
3
4
5
6
7
8
9
10
11
12
let arr = [27, 49, 5, 7]
arr.sort(function (a, b) {
if (a > b) {
return 1
} else {
return -1
}
})
// arr.sort(function (a, b) {
// return a - b
// })
console.log(arr) //[5, 7, 27, 49]
降序
1
2
3
4
5
let arr = [27, 49, 5, 7]
arr.sort(function (a, b) {
return b - a
})
console.log(arr) //[49, 27, 7, 5]
随机排序

Math.random( ) 返回随机 ( 0, 1 ] 之间的数

1
2
3
4
5
6
7
8
9
10
let arr = [1, 2, 3, 4, 5]
arr.sort(function (a, b) {
let rand = Math.random()
if (rand - 0.5 > 0) {
return 1
} else {
return -1
}
})
console.log(arr)
1
2
3
4
5
let arr2 = [1, 2, 3, 4, 5]
arr2.sort(function (a, b) {
return Math.random() - 0.5
})
console.log(arr2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let arr = [
{
son: 'Jenny',
age: 18,
},
{
son: 'Jone',
age: 10,
},
{
son: 'Crytal',
age: 16,
},
{
son: 'Ben',
age: 3,
},
]
arr.sort(function (a, b) {
// if (a.age > b.age) {
// return 1
// } else {
// return -1
// }
return a.age - b.age
})
console.log(arr)
// 0: {son: 'Ben', age: 3}
// 1: {son: 'Jone', age: 10}
// 2: {son: 'Crytal', age: 16}
// 3: {son: 'Jenny', age: 18}
// length: 4
// [[Prototype]]: Array(0)
1
2
3
4
5
6
7
8
9
10
let arr = ['123', '1', '12345', '1234567']
arr.sort(function (a, b) {
// if (a.length > b.length) {
// return 1
// } else {
// return -1
// }
return a.length - b.length
})
console.log(arr) //['1', '123', '12345', '1234567']

数组按照元素的字节数排序
Unicode 0-255 一个字节,256- 两个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getBytes(str) {
let bytes = str.length
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 255) {
bytes++
}
}
return bytes
}
console.log(getBytes('我爱你')) //6

let arr = ['我爱你', 'Ok', 'Hello', '你说什么', '可以']
arr.sort(function (a, b) {
return getBytes(a) - getBytes(b)
})
console.log(arr)
//['Ok', '可以', 'Hello', '我爱你', '你说什么']

concat

将两个数组合并,返回合并后的新数组

1
2
3
4
5
6
let arr1 = ['a', 'b', 'c']
let arr2 = ['d', 'e', 'f']
let arr3 = arr1.concat(arr2)
let arr4 = arr2.concat(arr1)
console.log(arr3) //['a', 'b', 'c', 'd', 'e', 'f']
console.log(arr4) //['d', 'e', 'f', 'a', 'b', 'c']

toString

将数组转换成字符串

1
2
3
4
let arr = ['a', 'b', 'c']
let arr1 = [1, 2, 3]
console.log(arr.toString()) //a,b,c
console.log(arr1.toString()) //1,2,3

slice

array.slice(start, end),截取数组的一部分,返回截取的新数组
参数可选,[ start , end ) 包含start, end不包含

1
2
3
4
5
6
7
8
9
let arr = ['a', 'b', 'c']
let arr1 = arr.slice()
console.log(arr1) //['a', 'b', 'c']
let arr2 = arr.slice(1, 2)
console.log(arr2) //['b']
let arr3 = arr.slice(1)
console.log(arr3) //['b', 'c']
let arr4 = arr.slice(-3, 2)
console.log(arr4) //['a', 'b']

join/split

join( )把数组中的所有元素放入一个字符串,参数作为分隔符
split( )把一个字符串分割成字符串数组,第一个参数要和被分割的分隔符一致,第二个参数可选是分割结束位置

1
2
3
4
5
6
7
8
9
10
11
let arr = ['a', 'b', 'c']
let arr1 = arr.join('-')
console.log(arr1) //a-b-c
let arr2 = arr.join(0)
console.log(arr2) //a0b0c

let arr3 = arr1.split('-', 2)
console.log(arr3) //['a', 'b']

let arr4 = arr2.split('', 4)
console.log(arr4) //['a', '0', 'b', '0']

类数组

类数组一定要有下标和值对应的形式,还有length属性
数组和类数组都有length属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Object.prototype
let obj = {
0: 1,
1: 2,
2: 3,
length: 3,
// push: Array.prototype.push,
// splice: Array.prototype.splice,
}
Object.prototype.push = Array.prototype.push
Object.prototype.splice = Array.prototype.splice
obj.push(4)
console.log(obj)
// Object(4) [1, 2, 3, 4]
// 0: 1
// 1: 2
// 2: 3
// 3: 4
// length: 4
// [[Prototype]]: Object

arguments是类数组,因为它并没有继承Array.prototype,继承的是Object.prototype。所以没有数组的push方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function test() {
console.log(arguments)
}
test(1, 2, 3)
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 0: 1
// 1: 2
// 2: 3
// callee: ƒ test()
// length: 3
// Symbol(Symbol.iterator): ƒ values()
// [[Prototype]]: Object
function test2() {
arguments.push(4) // arguments.push is not a function
console.log(arguments)
}
test2(1, 2, 3)

自己声明的数组,原型上有Array.prototype

1
2
3
4
5
6
7
8
let arr = [1, 2, 3]
console.log(arr)
// [1, 2, 3]
// 0: 1
// 1: 2
// 2: 3
// length: 3
// [[Prototype]]: Array(0)

类数组转化成数组

Array.prototype.slice.call()

1
2
3
4
5
6
function test() {
let arArr = Array.prototype.slice.call(arguments)
arArr.push(1)
console.log(arArr)
}
test(1, 2, 3)//[1, 2, 3, 1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let obj = {
2: 3,
3: 4,
length: 2,
push: Array.prototype.push,
splice: Array.prototype.splice,
}
obj.push(1)
obj.push(2)
console.log(obj)
//push了两次,长度变为4,第三位和第四位被替换掉了
//Object(4) [empty × 2, 1, 2, push: ƒ, splice: ƒ]
// 2: 1
// 3: 2
// length: 4
// push: ƒ push()
// splice: ƒ splice()
// [[Prototype]]: Object
1
2
3
4
function test() {
console.log(typeof arguments) //类数组
}
test() //object

数组去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let arr = [0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 'a', 'a']
Array.prototype.unique = function () {
let temp = {},
newArr = []
for (let i = 0; i < this.length; i++) {
if (!temp.hasOwnProperty(this[i])) {
//解决temp[0]是0的情况
temp[this[i]] = this[i]
newArr.push(this[i])
}
}
return newArr
}
console.log(arr.unique()) //[0, 1, 2, 3, 'a']

字符串去重

1
2
3
4
5
6
7
8
9
10
11
12
13
let str = '11122200aabb'
String.prototype.unique = function () {
let temp = {},
newStr = ''
for (let i = 0; i < this.length; i++) {
if (!temp.hasOwnProperty(this[i])) {
temp[this[i]] = this[i]
newStr += this[i]
}
}
return newStr
}
console.log(str.unique()) //120ab

找到重复的第一个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let str = 'rnvhnvbdashguiqdrpjgdhrghbdrgd'
function test(str) {
let temp = {}
for (let i = 0; i < str.length; i++) {
if (temp.hasOwnProperty(str[i])) {
temp[str[i]]++
} else {
temp[str[i]] = 1
}
}
for (let key in temp) {
if (temp[key] === 1) {
return key
}
}
}
console.log(test(str)) //a

JS错误信息类型

SyntaxError 语法错误

变量名不规范

1
2
let 1=1 //Unexpected number 以外的数字
let 1ab=1 //Invalid or unexpected token 无效或意外的令牌

关键字赋值

1
2
new = 5 // Unexpected token '=' 以外的标记
function = 1 // Unexpected token '=' 以外的标记

基本语法错误

1
let a = 5: //Unexpected token ':'

ReferenceError 引用错误

变量或者函数未声明

1
2
test() //test is not defined
console.log(a) //a is not defined

给无法被赋值的对象赋值

1
let a=1=2 //Invalid left-hand side in assignment 赋值的左侧无效
1
2
let a = 1
console.log(a)=1;//1 Invalid left-hand side in assignment

RangeError 范围错误

数组长度为负数

1
2
3
let arr = [1, 2, 3]
arr.length = -1
console.log(arr) //Invalid array length 数组无效的长度

对象参数超出可行范围

1
2
3
let num = new Number(66.66)
console.log(num.toFixed(-1))
//toFixed() digits argument must be between 0 and 100 at Number.toFixed (<anonymous>)

TypeError 类型错误

调用不存在的方法

1
2
3
4
123() //123 is not a function

let obj = {}
obj.say() //obj.say is not a function

实例化原始值

1
2
let a = new 'string'() //"string" is not a constructor
let a = new 123() //123 is not a constructor

URIError URI错误

1
2
3
console.log(encodeURI('http://你好')) //http://%E4%BD%A0%E5%A5%BD 将中文转译成对应编码
console.log(decodeURI('%E4%BD%A0%E5%A5%BD')) //你好 将中文对应的编码转译成中文
let str = decodeURI('%cvav') //URI malformed at decodeURI (<anonymous>) URI格式不正确

EvalError eval函数执行错误

人为制造错误

1
2
console.log(new Error('代码错误')) //Error: 代码错误
console.log(new SyntaxError('代码错误')) //SyntaxError: 代码错误

手动抛出错误的方法

try catch finally throw

try是可能出错的,finally是一定要执行的,
catch捕获try的错误信息,throw自定义错误信息

1
2
3
4
5
6
7
8
9
10
try {
console.log('正常执行1') //正常执行1
console.log(a)
console.log('正常执行2')
} catch (e) {
// console.log(e) //eferenceError: a is not defined
console.log(e.name + ':' + e.message) //ReferenceError:a is not defined
} finally {
console.log('正常执行3') //正常执行3
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let jsonStr = ''
try {
if (jsonStr === '') {
throw 'JSON字符串为空'
}
console.log('我要执行了') //我要执行了
let json = JSON.parse(jsonStr)
console.log(json)
} catch (e) {
console.log(e) //JSON字符串为空
let errorTip = {
name: '数据传输失败',
errorCode: '1020',
}
console.log(errorTip) //{name: '数据传输失败', errorCode: '1020'}
}

垃圾回收

标记清除、引用计数

  1. 找出不再使用的变量
  2. 释放其占用内存
  3. 固定时间间隔运行

    闭包解除引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function test1() {
    let a = 1
    return function () {
    a++
    console.log(a)
    }
    }
    let test = test1()
    test() //2
    test() //3
    test = null
    test() //test is not a function

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!