Object:对象
对象具有属性和方法
面向对象:三大特点: 封装 继承 多态
封装/创建/定义
1.直接量方式:
var obj={
"属性名":属性,
... ,
"方法名":function(){
操作
},...
}
强调:
1.其实属性名和方法名的""可以不加
2.访问对象的属性和方法
对象名.属性名 === 对象名["属性名"]
对象名.方法名() === 对象名["方法名"]()
js中一切皆为对象,除了undfined和null;一切对象的底层都是hash数组
3.访问到不存在的属性,返回undifined
4.可以随时随地添加新属性和新方法
obj.属性名=属性值
obj.方法名=function(){}
5.必须使用for in循环,必须写为obj[i]才能拿到,不要使用.会得到undifined
6.在对象的方法里,使用对象自己的属性,必须写为this.属性名
this的指向
1.单个元素绑定事件this -> 这个元素
2.多个元素绑定事件this -> 当前触发这个事件的元素
3.定时器中的this -> window
4.箭头函数中的this -> 外部对象
5.函数中的this -> 当前使用函数的这个对象
6.自定义构造函数中的this -> 当前正在创建的对象
2.预定义构造函数方式
var obj=new object();
obj.属性名=属性值
obj.方法名=function(){}
以上两个方法都有一个缺陷:一次只能创建一个对象
3.自定义构造函数
创建自定义构造函数
function 类名(name,age,hobby,...){
this.name=name;
this.age=age;
this.hobby=hobby
}
调用构造函数创建对象
var obj=new (){实参,...}
面对对象的优点:
1.所有属性和方法都保存在一个对象中
2.每个功能分开写,便于以后维护
3.一个方法触发,多个方法联动
继承 :父对象的成员(属性和方法),子对象可以直接使用
为什么要继承:代码重用,提高代码的复用性,节约内存空间,提升网站性能
何时继承:只要多个子对象共用的属性和方法,都要集中定义在父元素中
如何找到原型对象(父对象):保存一类子对象的共有属性和共有方法
对象名.__proto__;
构造函数名.prototype;
有了原型对象,可以设置共有属性和共有方法
1.原型对象.属性名=属性值
2.原型对象.方法名=function(){}
面向对象:
判断自有还是共有
判断自有:obj.hasOwnProperty("属性名") 结果为true,则有自有属性;为false,则可能有,可能没有
判断共有:if(obj.hasOwnProperty("属性名")==false&&"属性名"in obj){
共有
}else{
自有
}
完整公式
if(obj.hasOwnProperty("属性名")){
console.log("自有")
}else{
if("属性名" in obj){
console.log("共有")
}else{
console.log("没有")
}
}
删除和修改: 自有和共有
自有:
修改:obj.属性名=新值
删除: delete obj.属性名
共有:
修改:原型.属性名=新值
删除:delete 原型.属性名
如何为老IE的数组添加indexOf方法
if(Array.prototype.indexOf===undifined){
Array.prototype.indexOf=function(key,starti){
starti===undifined&&(starti=0);
fot(var i=starti;i<this.length;i++){
if(this[i]==key){
return i;
}
}
return -1;
}
}
var arr1=[1,2,3,4,5];
var arr2=[2,4,6,8,10];
console.log(arr1.indexOf(2));
console.log(arr2.indexOf(2));
判断X是不是数组
判断X是不是继承自Array.prototype
Array.prototype.isPrototypeOf(X) 结果为true,则是;为false,则不是
判断X是不是由Array这个构造函数创建的
x instanceof Array 结果为true,则是;为false,则不是
Array.isArray(x)
对输出[对象的字符串]形式
在Object的原型上保存着最原始的toString()
对最原始的toString()输出的形式:[object 构造函数名]
多态
子对象觉得父对象的成员不好用,就在本地定义一个同名函数,覆盖父对象的成员。不严格的定义:同一个函数,不同的人使用,表现出来的效果不一样,有多种形态
借用的固定套路:Object.prototype.toString.call/apply(x)===[object.Array]
实现自定义继承
两个对象之间设置继承
子对象.__proto__=父对象
批量设置继承
构造函数名.prototype=父对象
注意:先继承,再创建对象
class 关键字:简化面向对象(封装,继承,多态)
class 类名extends 老类{
constructor(name,speed,rl){
super(name,speed);
this。rl=rl;
}
}
Function:作用域&闭包
作用域:
1.全局:随处可见,可以反复使用,缺点:容易被污染
2.函数:只能在函数调用时内部可用,不会被污染,缺点:一次性的,用完就会被释放
函数的执行原理
1、程序加载时:
创建执行环境栈(ECS):保存函数调用顺序的数组
首先压入全局执行环境(全局EC)
全局EC引用着全局对象window
window中保存着我们的全局变量
2、定义函数时:
创建函数对象:封装代码段
在函数对象中有一个scope(作用域)的属性:记录着函数来自的作用域是哪里
全局函数的scope都是window
3、调用前:
在执行环境栈(ECS)中压入新的EC(函数的EC)
创建出活动对象(AO):保存着本此函数调用时用到的局部变量
在函数的EC中有一个scope chain(作用域链)属性引用着AO
AO还有parent属性是函数的scope引用着的对象
4、调用时:
正是因为有了前3步,才带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
5、调用完:
函数的EC会出栈,没人引用着AO,AO自动释放,局部变量也就释放了
闭包:希望保护一个可以反复使用的局部变量的一种词法结构,其实还是一个函数,只是写法比较特殊
何时使用:希望保护一个可以反复使用的局部变量
如何使用:
1.两个函数进行嵌套
2.外层函数创建出受保护的变量
3.外层函数return出内层函数
4.内层函数操作受保护的变量
强调:
1.判断是不是闭包,有没有两个函数进行嵌套,返回内层函数,内层函数再保护受保护的变量
2.外层函数调用几次,就创建了几个闭包,受保护的变量就有几个副本
3.同一次外层函数调用,返回的内层函数,都是在操作同一个受保护的变量
缺点:受保护的变量,永远不会被释放,食用过多,会导致内存泄漏 - 闪退,不可多用
使用场景:防抖节流
5个事件需要防抖节流 - 共同:触发很快,但是不需要很快的修改DOM树
1.elem.onmousemove
2.input.oninput
3.elem.onclick
4.window.onscroll
5.windou.onresize
固定公式:
function fdjl(){
var timer=null;
return function(){
if(timer!=null){
clearTimeout(timer)
}
timer=setTimeout(()=>{
操作
},1000)
}
}
总结:
两链一包:
原型链:每个对象都有一个属性.__proto__,可以一层一层的找到每个人的父亲,形成的一条链式结构
作用:查找共有属性和共有方法,哪怕自己没有也会悄悄向上找,如果顶层都没有就会报错
作用域链:以函数EC的scope chain属性为起点,经过AO逐级引用,形成的一条链式结构
作用:查找变量,带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
作用:专门用于完成防抖节流
保护对象:保护对象的属性方法
对象的每个属性都具有四大特性
{
value:1000;
wirtable:true/false;
enumerable:true/false;
configurable:true/false;
}
修改四大特性:
Object.definedProperty(对象名."属性名",{
writable: true/false,
enumerable: true/false,
configurable: true/false
})
调用一次方法只能保护一个属性的四大特性
Object.defineProperties(对象名,{
"属性名":{
四大特性
},
...
})
至少方法只调用了一次,但是四大特性写着始终麻烦,甚至不能防止添加
三个级别:
防扩展:防添加
Object.preventExtensions(x)
密封:防添加和防删除
Object.seal(x)
冻结:防添加和防删除和防修改
Object.freeze(x)
四大特性其实该叫六大特性 - 可以做出动态数据
Object.definedProperty(对象名,"属性名",{
get:()=>{
console.log("获取数据会被拦截")
}
set:()=>{
console.log("设置数据会被拦截")
}
})
对象的深拷贝和浅拷贝
1.浅拷贝:利用按值传递的
var obj1={"name":"obj1"};
var obj2=obj1;
obj2.name="obj2";
console.log(obj1)
console.log(obj2)
2.深拷贝:两者互不影响
var obj1={"name":"obj1"};
var obj2={...obj1};
obj2.name="obj2";
console.log(ob1)
console.log(ob2)
后端不能直接传数据,必须穿上衣服才能出门,变成一个JSON组字符串
后端穿衣服:var jsonTxt=JSON.stringify(jsonObj)
前端穿衣服:var jsonObj=JSON.parse(jsonTxt)或eval"("+json+js0nTxt+")")
此方法能也就能实现深拷贝
Error对象
目的:
1.快速找到错误
2.防止用户乱输入
浏览器自带的4种错误类型
语法错误:SyntaxError - 一定是符号/语法写错了
引用错误:RefenerError - 没有创建就去使用
类型错误:typeError - 不是你的方法,却使用了
范围错误:RangeError - 只有num.toFxied,d的范围0~100之间
错误处理
语法:
try{
只放入可能出错的代码
}catch(err){
发生错误后才会执行的代码
}
try...catch...的性能非常差,几乎是所有代码里最差的,可用if...else...代替
抛出自定义错误:
throw new Error("自定义错误消息")
严格模式:
开启:"use strict"
作用:
1.禁止全局污染
2.将静默失败升级为报错
柯里化函数
function add(a){
return function(b){
return function(c){
console.log(a+b+c)
}
}
}
add(3)(5)(7)
匿名函数:没有名字的函数
1.自调:只能执行一次,好处:函数中的【没有用的变量】是会自动释放的,它可以用于代替你的全局代码写法,两者很相似,都只会执行一次,但是自调会释放掉没用的东西,绑定好的事件是不会被自动释放掉的
(function(){
操作;
})()
2.回调:匿名函数不是自调,就是回调 - 往往我们不需要关注回调函数前辈们是如何创建的,我们更关心如何使用
elem.on事件名=function(){}
arr.sort(function(){})
var obj={
"方法名":function(){}
}
var 方法名=function(){}
一切的回调函数都可以简化为箭头函数
设计模式:不仅仅局限于前端,它是一种编程思想,越来越复杂,有21种设计模式
1.单例模式:也称之为单体模式,保证一个类仅有一个实例对象创建,并且提供一个访问它的全局访问点
最简单的单例模式:利用ES6let不允许重复声明的特性,刚好就复合了单例的特点
let obj={
"name":"袍哥1",
"getName":function(){return this.name+"在上课"}
}
不太推挤这种写法:
1、污染命名空间(容易变量名冲突,会报错);
2、维护时不易管控(搞不好就直接覆盖了);
推荐写法:
var h52303=(function(){
let state=null;
return function(name,age){
this.name=name;
this.age=age;
if(state){
return state;
}
return state=this;
}
})()
h52303.prototype.sayHello=function(){
return this.name+":hello";
}
var wh=new h52303("吴昊",18);
var dwj1=new h52303("短文将1",19);
console.log(wh);
console.log(wh.sayHello());
console.log(dwj1);
console.log(dwj1.sayHello());
2.观察者模式:也有人称呼叫作 订阅 - 发布 模式
现实生活中:QQ空间、朋友圈、微博...
let obj={};
function on(id,fn){
if(!obj[id]){
obj[id]=[];
}
obj[id].push(fn);
}
on("袍哥",(msg)=>{console.log("小吴来了",msg)})
on("袍哥",(msg)=>{console.log("小段来了",msg)})
on("袍哥",(msg)=>{console.log("小肖来了",msg)})
on("袍哥",(msg)=>{console.log("小王来了",msg)})
on("袍哥",(msg)=>{console.log("小李来了",msg)})
on("袍哥",(msg)=>{console.log("小张来了",msg)})
function emit(id,msg){
obj[id].forEach(fn=>fn(msg));
}
btn.onclick=()=>{
emit("袍哥","一支穿云箭,千军万马来相见")
}
事件轮询:JS其实是单线程应用,代码必须是从上向下,一步一步执行的,如果某一块代码非常耗时,可能会导致整个页面都卡住了,尤其是如果你把JS放在head里面,用户可能只能看到一个白板
宏任务:不会再卡住我们的单线程应用,可以让后续代码先走,我们慢慢跟着来,但是问题在于,多个宏任务同时存在,到底谁先执行,谁后执行,分不清楚
1、定时器:setInterval 和 setTimeout
2、AJAX - 他是前端和后端连接的关键点,他也是一个异步宏任务
微任务:ES6提供了Promise对象 - 可以控制异步代码,依然是异步代码,但是可以控制执行的顺序了
可以把Promise对象理解为是现实生活中的保安、保镖...
function ajax1(resolve){
setTimeout(()=>{
console.log("<h1>页面的头部</h1>");
resolve();
},Math.random()*5000);
}
function ajax2(){
return new Promise(function(resolve){
setTimeout(()=>{
console.log("<h1>页面的身体</h1>");
resolve();
},Math.random()*5000);
})
}
function ajax3(){
return new Promise(function(resolve){
setTimeout(()=>{
console.log("<h1>页面的脚部</h1>");
resolve();
},Math.random()*5000);
})
}
new Promise(ajax1).then(ajax2).then(ajax3);
console.log("后续代码跑的依然更快")