typeof 与 instanceof
typeof:
可以区别: 数值,字符串,布尔值,undefined,function
不可以区别: null,对象,数组 打印出来都是Object
instanceof:
专门用来判断对象数据的类型:Object,Array,Fuction
还可以用来判断undefined,null
null和undefined的区别
undefined 代表未定义 没有赋值
null 代表赋值了,只是赋的值为null
console.log(null == undefined) 返回true
原因: null的布尔值为0 undefined的布尔值也为0
this指向
<script>
console.log(this) //this 指向window
function fn(){
console.log(this)
}
fn() //这边this指向window
//相当于执行 window.fn()
new fn() //this 指向实例化对象
var obj1 = {
fun:function(){
console.log(this)
}
}
obj1.fun() //this指向obj1
var obj = {name:'jenny'}
fn.call(obj) //强行将this指向obj
fn.apply(obj) //强行将this指向obj
</script>
this的理解:
-
关键字,不能将其定义成变量
-
this本身是一个内置的变量,该变量用于指向一个对象
-
this分为两种:
全局this 指向window
局部(函数)this 指向调用的对象,构造函数this 指向构造函数的实例对象
-
函数的this不是在定义的时候决定的,实在调用的时候决定的
回掉函数(DOM事件函数,定时器函数)
定义:1.自己定义 2.不是自己手动调用 3.最终还是执行了(满足一定条件之后执行)
立即执行函数(IIFE)
特点:1.只执行一次,2.在代码执行到函数位置执行3.内部的数据是私有的4.一般为匿名函数,当需要调用匿名函数可以用argumens.callee 去调用5.()代表执行符号,只有表达式才能被执行符号执行
(function ()()) //w3c推荐
或者
(function ())()
原型对象与原型链
- 每个函数都有一个属性prototype(显式原型对象)
- 每个实例化对象上面都有一个属性__poto__,该属性指向当前实例化对象的原型对象(隐式原型对象)
- 构造函数的显式原型对象 === 当前构造函数实例化对象的隐式原型对象
为什么要设计出原型??
function Person(name,age){
this.name = name,
this.age = age,
this.eat = function (){
console.log("i like eat meet")
}
}
var person1 = new Person('blossom',22)
var person2 = new Person('smith',22)
当需要定义公共的方法时,可以将该方法定义在原型对象上供所有的实例对象使用,从而减小内存开销
显式原型 vs 隐式原型
显示原型可被程序员直接操作,隐式原型不能被程序员直接操作(ES6允许操作隐式原型)
值传递和引用传递
值传递:通常针对基本数据类型,传值的内容就是值本身
引用传递:通常针对的是引用数据类型(对象),传递的是数据在内存中的地址值
执行上下文
定义: js引擎在js代码正式执行之前会先创建执行环境,在执行环境中做预处理工作
工作内容:
1.创建空对象 2.该空对象用来收集变量,函数,函数的参数(找var 和 function) 3.创建作用域链 4.确认this的指向
变量提升(预处理)
- 找var function 关键字
- 找到var之后,将var 之后的变量提前声明 但是不进行复制,相当于var a;
- 找到function之后定义该函数
==注意:当有同名的变量和函数时,预处理之后 typeof得到的一定是 function==
函数有个默认的属性 name === 函数名
执行上下文栈
作用:用来保存 执行上下文对象
压栈特点:先进后出,后进先出
作用域
- 定义:代码执行区域
- 作用域什么时候产生:代码定义的时候产生
- 作用域分类:全局作用域,局部作用域(函数作用域)
- 作用域什么时候销毁: 局部:函数执行完成 全局:关闭浏览器
- 当初为什么要设计作用域:防止变量污染,隔离变量
- 作用域链:查找(使用)变量时,会先在当前作用域找,如果当前作用域没有,向外层作用域去找,直到找到全局作用域,如果还是没有,会报错,xxx is not defined
- 在作用域中查找变量去哪找:取当前作用域下的执行上下文对象中去找
==注意:obj.test() 对象是在作用域上找,方法是在原型链上找==
作义域 vs 执行上下文
作用域是在代码定义的时候产生(只有一个)
执行上下文是在代码执行前产生(可以有多个)
闭包
(在全局中,定义一个函数,未调用,全局执行上下文中还是会有这个函数,在局部作用域中,定义一个函数,未调用,局部执行上下文中,不会有这个函数)
定义:
- 闭包是一个闭合容器
- 我们可以认为闭包就是一个对象:{key:value}
function fun(){
var num = 1,
return function fun2(){
console.log(num)
}
}
var fun2 = fun()
闭包的形成条件:
- 函数嵌套
- 内部函数引用外部函数变量
- 外部函数一定被调用
作用:
- 延长外部函数的局部变量的生命周期
- 从外部访问函数内部的局部变量
闭包的缺点:
- 占内存
- 不及时清除闭包容易导致内存溢出
使用闭包时,怎么避免闭包带来的缺点
- 能不用就不用
- 及时清除闭包
闭包的使用场景
- 解决循环遍历加监听的问题
- 将内部的函数返回出来
- 将函数作为实参传递给另一个函数
js是单线程还是多线程?
js引擎是单线程,但是它可以通过轮转时间片实现多线程操作
垃圾回收机制(循环机制)
计数清零: 看内存的地址身上有几个指针指向,当一块内存地址身上指针个数为零,说明这块地址马上要被回收
标记清除: 进入代码执行环境后,检测到需要使用的变量之后就在其身上加一个进场标记,,在代码执行完成的时候,就会在之前加标记的变量身上再添加一个出场标记,当出场标记和进场标记同时存在时,该变量就会被销毁
同步 vs 异步
- 同步:同步会阻塞后续代码执行,同步没有回调
- 异步:异步时非阻塞的,异步一定有对应的回掉函数
对象的创建方式
- Object构造函数方式 缺点:语句太多,流程太啰嗦
var person = new Object()
person.name = 'kate'
person.age = '13'
- 对象的字面量创建方式,优点:书写简单,方直观 缺点:有太多重复的代码
var person1 = {
name:'kate',
age:'18'
}
var person2 = {
name:'bob',
age:'15'
}
- 工厂模式:优点:避免重复代码,可以批量生产对象
- 缺点:不能明确区分是哪一类
function Person(name,age){
return {
name:name,
age:age
}
}
var person1 = Person('kate','12')
var person2 = Person('bob','13')
- 自定义构造函数模式
- 优点:可以生产多个实例对象
- 缺点:如果定义的方法直接给实例对象,会消耗大量内存
function Person(name.age){
this.name = name,
this.age = age,
this.text = function (){
console.log('hhh')
}
}
function Student(name,age,sex){
this.name = name,
this.age = age
this.sex = sex
}
var man = new Person('kate','18')
var bob = new Student('bob','13')
继承
- 原型继承:子类的原型对象 == 父类的构造函数
Child.prototype = new Parent()
==注意点:上述操作,会导致子类原型上的构造器属性缺失,所以需要手动的添加构造器属性==
Child.prototype.constructor = Child
- 借用构造函数继承(不是真正意义上的继承)
==注意点:父类构造器上的this指向问题==
解决:通过call/apply 方法强制修改this指向
function Parent(name,age){
this.name = name
this.age = age
}
function Student(name,age,sex){
// this.name = name
// this.age = age
//可以改变构造函数的this指向,从而减少代码量
Parent.call(this,name,age)
this.sex = sex
}
事件循环机制
- js是单线程
- 所有的js代码都会在主线程执行
- 同步任务加载及执行
- 异步任务不会立即执行,而是会交给对应的管理模块
- 管理模块一直被监视异步任务是否满足条件,如果满足条件会将对应的回调放入callback queue(回调队列)
- 主线程上的同步任务执行完一i后会通过event loop(事件轮询机制)询问callback queue,查看事件是否有可执行的回调函数,如果有将回调勾到主线程上执行,如果没有,待会再来询问
ES6语法
严格模式 (use strict)
语法和行为的改变
- 必须用var声明变量,不然会报错
- 禁止自定义函数的this指向window
function fn() {
console.log(this)
}
fn()
//this为undefined
- 创建eval作用域
var a = 123
eval('var a = 1; alert(a)')
console.log(a)
//alert的值为1
//console的值为123
//使用严格模式 不会将变量污染全局
- 对象不能又重名属性
Object 扩展
- Object.create(prototype, [descriptors])
-
作用:可以指定对象为原型创建新的对象
-
为新的对象指定新的属性,并对属性进行描述
-
value :指定值
-
writable :标识当前属性是否可以修改,默认值为false
-
configurable :标识当前属性是否可以删除,默认值为false
-
enumerable :标识当前属性是否能用for in 枚举,默认值为false
var obj = {
name: 'kate',
age: 14,
setName: function(){
this.name = 'bob'
}
}
var obj2 = {}
obj2 = Object.create(obj,{
sex:{ //配置对象
value:'男', //给obj2上新增属性
writable: true, //当前属性可修改
configurable: true, //当前属性可删除
enumerable: true //当前属性可被枚举
}
})
obj2.sex = '女' //修改属性值
delete obj2.sex //删除属性
- Object.defineProperties(Object,descriptors)
- 作用:为指定对象定义扩展多个属性
- get:用来获取当前属性值的回调函数
- set:用来修改当前属性值的触发回调函数,并且实参即为修改后的值
- 存储器选择:setter,getter一个用来存值一个用来取值
var sex = '男'
var obj = {
name: 'kate',
age: 18
}
Object.defineProperties(obj,{
sex: {
get: function (){ //设置值,获取对象属性值时会自动被调用
return sex
},
set: function (value){ //当修改扩展的属性值时,该方法就自动被调用
sex = value
}
}
})
obj.sex('女')
call() | apply() | bind() 对比
var obj = {
name:'kate',
age:12
}
function fn(msg){
console.log(this)
console.log(msg)
}
fn() //函数自调用时this指向window
fn.call(obj, 'this is call passing in arguments')
fn.apply(obj, ['this is apply passing in arguments'])
fn.bind(obj, 'this is bind passing in arguments')()
call与bind传入参数得方式一样
call与apply绑定完this会立即调用函数,bind只会绑定this但是不会立即执行
bind的运用场景:当要为回调函数绑定this时,只能用bind
setTimeout(function (){
console.log(this)
}.bind(obj),2000)
关键字
let
- 在块级作用域中有效
<button>第一个按钮</button>
<button>第二个按钮</button>
<button>第三个按钮</button>
var btns = document.getElementByTagName('button')
for(var i = 0; i < btns.length; i++){
var btn = btns[i]
(function (i){
btn.onclick = function(){
alert(i)
}
})(i)
}
//之前要实现点击按钮跳出相应按钮的下标,需要通过闭包来实现
for(let i = 0; i < btns.length; i++){
let btn = btns[i]
btn.onclick = function (){
alert(i)
}
}
//通过let会产生一个自己的块级作用域
//例如上例,会产生三个不同的块级作用域,每个作用域的i都不相同
//因此在点击button按钮时,会获得当前块级作用域中的i值
-
不能重复声明,重复声明之后就会报错
-
不会预处理,不存在变量提升
const
- 作用:定义一个常量
const NUMBER = 123
- 特点:
- 不能被修改
- 在块级作用域中有效
- 不能重复声明
- 不会预处理,不存在变量提升
解构赋值
对象的解构赋值
let obj = {
name:'kate',
age:14
}
//原来获取obj中的name
let yname = obj.name
let yage = obj.age
cobsole.log(yname,yage)
//es6 通过对象中的key值 来获取相对应的值
let {name,age} = obj
console.log(name,age)
数组的结构赋值
let arr = [1,3,4,2,5]
//es6 通过获取数字的index 来获取相对应的值
let [a,b,c] = arr
console.log(a,b,c)
//打印 1 3 4
function fun({name,age}){
console.log(name,age)
}
fun(obj)
简化对象写法
let name = 'kate',
let age = 14,
let obj = {
name : name,
age : age
setName : function (name){
this.name = name
}
}
//es6 简化之后
let obj = {
name,
age,
setName(name){
this.name = name
}
}
三点运算符(...)
function fun(name,age){
console.log(arguments)
//打印出关于参数的伪数组(不具备真数组的所有方法)
}
fun('kate',12)
//es6
function fn (...value){
console.log(value)
//打印出关于参数的真数组(具备数组的所有方法)
}
fn('kate',12)
function fn2(name,...value){
console.log(value)
//打印 [12,'男']
//name会对应kate,其余的参数都将会被value搜集
//...value 一定要放在参数最后一位,否则就会报错
}
fn2('kate',12,'男')
扩展运算符
let arr = [1,4,5,6]
let arr2 = [2,3]
arr = [1,...arr2,4,5,6]
console.log(arr)
//打印[1,2,3,4,5,6]
//...arr2 可以循环遍历数组里的值 ...arr2 => 2 3
//...obj 不可以循环遍历对象
形参默认值
//es6
funtion Point(x = 0,y = 0){
this.x = x,
this.y = y
}
let location = new Point()
console.log(location)
//es5中创建实例对象,如果不传值打印出来的属性值为undefined
//es6中允许形参赋值,如果创建实例对象没有传入值,那么默认打印出的是定义时赋予的值
Promise对象
- 作用:解决异步回掉
- 语法:
- 1.创建promise实例对象 ---> 初始化promise对象的状态为pending
- 2.执行异步任务:开启定时器任务/执行ajax请求
- 3.根据异步任务执行的结果去修改promise实例对象的状态:resolve()成功,reject()失败
- 4.promise实例对象默认有个then()方法,该方法需要两个参数,这两个参数是回调函数
- 5.当promise对象的状态变为成功/失败的时候自动调用then方法中的回调函数
Symbol.iterator
- 概念:Symbol.iterator是一个接口机制(方法),为各种不同的数据结构提供统一的访问机制
- 作用:
- 为各种数据结构,提供一个统一的、便捷的访问接口
- 使得数据结构的成员能够按照某种次序排列
- ES6创造了一种新的遍历命令 for ... of循环,Iterator接口主要供 for ... of消费
- 工作原理
- 创建一个指针对象(遍历器对象),指向数据结构的起始位置
- 第一次调用next()方法,指针自动指向数据结构的第一个成员
- 每次调用next()方法返回的是一个包含value 和 done 的对象,{value:当前成员值,done:布尔值}
- value表示当前成员的值,done对应的布尔值表示当前的数据结构是否遍历结束
- 当遍历结束的时候返回的value值是undefined,done值为false
- 原生具备iterator接口的数据(可用for of 遍历)
- Array
- arguments
- set容器
- map容器
- String
function iteratorUtil(){
console.log(this)
let that = this
let index = 0
if(that instanceof Array){
return {
next(){
return index < that.length ? {value:that[index++],done:false}:{value:that[index++],done:true}
}
}
}else{
let keys = Object.keys(obj)
let length = keys.length
return {
next(){
return index < length ? {value:that[keys[index++]],done:false}:{value:that[keys[index++]],done:true}
}
}
}
}
let arr = [1,2,2,3,4,5]
let obj = {
name:'kete',
age:13,
sex:'男'
}
console.log(Object.keys(obj))
// let iteratorObj = iteratorUtil(arr)
Array.prototype[Symbol.iterator] = iteratorUtil
Object.prototype[Symbol.iterator] = iteratorUtil
for(let i of arr){
console.log(i)
}
for(let i of obj){
console.log(i)
}
async函数
- 语法
- 定义:async + function (){}
- 函数里搭配使用 await 异步操作
- await 后跟异步任务,当执行await会阻塞,当异步任务成功后续操作
- async + promise对象
- 异步任务使用promise包装
- await的异步任务通常返回一个promise对象
- 通过修改promise实例的状态来通知await以及给await设置返回值(传递的数据)
- 特点
- 语义化明确
- 真正意义上解决异步回掉的问题
class
- 通过class定义类/实现类的继承
- 在类中通过constructor定义构造方法
- 通过new来创建类的实例
- 通过 extend 来实现类的继承
- 通过super()调用父类的构造方法
<script>
//类
class Person{
//类的构造函数
constructor(name,age){
this.name = name
this.age = age
}
//这个方法相当于构造函数Person2.prototype.showName = function () {}创建出来的方法
// showName(),供实例对象使用
showName(){
console.log(this.name,this.age)
}
//这个是Person类自身的方法,实例对象不能使用
static test(){
console.log('hahahahhah')
}
}
//构造函数Person2中的原型中也有一个constructor,这个constructor指向Person2自己
function Person2(name,age) {
this.name = name
this.age = age
}
Person2.prototype.showName = function (){
}
Person2.test = function (){
console.log('我是构造函数Person2的自身方法,该方法不能给实例对象使用')
}
class ChildPerson extends Person{
constructor(name,age,sex){
//super()调用父类的构造方法
//相当于写了 this.name = name this.age = age
super(name,age)
this.sex = sex
}
// 重写父类Person的showName()方法
showName(){
console.log(this.name,this.age,this.sex)
}
}
let person = new Person('Bob',23)
person.showName()
let childPerson = new ChildPerson('jenny', 2, '女')
childPerson.showName()
</script>
浅度拷贝 和 深度拷贝
-
浅度拷贝:拷贝一个结构,当里面的数据类型为基本数据类型时,会原样拷贝,当遇到对象和数组时,拷贝的时对象的引用地址,所以新对象对象/数组,或者被拷贝的对象/数组,会导致数据发生共同的改变
-
深度拷贝:拷贝一个结构,当结构内部存在对象或者数组时,不仅会把对象和数组的引用地址拷贝下来,还会拷贝对象和数组的值,所以当改变新对象/数组或者被拷贝对象/数组时不会导致对方数据改变
-
深度拷贝的核心思想:克隆的数据不能有引用数据类型,如果有,继续遍历挖掘,直到每次拷贝的数据都是基本数据类型
//实现深度克隆
<script>
//判断是数组还是对象
function getType(target){
// console.log(Object.prototype.toString.call(target).slice(8,-1))
return Object.prototype.toString.call(target).slice(8,-1)
}
function cloneUtil(target){
let result;
// 判断数据类型
if(getType(target) === 'Array'){
result = []
} else if(getType(target) === 'Object'){
result = {}
} else {
return result
}
for (let i in target) {
let item = target[i]
//当遍历的数据中仍然有数组/对象时,重新按照之前的步骤 进行拷贝,直到所有拷贝数据的类型都是基本数据类型
if(getType(item) === 'Object' || getType(item) === 'Array'){
let cloneitem = cloneUtil(item)
result[i] = cloneitem
} else{
result[i] = item
}
}
return result
}
let obj = {
name:'Bob',
age:22,
sex:{
option1:'男',
option2:'女'
}
}
let cloneObj = cloneUtil(obj)
cloneObj.sex.option1 = 'xxx'
console.log(obj)
console.log(cloneObj)
</script>
正则表达式
/./g . 表示通配符
/3\.14/g \ 表示转义符 把通配符. 转化成一个普通的点
/\w/g \w 表示匹配阿拉伯数字,英文字母,和一个下划线(0~9 a~z A~Z)
/\W/g \W 表示匹配被\w排除在外的字符(\w的补集)
/\d/g \d 表示仅仅匹配阿拉伯数字
/\D/g \D 表示匹配除阿拉伯数字以外的字符 也匹配空格
/\s/g \s 表示匹配空格、制表符(Tab)、断行等
/\S/g \S 表示\s的补集
/[a-z]/g [] 表示选择范围,匹配括号中的任意一个字符
/yo+/g + 表示重复一次或者多次,例如oooooo可以写成o+,
/[yY][oO]+/g 表示Yoooo,yOOOOOO,YOOOOOO,yooooo 满足四种方式
/[yY][oO]+[!~\.]*/g 表示[!~\.]重复零个或多个
/p?/g ? 表示出现零次或者一次
/yo{1,2}/g {a,b}表示重复a到b次