Object
JS是基于原型的一个面向对象语言。
面向对象 – 三大特点:封装、继承、多态
封装/创建/定义:封装的自定义对象:3种
1.*直接量方式:
var obj = {
“属性名”:属性值,
…
“方法名”:function(){操作},
…
}
强调:
1.其实属性名和方法名的“”可以不加 – 暂时建议加上,但Json数据格式的键必须加上。
2.访问对象的属性和方法
a)对象名.属性名; === 对象名[“属性名”];
b)对象名.方法名(); === 对象名[“方法名”]();
c)JS中一切都是对象,除了undefined和null,一切对象的底层都是hash数组。
d)建议使用 **.** 去访问对象的属性和方法,更简单。
3.访问到不存在的属性,返回的是一个undefined
4.可以随时随地的添加新属性和新方法
Obj.属性名=属性名;
Obj.方法名=function(){};
5.如果希望遍历出所有东西,必须使用for in循环,必须写为obj[i]才能拿到,不要使用 **.** 会得到undefined。
6.***如果希望在对象的方法里,使用自己的属性,必须写为this.属性名!
**this的指向**
① 单个元素绑定事件this -> 这个元素
② 多个元素绑定事件this -> 当前元素
③ 定时器中this -> window
④ 箭头函数 this -> 外部对象
⑤ 函数中的this -> 谁在调用此方法,this就是谁
⑥ 自定义构造函数的this -> 当前正在创建的对象。
2.预定义构造函数方式
var obj=new Object();
Obj.属性名=属性值;
Obj.方法名=function(){}
以上两个方法都有一个缺陷:一次只能创建一个对象,适合创建单个对象的时候使用(第一种方式),第二种永远是垃圾,如果希望批量创建多个对象,推荐第三种方式。
3.自定义构造函数:2步
1.创建自定义构造函数
function 类名(name,age,hobby){
this.name=name;
this.age=age;
this.hobby=hobby;
}
2.调用构造函数创建对象:
var obj=new 类名(实参,…);
面向对象:
优点:
所有的属性和方法都保存在一个对象之中 – 更符合现实生活,更有意义。
每个功能特地的分开写 – 便于维护,哪怕不写注释也看得懂
一个方法触发,多个方法联动
继承:
父对象的成员(属性和方法),子对象可以直接使用
为什么要继承:代码重用!提高代码的复用性,节约了内存空间,网站的性能自然也就提升了。
何时继承:只要多个子对象共用的属性和【方法】,都要集中定义在父对象之中
如何找到原型对象(父对象):保存一类子对象的共有属性和共有方法
**对象名.__proto__** ;
**构造函数名.prototype**;
**两链一包:作用链和【原型链】和闭包** (初次简单理解)
原型链:每个对象都有一个属性:__proto__,可以一层一层的找到每个人的父亲,形成一条链式结构,我们就称之为原型链。
可以找到父对象的成员(属性和方法),作用:找共有属性和共有方法的,自己没有的,会悄悄的向上找,如果顶层没有就会报错。
最顶层的是object的原型,上面放着一个我们很眼熟的方法toString()
作用域链:
以函数EC的scope chain属性为起点,经过AO逐级引用,形成的一条链式结构
作用:查找变量,带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
闭包:希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
作用:专门用于完成防抖节流
有了原型对象,可以设置共有属性和共有方法:
原型对象.属性名=属性值;
原型对象.方法名=function(){};
判断是自有的还是共有
判断自有:obj.hasOwnProperty(“属性名”);
结果为true,说明是自由属性,如果是false,有两种可能,有可能是共有,有可能是没有。
判断共有:
if(obj.hasOwnProperty("属性名")==false&&"属性名" in obj){
会自动查找整条原型链上的属性,找到了结果就为true,没找到结果就为false
}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===undefined){
Array.prototype.indexOf=function(key,starti){
starti===undefined&&(starti=0);
for(var i=starti;i<this.length;i++){
if(this[i]==key){
return i;
}
}
return -1;
}
}
多态:
子对象觉得父对象的成员不好用,就在本地定义了一个同名函数,覆盖了父对象的成员,
不严格的定义:同一个函数,不同的人来使用,表现出来的效果是不一样的,有多种形态
实现自定义继承:
1.两个对象之间设置继承
**子对象.__proto__=父对象**
2.批量设置继承:
**构造函数名.prototype=父对象**
注意时机:应该在创建对象之前设置好继承关系
如何判断xxx是不是一个数组:4种方法
Array.prototype.isPrototypeOf(x);
x instanceof Array;
Array.isArray(x) ES5提供的
以上三种,结果为true,说明是数组,结果为false,说明不是数组
*输出【对象的字符串】形式
在Object的原型上保存着最原始的toString();
最原始的toString()输出的形式:[object 构造函数名]
借用的固定套路:Object.prototype.toString.call/apply(x)==="[object Array]";
ES6 – class关键字
为了简化面向对象:封装、对象、多态
class 类名 extends 老类{
constructor(name,speed,rl){
就跟以前的构造函数的写法一样
super(name,speed);
this.rl=rl;
}
}
Function:作用域链&闭包
作用域:2种
1、全局:随处可用,可以反复使用,缺点:容易被污染
2、函数:只能在函数调用时内部可用,不会被污染,缺点:一次性的,使用完就会自动释放
闭包:
作用:用于防抖节流
希望保护一个可以【反复使用的局部变量】的一种词法结构,其实还是一个函数,只是写法比较特殊
如何使用:思路
1、两个函数进行嵌套
2、外层函数创建出受保护的变量
3、外层函数return出内层函数
4、内层函数操作受保护的变量
强调:
1、判断是不是闭包,有没有两个函数进行嵌套,返回内层函数,内层函数再操作受保护的变量
2、外层函数调用几次,就创建了几个闭包,受保护的变量就有了几个副本
3、同一次外层函数调用,返回的内层函数,都是在操作同一个受保护的变量
缺点:受保护的变量,永远不会被释放,使用过多,会导致内存泄漏 - 闪退的,不可多用!
防抖节流
5个事件需要防抖节流 - 共同:触发的飞快,但是我们不需要飞快的修改DOM树!
1、elem.onmousemove;
2、input.oninput;
3、elem.onclick;
4、window.onscroll;
5、window.onresize;
固定公式:
function fdjl(){
var timer=null;
return function(){
if(timer!=null){
clearTimeout(timer)
}
timer=setTimeout(()=>{
操作!
},1000)
}
}
函数的执行原理
1.程序加载时:创建执行环境栈(ESC):保存函数调用顺序的数组
首先压入全局执行环境(全局EC)
全局EC引用着全局对象window
window中保存着我们的全局变量
2. 定义函数时
创建函数对象:封装代码段
在函数对象中有一个scope(作用域)的属性:记录着函数来自的作用域是哪里
全局函数的scope都是window
3. 调用前:
在执行环境栈(ECS)中压入新的EC(函数的EC)
创建出活动对象(AO):保存着本次函数调用时用到的局部变量
在函数的EC中有一个scope chain(作用域链)属性引用着AO
AO还有parent属性是函数的scope引用着的对象
4. 调用时:
正是因为有了前三部,才带来了变量的使用规则:优先使用局部的,局部没有找全局,全局没有就报错
5. 调用完:
函数的EC会出栈,没人引用着AO,AO自动释放,局部变量也就释放了
ES5 - 保护对象的属性和方法
**对象的每个属性都有四大特性+get()+set():**
value: 1001,
writable: true,
enumerable: true,
configurable: true
修改四大特性:
Object.defineProperties(对象名,{
"属性名":{
四大特性
},
...
})
get()和set():
Object.defineProperty(对象名,"属性名",{
get:()=>{
console.log("获取数据会进行拦截")
},
set:(v)=>{
console.log("设置数据会进行拦截")
}
})
**三个级别:**
1.Object.preventExtensions(x);
2.Object.seal(x);
3.Object.freeze(x);
深拷贝还有一种方式:
后端穿衣服:var jsonTxt=JSON.stringify(jsonObj) - Node.js就是这句话
前端脱衣服:var jsonObj=JSON.parse(jsonTxt); 或者 eval("("+jsonTxt+")")
Error对象
浏览器自带4种错误类型:快速找到错误
① 语法错误:SyntaxError - 一定是你的符号/语法写错了
② 引用错误:ReferenceError - 没有创建就去使用了
③ 类型错误:TypeError - 不是你的方法,你却去使用了,最有可能的就是你拿到了undefined和null
④ 范围错误:RangeError - 只有API会遇到:num.toFixed(d);
错误处理:就算发生错误,我们也不希望报错,而是给出一个错误提示,让后续代码依然可以继续执行!
语法:
try{
只放入可能出错的代码
}catch(err){
发生错误后才会执行的代码
err - err形参会报错到错误消息,但是不是报错,只是一个黑色的错误消息,
但是是英文,中国人不一定看得懂,建议你添加上中文错误提示
}
try...catch...的性能非常差,几乎是所有代码里效能最差的,放在try里面的代码
效率都会被降到最低
*可用一个技术代替他:分支技术!
*开发经验的累积:记住一切的客户端输入/用户输入都是坏人 - 但是不必担心,只要做好防护就
不会出错(!isNaN、正则)
抛出自定义错误:只要是报错,就会导致后续代码不执行
throw new Error("自定义错误消息");
事件轮询
JS其实是单线程应用,代码必须是从上向下,一步一步执行的,如果某一个代码非常耗时,可能会
导致整个页面都卡住了,尤其是如果把JS放在head里面,用户可能只能看到一个白板
1、宏任务
不会再卡住我们的单线程应用,可以让后续代码先走,我们慢慢跟着来,但是问题在于,多个
宏任务同时存在,到底谁先执行,谁后执行,分不清楚
1、定时器:setInterval 和 setTimeout
2、AJAX - 他是前端和后端连接的关键点,他也是一个异步宏任务
2.微任务
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);
里面return了Promise
console.log("后续代码跑的依然更快")
封装一个运动(动画)函数
每次都要写elem.style.css属性名="css属性值",简化这一句话
function animate(selector,obj){
var elem=document.querySelector(selector);
for(var i in obj){
console.log(i)
elem.style[i]=obj[i];
}
}