JavaScript(二)对象分类 对象分类 写个正方形 浪费内存的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 let squareList = []let widthList = [5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 ]for (let i = 0 ; i < 12 ; i++) { squareList[i] = { width : widthList[i], getArea ( ) { return this .width * this .width }, getLength ( ) { return this .width * 4 } } }
浪费内存 优化后的代码和内存图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let squareList=[]let widthList = [5 ,6 ,5 ,6 ,5 ,6 ,5 ,6 ,5 ,6 ,5 ,6 ]let squarePrototype ={ getArea ( ){ return this .width *this .width }, getLength ( ){ return this ,width*4 } }for (let i =0 ;i<12 ;i++){ squareList[i] = Object .create (squarePrototype) squareList[i].width = widthList[i] }
抽离到函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let squareList = []let widthList = [5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 ]function createSquare (width ) { let obj = Object .create (squarePrototype) obj['width' ] = width return obj }let squarePrototype = { getArea ( ) { return this .width * this .width }, getLength ( ) { return this .width * 4 } }for (let i = 0 ; i < 12 ; i++) { squareList[i] = createSquare (widthList[i]) }
函数与原型的结合(几近完美) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let squareList = []let widthList = [5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 , 5 , 6 ]function createSquare (width ) { let obj = Object .create (createSquare.squarePrototype ) obj['width' ] = width return obj } createSquare.squarePrototype = { getArea ( ) { return this .width * this .width }, getLength ( ) { return this .width * 4 }, constructor : createSquare }for (let i = 0 ; i < 12 ; i++) { squareList[i] = createSquare (widthList[i]) console .log (squareList[i].constructor ) }
new操作符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let squareList = []let widthList = [5 , 6 , 5 , 6 , 5 , 6 ]function Square (width ) { this ['width' ] = width }Square .prototype .getArea = function ( ) { return this .width * this .width }Square .prototype .getLength = function ( ) { return this .width * 4 }for (let i = 0 ; i < 6 ; i++) { squareList[i] = new Square (widthList[i]) console .log (squareList[i].constructor ) }
写个圆 1 2 3 4 5 6 7 8 9 10 11 function Circle (radius ) { this ['radius' ] = radius }Circle .prototype .getArea = function ( ) { return this .radius * 2 * Math .PI }Circle .prototype .getLength = function ( ) { return Math .pow (this .radius , 2 ) * Math .PI }let c1 = new Circle (5 )
写个矩形 1 2 3 4 5 6 7 8 9 10 11 12 function Rect (width, height ) { this .width = width this .height = height }Rect .prototype .getArea = function ( ) { return this .width * this .height }Rect .prototype .getLength = function ( ) { return (this .width + this .height ) * 2 }
总结 new X() 自动做了四件事 自动创建空对象 自动为空对象关联原型,原型地址指定为 X.prototype 自动将空对象作为 this 关键字运行构造函数 自动return this JS之父的爱
构造函数X X 函数本身负责给对象本身添加属性 X.prototype 对象负责保存对象的共用属性
代码规范 大小写 所有构造函数(专门用于创建对象的函数)首字母大写 所有没构造出来的对象,首字母小写
词性 new 后面的函数,使用名词形式 如 new Person()、new Object()其他函数,一般用动词开头 如 createSquare(5)、createElement(‘div)原型公式 对象.proto ===其构造函数.prototype x.原型 等价于 x.proto
需要分类 理由 理由一 有很多对象拥有一样的属性和行为 需要把他们分为同一类 如 square1 和 square2 这样创建类似对象的时候就很方便理由二 但是还有很多对象拥有其他的属性和行为 所以就需要不同的分类 比如 Square / Circle / Rect 就是不同的分类 Array / Function 也是不同的分类 而 Object 创建出来的对象,是最没有特点的对象
类型 V.S. 类 类型 类型是 JS 数据的分类,有7种 四基两空一对象
类 类是针对于对象的分类,有无数种 常见的有 Array、Function、Date、RegExp等
数组对象 定义一个数组 1 2 3 let arr = [1 ,2 ,3 ]let arr = new Array (1 ,2 ,3 )let arr = new Array (3 )
数组对象自身属性 ‘0’ / ‘1’ / ‘2’ / ‘length’ 属性没有数字,只有字符串
数组对象的共用属性 ‘push’/‘pop’/‘shift’/‘unshift’/‘join’/‘concat’
函数对象 定义函数 1 2 3 4 function fn (x,y ){return x+y}let fn2 = function fn (x,y ){return x+y}let fn = (x,y ) =>x+ylet fn = new Function ('x' ,'y' ,'return x+y' )
函数对象自身属性 ‘name’/‘length’
函数对象共用属性 ‘call’/‘apply’/‘bind’
JS 终极一问 x.原型 等价于 x.proto window 是谁构造的 Window 可以通过constructor 属性查看构造者window.Object 是谁构造的 window.Function 因为所有的函数都是 window.Function 构造的window.Function 是谁构造的 window.Function 因为所有函数都是 window.Function 构造的 并不是自己构造自己,这是开发者的安排 浏览器构造了 Function,然后指定它的构造者是自己
用类构造函数 用类创造一个对象 constructor内部是对象 外部是函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { constructor (name, age ) { this .name = name this .age = age } hello ( ) { console .log ('你好,我叫' + this .name ) } }let person = new Person ('luca' , 18 ) person.name === 'luca' person.age === 18 person.hello ()
JS数组对象 JS的数组不是典型数组 典型的数组 元素的数据类型相同 使用连续的内存存储 通过数字下标获取元素但 JS 的数组不这样 元素的数据类型可以不同 内存不一定是连续的(对线是随机存储的) 不能通过数字下表,而是通过字符串下标 这意味着数组可以有任何key 比如
1 2 let arr =[1 ,2 ,3 ] arr['xxx' ]=1
创建一个数组 新建 1 2 3 let arr =[1 ,2 ,3 ]let arr = new Array (1 ,2 ,3 )let arr = new Array (3 )
转化 1 2 3 4 let arr ='1,2,3' .split (',' )let arr = '123' .split ('' )Array .from ('123' )Array .from ({0 :'a' ,1 :'b' ,2 :'c' ,3 :'d' ,length :2 }) 只要有0123 下标跟length
伪数组 1 2 3 let divList = document .querySelectorall ('div' )let divArray = Array .from (divList)console .dir (divArray)
Array.from()把普通对象变成数组 伪数组的原型链中并没有数组的原型
创建一个连续数组(连接) 合并两个数组,得到新数组
1 2 let arr3=arr1.concat (arr2) 只提供浅拷贝
截取一个数组的一部分
1 2 3 4 5 arr5 =[3 , 3 , 3 , 4 , 4 , 4 , 4 ] arr5.slice (3 ) [4 , 4 , 4 , 4 ] arr5.slice (0 ) JS 只提供浅拷贝
增删改查 删 跟对象一样 数组的长度并没有变(稀疏数组)
1 2 3 let arr =['a' ,'b' ,'c' ]delete arr['0' ]
不要随便改length 会删元素
1 2 3 let arr =[1 ,2 ,3 ,4 ,5 ,6 ] arr.length = 3
正确删元素 删除头部元素
删除尾部元素
删除中间的元素
1 2 3 arr.splice (index,1 ) arr.splice (index,1 ,'x' ) arr.splice (index,1 ,'x' ,'y' )
查看所有元素 查看所有属性名
1 2 3 let arr = [1 ,2 ,3 ,4 ,5 ];arr.x ='xxx' Object .keys (arr)for (let key in arr){console .log (`${key} :${arr[key]} ` )}
查看数字(字符串)属性名和值 要自己让i 从0增长到 length-1
1 2 3 for (let i =0 ;i<arr.length ;i++){ console .log (`${i} :${arr[i]} ` ) }
也可以用forEach / map 等原型上的函数
1 2 3 4 5 6 7 8 9 10 11 arr.forEach (function (item,index,array ){ console .log (`${index} :${item} ${array} ` ) })function forEach (array,fn ){ for (let i = 0 ;i<array.length ; i++){ fn (array[i],i,array) } } 对每一项调用 fn (array[i],i,array) 传入array不用管
两者区别是 for循环可以使用break 和continue for 是关键字不是函数查看单个属性 跟对象一样
索引越界 报错:Cannot read property ‘toString’ of undefined ** x.toString() 其中x如果是undefined 就报这个错**
1 2 3 arr[arr.length ] === undefined arr[-1 ] === undefined
查找某个元素是否在数组里
使用条件查找元素
1 2 3 4 5 arr.find (function (x ){ return x%2 ===0 }) arr.find (x => item%2 ===0 )
使用条件查找元素索引
1 2 3 4 5 6 7 arr.findIndex (function (x ){ return x%2 ===0 }) arr.findIndex ( x => x%2 ===0 )
增加数组中的元素 在尾部添加元素
1 2 arr.push (new ) arr.push (new1,new2)
在头部添加元素
1 2 arr.unshift (new ) arr.unshift (new1,new2,new3)
在中间添加元素
1 2 arr.splice (index,0 ,'x' ) arr.splice (index,0 ,'x' ,'y' ,'z' )
修改数组中元素的顺序 反转顺序
1 2 let s= 'abcde' s=s.split ('' ).reverse ().join ('' )
自定义顺序
数组变换 map n变n
1 arr.map (item => item*item)
filter n变少
1 2 arr.filter (item => item%2 ==0 ? true :false ) arr.filter (item => item%2 ==0 )
reduce n变1 得到一个结果
1 2 3 4 5 6 7 8 9 10 arr.reduce ((sum,item )=> { return sum+item },0 ) arr.reduce ((result,item )=> { return result.concat (item*item) },[])let arr = [1 , 2 , 3 , 4 , 5 , 6 ] arr.reduce ((result, item ) => { return result.concat (item % 2 === 0 ? item : []) }, [])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let arr = [ { 名称: '动物' , id : 1 , parent : null }, { 名称: '狗' , id : 2 , parent : 1 }, { 名称: '猫' , id : 3 , parent : 1 } ] arr.reduce ((result, item ) => { if (item.parent === null ) { result.id = item.id result.名称 = item.名称 } else { delete item.parent result.children .push (item) item.children = null } return result }, { id : null , 名称: null , children : [] })
JS函数对象 定义一个函数 具体名称函数
1 2 3 4 function 函数名(形式参数1 ,形式参数2 ){ 语句 return 返回值 }
匿名函数 具名函数去掉函数名就是匿名函数 =右边的叫函数表达式
1 let a = function (x,y ){return x+y}
如果是在=右边声明的函数,能作用的范围也只有等号右边
箭头函数 1 2 3 4 let f1 = x => x*xlet f2 = (x,y ) => x+y let f3 = (x,y ) => {return x+y} let f4 = (x,y ) => ({name :x,age :y})
函数调用 1 2 3 let fn = ( ) => console .log ('hi' )fn () 有圆括号才能调用
再进一步
1 2 3 let fn = ( ) => console .log ('hi' )let fn2 = fnfn2 ()
fn 保存了匿名函数的地址 这个地址被复制给了 fn2 fn2() 调用了匿名函数 fn 和 fn2 都是匿名函数的引用而已 真正的函数既不是 fn 也不是 fn2
函数的要素 调用时机 1 2 3 4 5 6 let i = 0 for (i = 0 ; i<6 ; i++){ setTimeout (()=> { console .log (i) },0 ) }
结果打出6个6
1 2 3 4 5 for (let i = 0 ; i<6 ; i++){ setTimeout (()=> { console .log (i) },0 ) }
结果:0、1、2、3、4、5
作用域 1 2 3 4 5 function fn ( ){ let a = 1 }fn ()console .log (a)
全局变量 。局部变量 在顶级作用域声明的变量是全局变量 window 的属性是全局变量 其他都是局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 let b =2 function f1 ( ) { window .a = 1 let c = 3 }f1 ()function f2 ( ) { console .log (a) console .log (b) console .log (c) }f2 ()
1 2 3 4 5 6 7 8 9 10 11 function f1 ( ) { let a = 1 function f2 ( ) { let a = 2 console .log (a) } console .log (a) a = 3 f2 () }f1 ()
作用域规则 如果多个作用域有同名变量 a 那么查找 a 的声明时,就向上取最近的作用域 简称就近原则 查找 a 的过程与函数执行无关 但 a 的值与函数执行有关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function f1 ( ) { let a = 1 function f2 ( ) { let a = 2 function f3 ( ) { console .log (a) } a = 22 f3 () } console .log (a) a = 100 f2 () }f1 ()
闭包 如果一个函数用到了外部的变量 那么这个函数加这个变量 就叫做闭包 f2()里面的 a 和 f3() 组成了闭包缺点: 在IE中使用闭包有 bug,IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量,最终造成内存泄露
形式参数 形式参数的意思就是非实际参数 x 和 y 就是形参,因为并不是实际的参数 调用 add 时,1 和 2 是实际参数,会被赋值给 x y 如果add()其中放着对象,则会复制对象内存地址给x y
1 2 3 4 5 6 7 8 9 10 function add (x, y ) { return x + y } 等价于function add ( ){ var x = arguments [0 ] var y = arguments [1 ] return x+y }
形式参数可多可少
1 2 3 4 function add (x ) { return x + arguments [1 ] }add (1 , 2 )
返回值 每个参数都有返回值
1 2 3 4 function hi ( ) { console .log ('hi' ) }hi ()
没写return,所以返回值是undefined
1 2 3 4 function hi ( ) { return console .log ('hi' ) }hi ()
返回值为 console.log(‘hi’)的值,undefined函数执行完了后才会返回 只有函数有返回值 1+2 的值为3,返回值为undefined
调用栈 压栈与弹栈 JS 引擎在调用(进入)一个函数前 需要把函数所在的环境 push 到一个数组里 这个数组级啊欧总调用栈 等函数执行完了,就会把环境弹 ( pop ) 出来 然后 return 到之前的环境,继续执行后续代码
递归函数 阶乘
1 2 3 function f (n ){ return n!==1 ? n*f (n-1 ):1 }
理解递归 先递进,在回归
1 2 3 4 5 6 7 8 f (4 ) =4 * f (3 ) =4 * (3 * f (2 )) =4 * (3 * (2 * f (1 ))) =4 * (3 * (2 * (1 ))) =4 * (3 * (2 )) =4 * (6 )24
爆栈 如果调用栈中压入的帧过多,程序就会崩溃
函数提升 什么是函数提升
不管把具名函数声明在哪里,他都会跑到第一行什么不是函数提升
这是赋值,右边的匿名函数声明不会提前
arguments 和 this 1 2 3 4 function fn ( ){ console .log (arguments ) console .log (this ) }
如何传 arguments 调用fn 即可传 arguments fn(1,2,3) 那么 arguments 就是 [1,2,3] ,伪数组,没有数组的共有属性如何传 this 目前可以用 fn.call(xxx,1,2,3) 传 this 和 arguments 而且 xxx 会被自动转化成对象(JS的糟粕),后面的则会被转换成伪数组 函数中如果不给条件,this 默认指向window 如果函数里不加 ‘use strict’ this 会尽量把你传的东西变成对象JS 在每个函数里加了 this 用this 获取那个未知的对象
1 2 3 4 5 6 7 8 9 10 11 let person = { name :'frank' , sayHi ( ){ console .log ('你好,我叫' + this .name ) } } person.sayHi () person.sayHi (person) 然后 person 被传给 this 了(person 是个地址) 这样每个函数都能用 this 获取一个未知对象的引用了
person.sayHi() 会隐藏式的把 person 作为 this 传给 sayHi 方便sayHi 获取 person 对应的对象
总结 想让函数获取对象的引用 但是并不想通过变量名做到 Python 通过额外的 self 参数做到 JS 通过额外的 this 做到: person.sayHi() 会把 person 自动传给 sayHi,sayHi 可以通过 this 引用 person其他 注意 person.sayHi 和 person.say Hi() 的区别 注意 person.sayHi() 的断句 (person,sayHi)()两种调用 小白调用法 会自动把 person 传到函数里,作为this
大师调用法 需要自己手动把 person 传到函数里,作为this
1 person.sayHi .call (person)
从这开始用第二种调用法
1 2 3 4 function add (x, y ) { return x + y } add.call (undefined , 1 , 2 )
上面没有用到 this 为什么要多些一个 undefined 因为第一个参数要作为 this 但是代码里没有 this 所以只能用 undefined 占位 null 也可以
this 的两种传递方法 隐藏式传递
1 2 fn (1 ,2 ) 等价于 fn.call (undefined ,1 ,2 ) obj.child .fn (1 ) 等价于 obj.child .fn .call (obj.child ,1 )
显示传递 apply的用法就是给形式参数加上[]
1 2 fn.call (undefined ,1 ,2 ) fn.apply (undefined ,[1 ,2 ])
绑定 this 使用 .bind 可以让 this 不被改变
1 2 3 4 5 6 function f1 (p1,p2 ){ console .log (this ,p1,p2) }let f2 = f1.bind ({name :'lucas' })f2 () 等价于 f1.call ({name :'lucas' })
.bind 还可以绑定其他参数
1 2 let f3 = f1.bind ({name :'lucas' },'hi' )f3 () 等价于 f1.call ({name :'lucas' },hi)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let lucasPhone = { name : "lucas" , phoneBattery : 40 , charge (num ) { console .log (this ) this .phoneBattery = num console .log (this ) } }let bobPhone = { name : "bob" , phoneBattery : 20 }let bobPhoneBatteryCharge = lucasPhone.charge .bind (bobPhone, 100 ) bobPhoneBatteryCharge ()
箭头函数 里面的 this 就是外面的 this
1 2 3 console .log (this ) let fn = ( ) => console .log (this )fn ()
就算加了 call 也一样,无法用call传递 this
1 2 3 4 5 6 7 8 9 10 fn.call ({name :'lucas' }) let person = { "name" : "lucas" , sayHi : () => { console .log ("你好" + this .name ) console .log (this ) } } person.sayHi .call (person)
立即执行函数 原理 ES 5时代,为了得到局部变量, 必须引入一个函数 但是这个函数如果有名字,就没用了 于是这个函数必须是匿名函数 声明匿名函数,然后立即加个() 执行它 但是JS 标准认为这种语法不合法 所以JS 程序员寻求各种办法 最终发现,只要在匿名函数前面加个运算符即可 !、~、()、+。- 都可以 但是这里面有些运算符会往上走 所以推荐用!来解决