目录
- 1_js数据类型
- 2_Object.defineProperty,Object.defineProperties
- 3_判断空对象
- 4_判断数组
- 5_ES6常用语法
- 6_原型详述
- 7_继承
- 8_DOM API
- 9_BOM_API
- 10_事件
1_js数据类型
- 基本数据类型
string number boolean undefined null symbol bigint - typeof 和 isNaN
- typeof 能判断除了null的基本类型 typeof null === ‘object’,typeof不能判断数字是否为NaN(typeof NaN===‘number’)
- typeof能判断声明的函数 typeof function(){} === ‘function’
- isNaN(NaN) === isNaN(undefined) === !isNaN(null) === true
- instanceof
手写实现_instanceof(a,b)的时候,注意几个细节
① b是否有prototype
② a是否为基本数据类型(a==null || typeof a!==‘object’),当变量为基础数据类型的string/number/boolean时,任何尝试访问类实例方法或属性的操作(无论是a.__proto__还是Object.getPrototypeOf(a)),都会在运行时自动创建包装类,即new String/Number/Boolean(a),再访问
③ while中比较时,注意return的条件是 a为空或a===b.prototype - 常见引用类型
Object,Function,Array,Date - 变量比较规则
- 相同类型的基本类型比较(也叫不可变的值的比较)
Symbol有一点点小小的特殊,有,但不是完全有
其他的,值相等且类型一样 === 就返回true
Symbol.for(key) //注意 作为key 1==='1', Symbol.for('1') === Symbol.for(1) //true
从全局Symbol注册表中找key,找得到就返回那个Symbol,找不到再创建
Symbol(sth)
每次调用都会创建一个独一无二的Symbol //Symbol(1) == Symbol(1) //false
- 引用类型的比较(也叫可变值的比较)
==和===都是比较引用是否相同
例:
x = new Number(1)
y = new Number(1)
x==y //false
x = {toString:function(){return 99}}
y = new Date();y.toString=function(){return 99}
z = function(){};z.toString=function(){return 99}
x==99,y==99,z==99 //true
x==y,x==z,y==z //false
(引用类型,即使是对象和函数和日期类,但是都是Object,所以不是不同类型的比较)
- 不同类型的比较
// === 一定返回false
1.NaN与所有的值都不等,包括自己 NaN == NaN //false
2.
null == undefined //true
null === null //true
3.引用类型和字符串比较 等同于 obj.toString() === str
({}) == '[object Object]' //true
var date = new Date()
[date] == date.toString() //true
[1,3] == '1,3' //true
function f(){}
f.toString=function(){return 1}
f == 1 //true
4. Number的实例与其它类型比较,隐式转换调用的是valueOf,而不是toString
例:
x = new Number(1)
x.toString=function(){return 'haha'}
x=='haha' //false
x.valueOf=function(){return 'vvvv'}
x=='vvvv' //true
5.其他的!**不同**!类型比较,都是先转为数字再比较
(1) 对象->字符串->数字 即 Number(obj.toString()) 比如[]==0,[].toString()->'',''->0, 0==0 //true
(2) 字符串->数字
(3) 布尔->数字(1/0)
(4) undefined->NaN (也就是 undefined==null,undefined===undefined)
(5) Symbol cannot convert to Number,所以Symbol与万物不等(Symbol.for()不是例外,只是创建机制不一样)
2_Object.defineProperty,Object.defineProperties
- Object.defineProperty(obj,key||Symbol,descriptor)
注意:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。合理的做法是在Object的构造函数中使用。
//descriptor:
{
configurable, //定义为false后,不能再进行任何配置,也不能重新设置为true
enumerable, //是否可在for in和 Object.keys中枚举
writable, //value是否可修改,严格模式下修改会报错
value, //显示定义这个值
}
var x = {a:'a'}
Object.defineProperty(x,'a',{
enumerable:false
})
for(let k in x){console.log(k)} //nothing
getter和setter
x = {__name:'xiao li'}
Object.defineProperty(x,'name',{
get:function(){
return 'i am ' + this.__name
},
set:function(name){
this.__name = 'xiao ' + name
}
})
console.log(x.name) //i am xiao li
x.name = 'zhang'
console.log(x.name) //i am xiao zhang
- Object.defineProperties
var x = {a:'a',b:'b'}
Object.defineProperties(x,{
a:{
enumerable:false
},
b:{
writable: false
}
})
x.b='c'
for(let k in x){console.log(x[k])} //'b'
3_判断空对象
JSON.stringify(x) === '{}' //如果x包含函数,那么这种方式无效
x = {f:function(){}}
console.log(JSON.stringify(x) === '{}') //true
x = {a:'a'}
Object.defineProperty(x,'a',{enumerable:false})
console.log(Object.keys(x).length===0) //这个api返回的是enumerable:true的keys
Object.getOwnPropertyNames(x) == 0 //[]==0 为true
//这种方法也有缺陷,它能返回enumerable为false的keys,但是不能返回使用Symbol部署的属性
x = {a:'a'}
Object.defineProperty(x,'a',{enumerable:false})
console.log(Object.getOwnPropertyNames(x)) //['a']
//BUT
x = {}
x[Symbol.for(1)] = 1
x[Symbol.iterator] = function(){}
console.log(Object.getOwnPropertyNames(x)) //[]
//比较通用的方案
function isEmptyObject(x){
//是否为基本数据类型
if(x==null || typeof x!=='object'){
return false
}
return Object.getOwnPropertyNames(x).length==0 && Object.getOwnPropertySymbols(x).length == 0
}
4_判断数组
var x = {0:'ele_0',length:0}
var arr = [1,2,3]
//1. Array.isArray
console.log(Array.isArray(x))
console.log(Array.isArray(arr))
//2. arr.constructor === Array
//3. arr instanceof Array
//4. Object.prototype.toString.call(arr) === '[object Array]'
5_ES6常用语法
- var let const
- 块级作用域、暂时性死区、变量地址不可变
- 模板字符串
- 解构赋值(对象、数组解构,重命名)
- 扩展运算符…(解构、rest参数、数组或对象部分属性复制、类数组转换)
- 部署了Symbol.iterator的原生数据结构及for of和…
string,map,set,array,类数组(arguments,nodelist等) - 箭头函数(注意this,不能用作构造函数,bind对它的影响,使用rest代替arguments)
- Promise,async/await,generator/yield(Promise链式调用、Promise api原理)
- import/export
6_原型详述
(1)每个函数有一个prototype指向其原型对象
(2)每个构造函数的实例对象有一个__proto__指向其构造函数的原型对象
(3)原型对象的constructor指向其构造函数
(4)访问对象的一个属性,会顺着原型链往上找,直到找到Object的原型对象上还没有,就返回undefined
(5)Object的原型对象的__proto__指向null
(6)Object.create(null)创造的对象身上无任何属性 x instanceof Object //false
(7)在使用构造函数new一个实例对象时,首先使用Object.create(Father.prototype)生成一个空对象(空对象的__proto__指向父类的prototype),然后让this指向这个空对象,然后执行构造函数内部代码,最后如果没有主动return object,就return this(return null也被忽略,仍然返回this)。
(8)一个构造函数new的所有实例的__proto__都指向同一个原型对象
7_继承
(0)父亲
function Father(name){
console.log('执行父类构造函数')
this.name = name
this.sayName = function(){
alert(this.name)
}
}
Father.prototype.age = 10
(1)原型链继承
function Person(){
}
Person.prototype = new Father() //Person new的实例的构造函数会指向Father,所以要用下面一行代码修复
Person.prototype.constructor = Person
var p = new Person('xxx') //p.__proto__ === Person.prototype, p.__proto__.__proto__ === Father.prototype
console.log(p.age) //10
console.log(p.name) //undefined
重点:让新实例的原型等于父类的实例。
特点:1、实例可继承父类构造函数内this挂载的属性及方法,可继承父类原型对象上的方法
缺点:1、(重点)新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例和原型对象上的属性。
(2)借用构造函数继承
function Person(name){
Father.call(this,name)
}
var p = new Person('xxx')
console.log(p.name) //xxx
p.sayName() //xxx
console.log(p.age) //undefined
重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性。
2、每次new都要重新调用父类构造函数
3、每个新实例都有父类构造函数的副本,臃肿。
(3)组合继承(原型链继承+借用构造函数继承)
function Person(name){
Father.call(this,name)
}
Person.prototype = new Father() //Person new的实例的构造函数会指向Father,所以要用下面一行代码修复
Person.prototype.constructor = Person
var p = new Person('xxx')
console.log(p.name) //xxx
console.log(p.age) //10
p.sayName() //xxx
重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数(如果主动修复的话,但是逻辑上是应当修复的)。
(4)原型式继承/实例继承
function Person(name){
let instance = new Father()
instance.name = name
return instance
}
var p = new Person('xxx') //也可以直接var p = Person()
console.log(p.name,p.age,p.sayName) //xxx,10,f
console.log(p instanceof Person) //false
优点:
不限制调用方式
简单,易实现
缺点:
不能多次继承
(5)寄生组合继承
function Person(name){
//继承父类构造器属性
Father.call(this,name)
}
//继承父类原型对象上的属性:写法一
(function(){
//创建空类
let Super = function(){}
Super.prototype = Father.prototype
//父类的实例作为子类的原型
Person.prototype = new Super() //Person.prototype.__proto__ === Father.prototype
}
)()
//修复构造函数指向
Person.prototype.constructor = Person
//继承父类原型对象上的属性:写法二
(function(){
var prototype = Object.create(Father.prototype) //function F(){};F.prototype = param;return new F();
//prototype.__proto__=Father.prototype
prototype.constructor = Person
Person.prototype = prototype
})()
var p = new Person('xxx')
console.log(p.name,p.sayName,p.age)//xxx f 10
PS:
constructor其实没有什么用处,只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。实际中,除非通过new xxx._proto_.constructor()显示调用,否则没有影响。
不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。
(6)ES6 class extends
class A{
a(){}
b=()=>{}
static c = ()=>{}
}
class B extends A{}
//(1)B.__proto__===A
//(2)B.prototype.__proto__ === A.prototype,...A.call(this,...params)
//(1)导致B也继承了A的静态方法
//(2)就是常规的原型链和构造函数的组合继承,既能继承构造函数的属性,也能继承A的原型上的属性
8_DOM API
单独的一篇文章整理。或者可以移步知乎,这篇讲的很好。
9_BOM_API
- 什么是BOM?浏览器对象模型
- 常用的BOM属性?
- location
- href 返回或设置当前文档的url
- search 返回查询字符串的部分,包括"?"
- hash 返回url #后的内容
- host 返回域名
- pathname 域名后的部分
- protocal 协议(包括冒号)
- port 端口(显示访问的才有)
- reload() 重载页面
- replace(url)设置当前页面url并删除当前页面的历史记录
- history
- go() //go()刷新,go(1)前进一个,go(-1)回退一个
- forward() 前进
- H5 pushState react-router-dom的browserrouter使用的是这个api
- navigator
- userAgent 用于判断用户的浏览器,IOS或Android
10_事件
- 事件流
页面接收事件的顺序,一种是事件冒泡,一种是事件捕获,先进入捕获阶段,从外层(document)到内层,再到处于目标阶段,再到冒泡阶段。 - 绑定事件的方法
- DOM 0级:
//绑定 - 只能绑定一个
btn.onclick = function(e){}
//删除
btn.onclick = null
- DOM 2级:
//绑定 - 能绑定多个回调函数
var callbackFn1 = function(e){}
btn.addEventListener('click',callbackFn1)
//移除
btn.removeEventListener('click',callbackFn1)
- 事件委托
场景:需要为大量dom元素添加时间处理元素的时候,比如为ul的li们添加点击监听事件,如果为每一个li都绑定事件,非常消耗资源。事件委托就是把对子元素的监听,委托给外层元素,利用事件冒泡,由外层元素的回调函数统一处理
//ul>li{$}*5
document.querySelector('ul').onclick = function(e){
//e.currentTarget===this===绑定事件的元素
console.log(e.target.textContent) //e.target是点击的li
}