71. 作用域和作用域链
-
作用域:当前代码的作用范围
-
全局作用域
可在全局window下使用
-
局部作用域
只能在当前局部内使用,常用于函数内部
-
块级作用域
ES6新增,只能在当前{}内使用,通过let声明变量、通过const声明常量
-
-
作用域链
当访问一个变量时,首先在当前作用域下查找,若没有,则一级一级的往上查找,直到window全局作用域,这个查找过程的路径就是一条作用域链
//典型作用域考题 let a = 1 function fn1(){ function fn3(){ function fn2{ console.log(a) } let a fn2() a = 4 } let a = 2 return fn3 } let fn = fn1() fn()//output:undefined
72. let、var、const
var
- ES6之前唯一声明变量的关键字
- 存在变量提升
- 可以重复声明
- 省略var直接赋值变量的话,该变量会变成全局变量
let
- ES6新增声明变量的关键字
- 不存在变量提升
- 不允许重复声明
- 不影响作用域链
- 块级作用域
const
- ES6新增声明常量的关键字
- 声明必须赋初始值
- 值不可修改
- 标识符一般为大写
- 不允许重复声明
- 块级作用域
- 注:对象属性修改和数组元素变化不会触发const错误
73.原型和原型链
-
构造函数使用了new关键字的函数,用来创建对象,所有函数都是Function()的示例
-
原型对象是用来存放实例对象的共有属性、共有方法的一个对象,所有原型对象都是Object()的实例
-
原型链又叫隐式原型链,通过
__proto__
属性串联起来,原型链的尽头是Object.prototype
74. 面向对象oop
-
ES5
创建对象的方式:
funciton 类名称(){}、字面量方式{}、Object
-
ES6
class关键字声明对象
constructor函数为构造函数
static关键字声明静态属性
extends关键字实现继承
super关键字访问父类构造函数
-
面向对象的四大特性
抽象、封装、继承、多态
-
面向对象的三大特点
封装:方法、属性都封装到类的内部
继承:子类可以继承父类的属性和方法
多态:同一个方法名,子类和父类中实现方式不同
-
面向对象的优点
易维护、易复用、易扩展
-
面向对象的缺点
性能比面向过程低
75. 设计模式
-
单例设计模式
JS实现单例设计模式:
- 一个类限制必须只能有一个示例,如果第二次创建的时候,可以抛出错误或者返回第一次的实例
实现示例:
function Person(name,age){ this.name=name this.age=age } Person.prototype.sayHello=function(){ console.log(this.name) } //创建一个代理类,实现单例设计模式 let PersonProxy=(funciton(){ let instance=null return function(name,age){ if(instance){ return instance } return instance=new Person(name,age) } })() let jack=new PersonProxy("jack",22) let mike=new PersonProxy("mike",21) jack.sayHello()//jack mike.sayHello()//jack
-
工厂设计模式
简单工厂:有一个工厂函数创建对象,而不是new出来的,为创建对象的方式之一
实现实例:
//小汽车 function Car(color,doors){ this.color=color; this.doors=doors return this } //大卡车 funciton Trank(weight){ this.weight=weight return this } //增强工厂函数 //可以创建各种各样的对象 function createVihicle(type){ if(type=="Car"){ retrun Car.apply(new Car(),[].slice.call(arguments,1)) }else if(type=="Trank"){ return Trank.apply(new Trank(),[].slice.call(arguments,1)) } } var vihicle=new createVihicle("Car","red",4) var vihicle2=new createVihicle("Trank",666) console.log(vihicle)//Car{color:"red",doors:4} console.log(vihicle2)//Trank{weight:666}
抽象工厂:
- 提供一个注册方法,然后实例这个类;
- 工厂维护一个类的清单,用一个对象来维持当前的所有的类内容,注册的时候可以提供预审查功能;
- 例如要求所有的注册类必须要有update和render方法,否则不允许注册
实现实例:
function Duck(color,age){ this.color=color this.age=age return this } Duck.prototype.update=function(){} Duck.prototype.render=function(){} function Chicken(color){ this.color=color return this } Chicken.prototype.update=function(){} Chicken.prototype.render=function(){} //定义一个工厂函数 var Factory=(function(){ let types={} //存储不同的构造函数,不同角色 return{ //进行注册 registFactory(type,typeFunction){ if(!typeFunction.prototype.update||!typeFunction.prototype.render){ throw new Error(type+"类型没有注册成功,请设置update和render方法后,再进行注册") return } //给types对象注册方法 types[type]=typeFunction } //创建对象 生成对象 createActor(type){ return types[type].apply(new types[type](),[].slice.call(arguments,1)) } } })() //注册 Factory.registFactory("Duck",Duck) Factory.registFactory("Chicken",Chicken) //实例 var yazi = Factory.createActor("Duck","黄",1) var xiaoji=Factory.createActor("Chicken","红") console.log(yazi)//Duck{color:"黄",age:1} console.log(xiaoji)//Chicken{color:"红"}
76. js继承的方法和优缺点
-
构造函数继承
优点:
- 避免了引用类型的值会被所有实例共享
- 在子类实例创建时,可以向父类传参
缺点:
- 方法在构造函数中,每次创建实例对象时都会重新创建一遍方法
示例:
function Person(){ this.name='jack' } Person.prototype.getName=function(){ console.log(this.name) } function Child(){ this.age=18 Person.call(this) } var xiaohai=new Child() xiaohai.getName()//jack
-
原型链继承
优点:
- 写法简单、容易理解
缺点:
- 引用类型的值会被所有实例共享
- 在子类实例对象创建时,不能向父类传参
示例:
function Person(){ this.name='jack' } Person.prototype.getName=function(){ console.log(this.name) } function Child(){ this.age=18 } Child.prototype=new Person() var xiaohai=new Child() xiaohai.getName()//jack
-
组合式继承
优点:
- 融合了构造函数继承和原型链继承的所有优点,是js中最常用的继承方式
缺点:
- 调用两次父类的构造函数
示例:
function Person(){ this.name='jack' } Person.prototype.getName=function(){ console.log(this.name) } function Child(){ this.age=18 Person.call(this) } Child.prototype=new Person() var xiaohai=new Child() xiaohai.getName()//jack //基于缺点进行优化 function Person(){ this.name='jack' } Person.prototype.getName=function(){ console.log(this.name) } function Child(){ this.age=18 Person.call(this) } Child.prototype=Object.create(Person.prototype) Child.prototype.constructor=Child var xiaohai=new Child() xiaohai.getName()//jack
-
ES6的继承
优点:代码简洁、易以理解
缺点:
示例:
class Person{ constructor(){ this.age=18 } } class Child extends ParentP{ constructor(){ super() this.name='jack' } } let xiaohai=new Child() console.log(xiao.name,xiaohai.age)
77.DOM操作
-
创建
- docunment.write
- innerHTML
- createElement
-
增
- appendChild
- insertBefore
-
删
- removeChild
-
改
- 修改元素属性:src、href、title等
- 修改普通元素内容:innerHTML、innerText
- 修改表单元素:value、type、disabled等
- 修改元素样式:style、className
-
查
- DOM提供的API方法:getElementById、getElementByTagName(古老用法,不推荐)
- H5提供的新方法:querySelector、querySelectorAll(提倡)
- 利用节点操作获取元素:父:parentNode;子:children;兄:previousElementSibling、nextElementSibling.(提倡)
-
属性操作
- setAttribute:设置dom的属性值
- getAttribute:得到dom的属性值
- removeAttribute:移除属性
-
事件操作
78. 数组遍历方法
- forEach
- map
- filter
- reduce
- some、every
- for of
79. 深拷贝、浅拷贝
-
浅拷贝
当变量为基本数据类型时,此时浅拷贝为值拷贝
var a=2 var b=a b=3 console.log(a)//2
当变量为引用数据类型时,浅拷贝只是只是进行了变量地址的拷贝,只拷贝了一层
var a={ name:'jack' age:18 } var b=a b.age=19 console.log(a.age)//19
-
深拷贝
指出了拷贝基本类型的值,还完全复刻了对象类型
暴力法:
var a={ name:'jack' age:18 } var b={ name:a.name age:a.age }
Object.assign():
可以对非嵌套对象进行深拷贝,若对象中出现嵌套,那么对成了普通的浅拷贝
var a={ name:'jack' age:18 } var b={} Object.assign(b,a)
JSON进行转换:
这种方式在实际开发中比较常用,不能对对象中的方法进行深拷贝,取不到值为undefined的key等缺点
var a={ name:'jack' age:18 } var b=JSON.parse(JSON.stringify(a))
递归:
function deepCopy(cur,pre){ var cur=cur||{} for(let key in pre){ if(typeof pre[key]==='object'){ cur[key]=(pre[key].constructor===Array)?[]:{} deepCopy(cur[key],pre[key]) }else{ cur[key]=pre[key] } } return cur }
第三方库lodash的cloneDeep()方法
80. JS事件代理原理
原理:不给每个子节点单独设置事件监听器,把事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点
作用:提高了程序的性能