本文最后更新于:1 小时前
JavaScript基础 五大浏览器内核
五大主浏览器
内核
IE
Trident
Chrome
Webkit Blink
Safari
Webkit
Firefox
Gecko
Opera
Presto
单线程 JS引擎是单线程,但是他会模拟多线程
轮转时间片 短时间之内轮流执行多个任务的片段
任务一 任务二
切分任务一 任务二
随机排列这些任务片段,组成队列
按照这个队列顺序将任务片段送进JS进程
JS线程执行一个又一个的任务片段
命名规范 不能以数字开头,可以字母_$开头,非开头部分可以写字母_$数字 不能使用关键字、保留字,注意要语义化、结构化 推荐小驼峰或者驼峰命名
JS值 原始值(基本类型数据)、引用值(引用类型数据)
原始值 Number、String、Boolean、Undefined、null、Symbol、BigInt
引用值 Array、Object、Function
运算 普通运算 加法 字符串拼接 任何数据类型的值 + 字符串都是字符串
c = 'str' + 1 + 1 c = 'str' + NaN c = 'str' + true
运算符优先级 括号运算 > 普通运算 >赋值
c = 1 + 1 + 'str' + (1 + 1 )
除法 NaN 非数,数字类型
c = 2 / 1 c = 0 / 0 c = 'a' / 'b' c = NaN / NaN c = NaN / 1 c = 1 / NaN
**Infinity **无穷,数字类型
取余(模) c = 6 % 4 c = 7 % 3 c = 0 % 3
交换值 ab值交换
let c, a = 1 , b = 2 c = a a = b b = clet a = 1 b = 2 a = a + b b = a - b a = a - b console .log (a, b)
++,– a++先打印后运算
let a = 1 console .log (a++) console .log (a)
++a先运算后打印
let a = 1 console .log (a) console .log (++a)
let a = 5 , b b = a++ + 1 console .log (a, b)
let a = 5 , b b = a-- + --a console .log (a, b)
let a = 5 , b b = --a + --a console .log (a, b)
let a = 5 , b b = --a + a++ console .log (a, b)
比较运算 < > = <= == === != !==
纯数字比较就是 number - number
数字和字符串比较 string先转化成number 在进行比较
字符串和字符串比较 字符串转换对应ASCII字码表对应的数值,多个字符的,从左到右一次对比,直到比较出大小为止
相等 相等是不看数据类型,全等要看数据类型是否相等
let bool = 1 == '1' let bool = 1 === '1'
不相等 NaN与任何东西(包括自己)都不相等
逻辑运算 与&& 或|| 非!
undefined,null,NaN,"",0,false除以上以外全部都是真 “ “ 有空格不是空字符串,是真
&& 遇到真就往后走,遇到假或者到最后就返回当前的值
let a = 1 && 2 let b = 1 && 2 && undefined && 10
|| 遇到假就往后走,遇到真或者走到最后就返回当前的值
let c = 0 || null || 1 || 0
循环 for循环 for (let i=0 ;i<10 ;i++){ console .log (i) }
步骤
拆解步骤 let i=0 ;for (;i < 10 ;){ console .log (i); i++; }
for循环转换为while循环 let i = 0 while (i < 10 ) { console .log (i) i++ }
打印0-100的数,( ) 只能有一个,不能写比较,{ } 不能出现 i++ i–
let i = 101 for (; i--; ) { console .log (i) }
10的n次方
let n = 5 , num = 1 for (let i = 0 ; i < n; i++) { num *= 10 }console .log (num)
n的阶乘
let n = 5 , num = 1 for (let i = 1 ; i <= n; i++) { num *= i }console .log (num)
789打印出987
let num = 789 let a = num % 10 let b = ((num - a) / 10 ) % 10 let c = (num - a - b * 10 ) / 100 console .log ('' + a + b + c)
打印三个数中最大的数
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和自己整除的数)
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_运算符,用来检测一个变量的类型。
console .log (typeof undefined ) console .log (typeof null ) console .log (typeof true ) console .log (typeof 1 ) console .log (typeof 123456789n ) console .log (typeof '123' ) console .log (typeof Symbol ('Sym' )) console .log (typeof function ( ) {}) console .log (typeof []) console .log (typeof {}) console .log (typeof NaN )
Object可以理解为引用类型, object/array都是Object引用类型
console .log (typeof (1 - '1' )) console .log (typeof ('1' - 1 )) console .log (typeof ('1' - '1' ))
console .log (typeof a) console .log (typeof typeof a) console .log (typeof typeof 123 )
封装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
parseInt
let a = '10' console .log (parseInt (a, 16 )) console .log (parseInt ('abc123' )) console .log (parseInt ('123abc1' ))
parseFloat let num = parseFloat ('2.345' )let num2 = parseFloat ('2' )console .log (num.toFixed (2 )) console .log (num2.toFixed (2 ))
toString null和undefined没有toString方法
let str = '123' console .log (str.toString ())
Boolean undefined、null、NaN、” “、0是false,其他都是true
隐式类型转换 a未定义,但是typeof a 是字符串 ‘’undefined’’
console .log (a) console .log (typeof a)
let a = '1' a++ console .log (a)
console .log (-true ) - 1 console .log (typeof -true ) console .log (+undefined ) console .log (typeof +undefined ) console .log (!!' ' ) console .log (!!'' ) console .log (typeof ' ' ) console .log (typeof '' )
字符串和数字 +时 ,会隐式类型转换成字符串 进行拼接,-、 、/、%和>、< 比较时 *,会隐式类型转换成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) console .log (typeof ('2' + 1 )) console .log ('2' - 1 ) console .log (typeof ('2' - 1 )) console .log (typeof (1 - '2' )) let b = 'b' + 1 console .log (b) console .log (typeof b) let b2 = 'b' - 1 console .log (b2) console .log (typeof b2) let b3 = 'b' * 1 console .log (b3) console .log (typeof b3)
字符串和字符串 比较时,会转成ASCII码在进行比较
let a = 'a' > 'b' console .log (a) console .log (typeof a)
全等于不进行数据类型转换
let a = 1 === '1' let b = 1 == '1' console .log (b) console .log (a)
let a1 = 2 > 1 > 3 let a2 = 2 > 1 == 1 console .log (a1, a2)
let a1 = undefined < 0 let a2 = undefined > 0 let a3 = undefined == 0 console .log (a1, a2, a3) let b1 = null < 0 let b2 = null > 0 let b3 = null == 0 console .log (b1, b2, b3) let c1 = undefined == null let c2 = NaN == 0 let c3 = NaN == NaN console .log (c1, c2, c3)
布尔值和数字 let a = false + 1 console .log (a) let b = true + 1 console .log (b)
let b = false == 1 console .log (b) let a = false !== 1 console .log (a)
isNaN 判断数据类型是否为非数,先将数据进行隐式转换成number在进行NaN判断
console .log (isNaN (NaN )) console .log (isNaN ('123' )) console .log (isNaN ('a' )) console .log (isNaN (null )) console .log (isNaN (undefined ))
函数 函数命名规则 不能数字开头,中间可以数字、字母、_ 、$,推荐小驼峰命名法
函数声明 function test ( ) { let a = 1 , b = 2 console .log (a, b) } let test1 = function ( ) { let a = 1 , b = 2 console .log (a, b) }
函数字面量(函数表达式)是忽略函数名
let test = function a ( ) { return 'a' }console .log (typeof a)
形参/实参 function test (a, b ) { console .log (test) console .log (arguments ) }test (1 , 2 , 3 )
function sum ( ) { let a = 0 for (let i = 0 ; i < arguments .length ; i++) { a += arguments [i] } console .log (a) }sum (1 , 2 , 3 )
在实参传了值的情况下,函数内部可以改变实参的值
function test (a, b ) { a = 3 console .log (arguments [0 ]) console .log (a, b) }test (1 , 2 )
在实参没有传值的情况下,函数内部给形参赋值是没有用的
function test2 (a, b ) { b = 3 console .log (arguments [1 ]) }test2 (1 )
形参和arguments对象不是同一个东西,但是有映射关系。
function test (a, b ) { a = 3 console .log (arguments [0 ]) }test (1 , 2 )
初始化参数 参数不传默认值是 undefined,形参和arguments谁不是undefined就默认选谁 传一个参数给第二位形参,第一位形参设置默认值,
function test (a = 1 , b ) { console .log (a) console .log (b) } 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 function test2 (a, b ) { a = arguments [0 ] || 1 b = arguments [1 ] || 2 console .log (a, b) } test2 (undefined , 6 ) function test2 (a, b ) { if (typeof arguments [0 ] !== 'undefined' ) { a = arguments [0 ] } else { a = 1 } console .log (a, b) } test2 (undefined , 3 ) function test3 (a, b ) { a = typeof arguments [0 ] !== 'undefined' ? arguments [0 ] : 1 console .log (a, b) } test3 (undefined , 2 )
默认参数作用域与暂时性死区 因为参数是按顺序初始化的,所以后定义默认值的参数可以引用先定义的参数
function makeKing (name = 'Henry' , numerals = name ) { return `King ${name} ${numerals} ` }console .log (makeKing ())
参数初始化顺序遵循“暂时性死区”规则,即前面定义的参数不能引用后面定义的
function makeKing (name = numerals, numerals = 'VIII' ) { return `King ${name} ${numerals} ` }console .log (makeKing ())
参数也存在于自己的作用域中,它们不能引用函数体的作用域:
function makeKing (name = 'Henry' , numerals = defaultNumeral ) { let defaultNumeral = 'VIII' return `King ${name} ${numerals} ` }console .log (makeKing ())
n的阶层,使用递归,不能用for循环
function fact (n ) { if (n <= 1 ) return 1 return n * fact (n - 1 ) }
arguments 对象其实还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。 阶乘函数一般定义成递归调用的,就像上面这个例子一样。只要给函数一个名称,而且这个名称不会变,这样定义就没有问题。但是,这个函数要正确执行就必须保证函数名是 fact,从而导致了紧密耦合。arguments.callee 就可以让函数逻辑与函数名解耦
function fact (num ) { if (num <= 1 ) return 1 return num * arguments .callee (num - 1 ) }
这个重写之后的 fact()函数已经用 arguments.callee 代替了之前硬编码的 fact。 这意味着无论函数叫什么名称,都可以引用正确的函数。 斐波拉去数列
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.解释一行,执行一行
变量提升 函数声明先提升,其次变量函数声明是整体提升
test ()function test ( ) { console .log (1 ) }
var变量只有声明提升,赋值不提示
let变量赋值都不提升
console .log (a) function a (a ) { var a = 1 var a = function ( ) {} } var a = 2
暗示全局变量 全局作用域下,未声明或者var声明的变量都会挂载到window下
var a = 1 b = 2 console .log (window .a )
函数内未声明的变量直接挂载到window下
function test ( ) { var a = b = 1 }test ()console .log (window .a ) console .log (window .b ) console .log (a)
AO activation object 活跃对象,函数上下文 先提升变量声明(不含赋值),然后实参给形参赋值,再提升函数声明,,最后执行函数。按照此顺序执行代码
寻找形参和变量声明
实参赋值给形参
找函数声明并赋值函数体
执行
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 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 )
GO global object 全局上下文 GO相当于是window 先变量声明提升,再函数提升,最后执行
找变量声明
找函数声明并赋值函数体
执行
var a = 1 function a ( ) { console .log (2 ) }console .log (a)
console .log (a, b) function a ( ) {}var b = function ( ) {}
function test ( ) { var a = (b = 1 ) console .log (b) }test ()
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 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 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) if (a) { var b = 2 } c = 3 console .log (c) }var atest () a = 1 console .log (a)
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 ())
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 ())
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) if (a) { var b = 3 } var c a = 4 var a console .log (b) f = 5 console .log (c) console .log (a) }var atest (1 )console .log (a) console .log (f)
作用域/作用域链 函数也是一种对象类型,是引用类型,引用值,有对象.name,.length,.protoytpe等属性 对象中有些属性是我们无法访问的,JS引擎内部固定有的隐式属性 [[scope]]
函数创建时,生成的一个JS内部隐式属性 函数存储作用域的容器,作用域链 AO,函数的执行期上下文, GO,全局的执行期上下文 函数执行完成之后,AO是要销毁的,AO是一个即时的存储容器
在函数被定义 的时候就已经形成了作用域,作用域链和GO在函数执行 的那一刻才生成自己的AO
外部函数为什么不能访问到内部函数的变量?
function a ( ) { function b ( ) { let b = 2 } let a = 1 b () console .log (b) }a ()
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 ()
闭包 当内部函数被返回到外部并保持时,一定会产生闭包,闭包会产生原来的作用域链不释放, 过度的闭包可能会导致内存泄漏或加载过慢
function test1 ( ) { function test2 ( ) { var b = 2 console .log (a) } var a = 1 return test2 }var c = 3 var test3 = test1 () test3 ()
什么是闭包 函数嵌套函数,内部函数就是闭包。就是能够访问其他函数内部变量的函数 。 闭包可以做数据缓存
普通闭包 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 ]()
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 () compute.add () compute.minus ()
构造函数闭包 构造函数被实例化时,内部产生一个this,最后隐式返回this
function Compute ( ) { let num = 10 this .add = function ( ) { num++ console .log (num) } this .minus = function ( ) { num-- console .log (num) } }let compute = new Compute () compute.add () compute.add () compute.minus ()
闭包中的this对象 在闭包中使用 this 会让代码变复杂。 如果内部函数没有使用箭头函数定义,则 this 对象会在运行时绑定到执行函数的上下文。 如果在全局函数中调用,则 this 在非严格模式下等于 window,在严格模式下等于 undefined。 如果作为某个对象的方法调用,则 this 等于这个对象。 匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。 不过,由于闭包的写法所致,这个事实有时候没有那么容易看出来。
window .identity = 'The Window' let object = { identity : 'My Object' , getIdentityFunc ( ) { return function ( ) { return this .identity } }, }console .log (object.getIdentityFunc ()())
为什么匿名函数没有使用其包含作用域(getIdentityFunc())的 this 对象呢? 每个函数在被调用时都会自动创建两个特殊变量:this 和 arguments。内部函数永远不可能直接访问外部函数的这两个变量。但是,如果把 this 保存到闭包可以访问的另一个变量中, 则是行得通的。
window .identity = 'The Window' let object = { identity : 'My Object' , getIdentityFunc ( ) { let that = this return function ( ) { return that.identity } }, }console .log (object.getIdentityFunc ()())
在定义匿名函数之前,先把外部函数的 this 保存到变量 that 中。然后在定义闭包时,就可以让它访问 that,因为这是包含函数中名称没有任何冲突的一个变量。即使在外部函数返回之后,that 仍然指向 object,所以调用 object.getIdentityFunc()() 就会返回”My Object”
立即执行函数 自动执行,执行完成之后立即释放 立即执行函数 IIFE - immediately-invoked function()括号包起来的都叫表达式,一定是表达式才能被执行符号执行
(function ( ) { })() ((function ( ) { })())
立即执行函数一定是表达式,非表达式不能执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (function test1 ( ) { console .log (1 ) })() function test ( ) { console .log (1 ); }() function test ( ) { console .log (1 ) }(2 )
立即执行函数执行完后立即销毁
普通函数执行完后不释放
let test = function ( ) { console .log (1 ) } console .log (test) let test1 = (function ( ) { console .log (2 ) })() console .log (test1)
立即执行函数可带参数
(function (a, b ) { console .log (a + b) })(2 , 4 )
立即执行函数的返回值要赋值给变量才能访问
let num = (function (a, b ) { return a + b })(2 , 4 )console .log (num)
函数声明变成表达式的其他方法 + - ! || && ,函数变成表达式之后,函数名自动忽略
1 && (function ( ) { console .log (1 ) })()undefined || (function ( ) { console .log (1 ) })() -(function ( ) { console .log (1 ) })() !(function ( ) { console .log (1 ) })()
var a = 10 if (function b ( ) {}) { a += typeof b }console .log (a)
将以下代码修改,打印出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 + ' ' ) } } return arr }var myArr = test ()console .log (myArr)for (var j = 0 ; j < 10 ; j++) { myArr[j]() }
修改后
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) } 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]() } function test2 ( ) { for (var i = 0 ; i < 10 ; i++) { (function ( ) { document .write (i + ' ' ) })() } } test2 ()
修改以下代码,使之点击对应的li,打印对应li下标
<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 ( ) { console .log (i) } } </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) } </script >
逗号运算 逗号运算默认只输出最后一个
let num = (1 + 1 , 2 + 2 , 1 - 1 )console .log (num)
var fn = (function test1 ( ) { return 1 },function test2 ( ) { return '2' })()console .log (typeof fn)
对象 创建对象 对象字面量 在对象内部,都是键值对来存储数据
let obj = { name : '张三' , sex : '男' , } obj.name = '李四' console .log (obj)
构造函数 对象和构造函数不是同一个东西,对象是通过实例化构造函数(new Object())而创造的对象实例
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 ( ) { this .name = '123' }let test = new Test ()
工厂模式 这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)
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指向实例化的那个对象
function Teacher ( ) { this .name = '张三' this .sex = '男' this .smoke = function ( ) { console .log ('I am smoking' ) } } let teacher1 = new Teacher () let teacher2 = new Teacher () teacher1.name = '李四' console .log (teacher1, teacher2)
也可以传参,解决创建多个类似对象的问题
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 () person2.sayName ()
也可以传对象,进行配置,方便以后维护和使用。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)
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 .color = color this .brand = brand }let car1 = new Car ('red' , 'Benz' ) console .log (car1.color )
不用new和this构造函数 function Car (color, brand ) { let me = {} me.color = color me.brand = brand return me }let car = Car ('red' , 'Mazda' )console .log (car.color ) console .log (car.brand )
显示返回一个值 返回原始值没有用
function Car ( ) { this .color = 'red' this .brand = 'Mazda' return 'abc' }let car = new Car ()console .log (car)
返回引用值就会覆盖
function Car ( ) { this .color = 'red' this .brand = 'Mazda' return function 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 () compute.times ()
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 ) compute.times (3 , 5 , 7 )
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' )) 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' ))
包装类 原始值没有自己的方法和属性,但是可以进行包装变成对象 string是原始值,没有length属性 包装系统内置三种构造函数new Number、new String、new Boolean 包装成对应对象之后可以为其添加属性,也能进行计算
let a = new Number (1 )console .log (a) a.A = '1' console .log (a) let a1 = a + 1 console .log (a1) console .log (a)
let test = new Number (undefined )console .log (test) let test1 = new Number (null )console .log (test1) let test2 = new String (undefined )console .log (test2) let test3 = new String (null )console .log (test3)
undefined、null是不可以设置任何属性和方法
console .log (null .length ) console .log (undefined .length )
包装类过程 如果不是对象
先判断定义类型是什么
然后系统自动转化成对应对象
最后无法保存对象,就只能删掉那个属性let a = 123 a.len = 3 console .log (a.len )
string是原始值,没有length属性。 str能够使用length是因为内部已经进行了包装类new String(str).lengthlet str = 'abc' console .log (new String (str).length )
数组有length属性,可以进行截断let arr = [1 , 2 , 3 , 4 , 5 ] arr.length = 3 console .log (arr) let arr1 = [1 , 2 , 3 , 4 , 5 ] arr1.length = 6 console .log (arr1)
string不可以被截断, 因为开始判断是原始值,然后进行包装类,后来因为不能保存,就删掉,结果就是打印无变化let str = 'abc' str.length = 1 console .log (str)
修改以下代码,打印出stringlet name = 'abc' name += 10 let type = typeof name if (type.length === 6 ) { type.text = 'string' }console .log (type.text )
提前包装类,转换成String对象,之后就可以添加属性let name = 'abc' name += 10 let type = new String (typeof name) if (type.length === 6 ) { type.text = 'string' } console .log (type.text )
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 }let test1 = new Test () test1.g () test1.g () let test2 = new Test () test2.g ()
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 2 3 4 51 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 foo3 (x ) { console .log (arguments ) return x })(1 , 2 , 3 , 4 , 5 )
function b (x, y, a ) { a = 10 console .log (arguments [2 ]) }b (1 , 2 , 3 )function c (x, y, a ) { arguments [2 ] = 10 console .log (a) }c (1 , 2 , 3 )
原型prototype
原型对象prototype其实是构造函数(function对象)的一个属性,但是他也是一个对象 只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。
function Test ( ) {}console .log (Test .prototype )
prototype是定义构造函数构造出的每个对象的公共祖先,所有被该构造函数构造出来的对象都可以继承原型上的属性和方法 对象自己身上有的就不会访问原型上的
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 ) console .log (hp2.ram ) console .log (hp1.screen ) console .log (hp2.screen )
推荐固定的值或者方法写在原型上,需要变化或者传参配置的值写在构造函数内部,这样可以减少代码冗余
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' , ram : '6G' , screen : '16:9' , call : function ( ) { console .log ('I am calling' ) }, } let hp1 = new Handphone ('red' , '小米' ) let hp2 = new Handphone ('black' , '红米' ) hp2.call ()
通过实例化对象不能增删改祖先prototype上的值
function Test ( ) {}Test .prototype .name = 'TEST' let test = new Test ()console .log (Test .prototype ) test.job = 123 console .log (Test .prototype , test) test.name = testconsole .log (Test .prototype , test)delete test.name console .log (Test .prototype , test)
构造器constructor constructor默认指向构造函数本身 在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自 Object。 构造函数有一个 prototype 属性引用其原型对象,而这个原型对象也有一个constructor 属性,引用这个构造函数。换句话说,两者循环引用
function Handphone (color, brand ) { this .color = color this .brand = brand }console .log (Handphone .prototype ) console .log (Handphone .prototype .constructor === Handphone )
可以通过原型内部的constructor更改构造函数的constructor
function Telephone ( ) {}function Handphone (color, brand ) { this .color = color this .brand = brand }Handphone .prototype = { constructor : Telephone , }console .log (Handphone .prototype )
proto ( [[Prototype]] )proto__只是一个容器,实例对象通过__proto__这个键名指向原型对象prototype 构造函数的prototype === 实例的__proto
function Car ( ) {}let car = new Car ()console .log (Car .prototype === car.__proto__ )
__proto__是实例化以后的结果,__proto__属于实例化对象 构造函数被实例化时会产生一个this,this默认有一个__proto__属性,指向他的原型。如果this中没有找到对应的属性,就会沿着__proto__找原型里的属性
function Car ( ) { }Car .prototype .name = 'Benz' let car = new Car () console .log (car.name )
__proto__只是一个内部属性,也可以改
function Person ( ) {}Person .prototype .name = '张三' let p1 = { name : '李四' , }let person = new Person ()console .log (person.name ) person.__proto__ = p1console .log (person.name )
实例化前后赋值prototype 实例化之前赋值prototype可以修改
Car .prototype .name = 'Mazda' Car .prototype .name = 'Benz' function Car ( ) {}let car = new Car ()console .log (car.name )
实例化之后赋值prototype可以修改
Car .prototype .name = 'Mazda' function Car ( ) {}let car = new Car ()Car .prototype .name = 'Benz' console .log (car.name )
实例化后重写prototype 实例化之前重写可以修改prototype属性
Car .prototype .name = 'Benz' function Car ( ) {}Car .prototype = { name : 'Mazkda' }let car = new Car ()console .log (car.name )
实例化之后重写的prototype属性不能影响实例化对象
Car .prototype .name = 'Benz' function Car ( ) {}let car = new Car ()Car .prototype = { name : 'Mazkda' , }console .log (car.name )
实例化对象的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 ()Car .prototype = { name : 'Mazkda' , }console .log (car)console .log (car.name ) console .log (car.constructor .prototype )
function test ( ) { let a = 1 function plus1 ( ) { a++ console .log (a) } return plus1 }let plus = test ()plus () plus () plus ()
function test ( ) { let a = 1 function plus1 ( ) { a++ console .log (a) } window .plus = plus1 }test ()plus () plus () plus ()
let plus = (function ( ) { let a = 1 function plus1 ( ) { a++ console .log (a) } return plus1 })()plus () plus () plus ()
(function ( ) { let a = 1 function plus1 ( ) { a++ console .log (a) } window .plus = plus1 })()plus () plus () plus ()
原型链 沿着__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 = professorfunction Teacher ( ) { this .mSkill = 'JS' }let teacher = new Teacher ()Student .prototype = teacherfunction Student ( ) { this .pSkill = 'HTML' }let student = new Student ()console .log (student)
原型链的顶端是Object.prototype Object.prototype里边保存了toString方法 Object.prototype.__proto__是null
console .log (Object .prototype .__proto__ === null )
如果是引用值,子级可以更改/添加父级或者祖先的属性(不推荐)
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 = professorfunction Teacher ( ) { this .mSkill = 'JS' this .success = { alibaba : '10' , } }let teacher = new Teacher ()Student .prototype = teacherfunction Student ( ) { this .pSkill = 'THML' }let student = new Student () student.success .baidu = '1' student.success .alibaba = '1' console .log (teacher, student)
如果是原始值,子级不能修改/增加父级或者祖先的属性
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 = professorfunction Teacher ( ) { this .mSkill = 'JS' this .students = 500 }let teacher = new Teacher ()Student .prototype = teacherfunction Student ( ) { this .pSkill = 'THML' }let student = new Student () student.students ++console .log (teacher, student)
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 () car.intro () Car .prototype .intro ()
不是所有的对象都继承于Object.prototype。Object.create(null)创建的实例对象不继承Object.prototype。
let obj = Object .create (null ) obj.num = 1 console .log (obj) let obj1 = { count : 2 , } obj.__proto__ = obj1 console .log (obj.count )
原始值是没有属性,所以undefined和null没有toString方法 undefined和null不能经过包装类,还没有原型
let num = 1 console .log (num.toString ())
let num = 1 let obj = {}let obj2 = Object .create (null )document .write (num) document .write (obj) console .log (obj2)
Object .prototype .toString .call (1 ) Object .prototype .toString .call ('a' ) Object .prototype .toString .call (true ) Object .prototype .toString .call ([1 , 2 , 3 ]) Object .prototype .toString .call ({ name : 1 }) Number .prototype .toString .call (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 ) }Foo .getName () getName () Foo ().getName () getName () new Foo .getName () new Foo ().getName () new new Foo ().getName ()
继承 最下边的对象会继承原型链上所有的属性,都可以通过__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) console .log (s)
封装圣杯模式 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传数组
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) Car .apply (newCar, ['Benz2' , 'red2' ])console .log (newCar) let car = new Car ('Ben1' , 'red1' )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 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 ) compute.minus (1 , 1 ) compute.mul (1 , 1 ) compute.div (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 ()
链式调用 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 ()
对象属性与遍历 动态属性 设置动态属性,属性名用中括号表示 JS引擎最早也是使用obj['name']处理
let myLang = { No1 : 'HTML' , No2 : 'CSS' , No3 : 'JavaScript' , myStudyingLang : function (num ) { console .log (this ['No' + num]) }, } myLang.myStudyingLang (1 ) myLang.myStudyingLang (2 ) obj = { name : '123' , }console .log (obj['name' ])
遍历 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 (key + ':' + car[key]) }let arr = [1 , 2 , 3 , 4 , 5 ]for (i in arr) { console .log (arr[i]) }
判断属性是否在对象里
let car = { brand : 'Benz' , color : 'red' , }console .log ('displacement' in car)
判断属性是否在构造函数里( in 不排除原型)
function Car ( ) { this .brand = 'Benz' this .color = 'red' }Car .prototype = { displacement : '3.0' , }let car = new Car ()console .log ('displacement' in car)
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]) }
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]) } }
instanceof 可通过 instanceof 操作符来判断对象的具体类型 检测某个对象是不是另一个对象的实例(判断A对象的原型里有没有B的原型)
function Car ( ) {}let car = new Car ()function Person ( ) {}let p = new Person ()console .log (car instanceof Car ) console .log (p instanceof Car ) console .log (p instanceof Object ) console .log ([] instanceof Array ) console .log ([] instanceof Object ) console .log ({} instanceof Object )
判断一个数据是否为数组 let a = []console .log (a.constructor ) console .log (a instanceof Array ) console .log (Object .prototype .toString .call (a))
Object.prototype.toString.call() toString原本是Object.prototype里边的一种方法,但是使用call()把this指向换了
let a = []Object .prototype = { toString : function ( ) { a.toString () }, }
为什么会打印出[object Array]
let arr = new Array (1 , 2 , 3 )console .log (arr) console .log (arr.toString ()) console .log (Object .prototype .toString ()) console .log (Object .prototype .toString .call (arr))
判断是否数组(推荐使用Object.prototype.toString.call())
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就返回谁
function test (a, b, c ) { console .log (arguments .callee ) console .log (test.length ) console .log (arguments .callee .length ) console .log (arguments .length ) }test (1 , 2 )
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 ()
在立即执行函数中使用
let sum = (function (n ) { if (n <= 1 ) return 1 return n + arguments .callee (n - 1 ) })(100 ) console .log (sum)
caller caller返回调用当前函数的函数引用
test1 ()function test1 ( ) { test2 () }function test2 ( ) { console .log (test2.caller ) }
typeof typeof可能返回的值有哪些? object(null)、boolean、number、string、undefined、function、symbol、bigint
console .log (typeof function ( ) {}) console .log (typeof null ) console .log (typeof {}) console .log (typeof 1 ) console .log (typeof 'abc' ) console .log (typeof true ) console .log (typeof undefined ) console .log (typeof Symbol ()) console .log (typeof 2n )
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 let a = 1 function c ( ) {} }test (123 )console .log (d) console .log (this .d ) console .log (window .d )
构造函数内部的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 ( ) { this .name = '123' }let test = new Test ()
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)
function foo ( ) { bar.apply (null , arguments ) }function bar ( ) { console .log (arguments ) }foo (1 , 2 , 3 , 4 , 5 )
let f = (function f ( ) { return '1' }, function g ( ) { return 2 }) console .log (f) console .log (typeof f)
let f = (function f ( ) { return '1' },function g ( ) { return 2 })()console .log (f) console .log (typeof f)
console .log (undefined == null ) console .log (undefined === null ) console .log (isNaN ('100' )) console .log (parseInt ('1a' ) == 1 )
function isNaN1 (num ) { let res = Number (num) if (res == NaN ) return true else return false }console .log (isNaN1 ('abc' )) function isNaN2 (num ) { let res = Number (num) + '' if (res == 'NaN' ) return true else return false }console .log (isNaN2 ('abc' ))
console .log ({} == {}) let obj1 = {} obj2 = obj1console .log (obj1 == obj2)
var a = '1' function test ( ) { var a = '2' this .a = '3' console .log (a) }test () new test () console .log (a)
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 () new test ()
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 }let test1 = new Test () test1.f () test1.f () let test2 = new Test () test2.f ()
三目运算符 普通三目运算 let a = 5 a > 0 ? console .log ('大于0' ) : console .log ('小于等于0' )
三目运算自带return功能 let a = 5 let str = a > 0 ? '大于0' : '小于等于0' console .log (str)
三目运算嵌套 let a = 5 , str = '' str = a > 0 ? (a > 3 ? '大于3' : '小于等于3' ) : '小于等于0' console .log (str)
let str = 89 > 9 ? ('89' > '9' ? '通过了' : '内层未通过' ) : '外层未通过' console .log (str)
判断是否闰年 1、整除4并且不能整除100, 2、整除400
let year = window .prompt ('请输入年份' )function isLeapYear (year ) { return (year % 4 === 0 && year % 100 !== 0 ) || year % 400 === 0 ? '闰年' : '不是闰年' }console .log (isLeapYear (year))
对象克隆 拷贝/复制/克隆 clone 普通克隆是将值赋值给i另一个新的对象,两个对象指向的同一个内存地址
let person1 = { name : '张三' , age : 18 , sex : 'male' , height : 180 , weight : 140 , }let person2 = person1 person2.name = '李四' console .log (person1, person2)
浅拷贝 遍历对象,将每一个属性和值分别赋值给一个空对象。 但是浅拷贝没有处理引用值
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)
浅拷贝的问题
没有处理引用值,引用值还是指向同一个地址。
如果原型上还有其他属性,浅拷贝也会拷贝下来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)
封装浅拷贝 传入拷贝源和目标对象,考虑排除原型上的对象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] } }
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 }
方法二(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)
function test ( ) { console .log (foo) var foo = 2 console .log (foo) console .log (a) }test ()
function a ( ) { var test test () function test ( ) { console .log (1 ) } }a ()
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 fun () a.say () var b = { name : '333' , say : function (fun ) { fun () }, } b.say (a.say ) b.say = a.say b.say ()
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) marty.printNmae .apply (test2) marty.printNmae () test3.printNmae () }test ()
var bar = { a : '1' , }function test ( ) { bar.a = 'a' Object .prototype .b = 'b' return function inner ( ) { console .log (bar.a ) console .log (bar.b ) } }test ()()
数组 数组基础 数组字面量声明数组
Array内置构造函数声明数组(不推荐)
let arr1 = new Array (2 ) console .log (arr1.length ) let arr2 = new Array ('2' ) console .log (arr2.length )
不使用new声明函数(不使用)
所有数组都继承于Array.prototype属性。 实际上数组就是对象的另一种形式
let arr = [1 , 2 , 3 , 4 ]let obj = { 0 : 1 , 1 : 2 , 2 : 3 , 3 : 4 }let obj1 = { name : 'a' , }console .log (arr[2 ]) console .log (obj[2 ])
稀松数组 数组不一定每一位都有值,也可以没有值,但是最后一位没有值,默认不计等于没有最后一位
let arr = [, 1 , 2 , , ,]console .log (arr) console .log (arr.length )
使用构造函数不可以创建稀松数组。因为字面量创建数组是已经成型的数组,构造函数创建数组是传参数
数组方法 数组的方法是继承数组原型上的方法Array.prototype 修改原数组:push/unshift pop/shift reverse splice sort 新建数组:concat toString slice join/split
push 在数组最后添加元素,返回值是执行了方法以后数组的长度
let arr = [1 , 2 , 3 ]let a = arr.push (2 , 2 )console .log (a) console .log (arr)
手写push方法
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)
push原理
Array .prototype .push = function (element ) { this [this .length ] = element this .length ++ }
unshift 在数组最前面添加元素,返回值是执行了方法以后数组的长度
let arr = [1 , 2 , 3 ]let a = arr.unshift ('2' , '4' )console .log (a) console .log (arr)
手写unshift方法1,使用splice方法
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)
手写unshift方法2,将类数组arguments转换成数组,然后使用concat方法
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)
pop 删除数组最后一位,返回删除的值。pop没有参数
let arr = [1 , 2 , 3 ]let a = arr.pop ()console .log (a) console .log (arr)
shift 删除现在数组第一位,返回删除的值
let arr = ['a' , 'b' , 'c' ]let a = arr.shift ()console .log (a) console .log (arr)
reverse reverse数组倒序,返回值是倒序后的数组
let arr = ['a' , 'b' , 'c' ]let a = arr.reverse ()console .log (a) console .log (arr)
splice splice(开始项下标,剪切长度,剪切位置开始添加数据)
let arr = ['a' , 'b' , 'c' ] arr.splice (1 , 2 )console .log (arr) let arr1 = ['a' , 'b' , 'c' ] arr1.splice (1 , 1 , 1 , 2 , 3 )console .log (arr1)
let arr = ['a' , 'b' , 'd' ] arr.splice (2 , 0 , 'c' )console .log (arr) let arr1 = ['a' , 'b' , 'd' ] arr1.splice (-1 , 0 , 'c' )console .log (arr1)
sort sort按照ASCII码排序,返回排序以后的数组
let arr = [-1 , -5 , 0 , 8 , 2 ] arr.sort ()console .log (arr) let arr1 = ['b' , 'z' , 'h' , 'i' , 'a' ] arr1.sort ()console .log (arr1) let arr2 = [27 , 49 , 5 , 7 ] arr2.sort ()console .log (arr2)
自定义排序 sort (function (a, b ) { })
参数 a,b 返回值:1、负值,a就排在前 2、正值,b就排在前 3、0 ,保持不动
升序 let arr = [27 , 49 , 5 , 7 ] arr.sort (function (a, b ) { if (a > b) { return 1 } else { return -1 } })console .log (arr)
降序 let arr = [27 , 49 , 5 , 7 ] arr.sort (function (a, b ) { return b - a })console .log (arr)
随机排序 Math.random( ) 返回随机 ( 0, 1 ] 之间的数
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)
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 ) { return a.age - b.age })console .log (arr)
let arr = ['123' , '1' , '12345' , '1234567' ] arr.sort (function (a, b ) { return a.length - b.length })console .log (arr)
数组按照元素的字节数排序 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 ('我爱你' )) let arr = ['我爱你' , 'Ok' , 'Hello' , '你说什么' , '可以' ] arr.sort (function (a, b ) { return getBytes (a) - getBytes (b) })console .log (arr)
concat 将两个数组合并,返回合并后的新数组
let arr1 = ['a' , 'b' , 'c' ]let arr2 = ['d' , 'e' , 'f' ]let arr3 = arr1.concat (arr2)let arr4 = arr2.concat (arr1)console .log (arr3) console .log (arr4)
toString 将数组转换成字符串
let arr = ['a' , 'b' , 'c' ]let arr1 = [1 , 2 , 3 ]console .log (arr.toString ()) console .log (arr1.toString ())
slice array.slice(start, end),截取数组的一部分,返回截取的新数组 参数可选,[ start , end ) 包含start, end不包含
let arr = ['a' , 'b' , 'c' ]let arr1 = arr.slice ()console .log (arr1) let arr2 = arr.slice (1 , 2 )console .log (arr2) let arr3 = arr.slice (1 )console .log (arr3) let arr4 = arr.slice (-3 , 2 )console .log (arr4)
join/split join( )把数组中的所有元素放入一个字符串,参数作为分隔符 split( )把一个字符串分割成字符串数组,第一个参数要和被分割的分隔符一致,第二个参数可选是分割结束位置
let arr = ['a' , 'b' , 'c' ]let arr1 = arr.join ('-' )console .log (arr1) let arr2 = arr.join (0 )console .log (arr2) let arr3 = arr1.split ('-' , 2 )console .log (arr3) let arr4 = arr2.split ('' , 4 )console .log (arr4)
类数组 类数组一定要有下标和值对应的形式,还有length属性 数组和类数组都有length属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let obj = { 0 : 1 , 1 : 2 , 2 : 3 , length : 3 , }Object .prototype .push = Array .prototype .push Object .prototype .splice = Array .prototype .splice obj.push (4 )console .log (obj)
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 )function test2 ( ) { arguments .push (4 ) console .log (arguments ) }test2 (1 , 2 , 3 )
自己声明的数组,原型上有Array.prototype
let arr = [1 , 2 , 3 ]console .log (arr)
类数组转化成数组 Array.prototype.slice.call()
function test ( ) { let arArr = Array .prototype .slice .call (arguments ) arArr.push (1 ) console .log (arArr) }test (1 , 2 , 3 )
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)
function test ( ) { console .log (typeof arguments ) }test ()
数组去重 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[this [i]] = this [i] newArr.push (this [i]) } } return newArr }console .log (arr.unique ())
字符串去重 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 ())
找到重复的第一个数
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))
JS错误信息类型 SyntaxError 语法错误 变量名不规范
关键字赋值
基本语法错误
ReferenceError 引用错误 变量或者函数未声明
给无法被赋值的对象赋值
let a = 1 console .log (a)=1 ;
RangeError 范围错误 数组长度为负数 let arr = [1 , 2 , 3 ] arr.length = -1 console .log (arr)
对象参数超出可行范围 let num = new Number (66.66 )console .log (num.toFixed (-1 ))
TypeError 类型错误 调用不存在的方法 123 () let obj = {} obj.say ()
实例化原始值 let a = new 'string' () let a = new 123 ()
URIError URI错误 console .log (encodeURI ('http://你好' )) console .log (decodeURI ('%E4%BD%A0%E5%A5%BD' )) let str = decodeURI ('%cvav' )
EvalError eval函数执行错误 人为制造错误 console .log (new Error ('代码错误' )) console .log (new SyntaxError ('代码错误' ))
手动抛出错误的方法 try catch finally throw try是可能出错的,finally是一定要执行的, catch捕获try的错误信息,throw自定义错误信息
try { console .log ('正常执行1' ) console .log (a) console .log ('正常执行2' ) } catch (e) { console .log (e.name + ':' + e.message ) } finally { console .log ('正常执行3' ) }
let jsonStr = '' try { if (jsonStr === '' ) { throw 'JSON字符串为空' } console .log ('我要执行了' ) let json = JSON .parse (jsonStr) console .log (json) } catch (e) { console .log (e) let errorTip = { name : '数据传输失败' , errorCode : '1020' , } console .log (errorTip) }
垃圾回收 标记清除、引用计数
找出不再使用的变量
释放其占用内存
固定时间间隔运行闭包解除引用 function test1 ( ) { let a = 1 return function ( ) { a++ console .log (a) } }let test = test1 ()test () test () test = null test ()