一、js相关
数据类型
强制类型转换中toString和valueof调用
toString偏重显示,valueof偏重计算。当有运算符时优先使用valueof,(== + - *)当valueOf仍无法运算时调用toString方法。在没有运算符的情况下,强制转换为字符串时调用优先toString方法,强制转换为数字时优先调用valueof方法。对象转换时优先调用toString方法
基本数据类型
6种,null undefined number string boolean symbol
null是object吗?
typeof null输出true,但是这只是js的一个bug,js初始版本中认为000开头就是对象,null表示为全0,因此错误判断为object。
typeof vs instanceof
typeof无法准确判断对象属于什么类型,instanceof可以判断对象所属的类。
如何判断一个对象是数组
- xx instanceof Array
- isArray
- 利用class属性判断 Object.prototype.toString.call(obj)===’[Object Array]’
bind call apply
- 这三个方法都是用于改变函数调用时所绑定的this对象
- call(obj,arg1,arg2)
- apply(obj,[arg1,arg2])
- fn=bind(obj,arg1,arg2) fn()
Function.prototype.mycall = function(context) {
var args = [...arguments].slice(1);
//为了保证调用的时候执行上下文指向context,因此将this指向context,然后由context来调用函数
//mycall的this指向调用mycall的函数
context.fn = this;
var result = context.fn(...args);
delete context.fn
return result
}
Function.prototype.myapply = function (context) {
var args = arguments[1];
context.fn = this;
if (args) {
var result = context.fn(...args);
} else {
var result = context.fn();
}
delete context.fn
return result
}
Function.prototype.mybind = function (context) {
var args = [...arguments].slice(1);
context.fn = this;
if (args) {
result = context.fn(...args);
}else {
result = context.fn();
}
delete fn;
return function (){
return result
}
}
数据类型的转换
对象转换成基本数据类型时,先调用Symbol.toPrimitive,如果没有该函数如果有运算符先调用valueof然后调用toString,没有运算符则调动toString。
四则运算符
- 加法运算符:如果一方是字符串则都转为字符串,否则转换为数字。
- 其他运算符:都转换为数字。
== 和严格相等符号区别
- ===:严格相等,判断两者类型和值是否相等;
- ==:
- 首先null被认为是空对象,会调用valueof toString方法,但是它没有,因此除了和undefined比是true,其他都是false。
- undefined会转换为NaN,因此和除了null的比都是false。
- string number boolean这三者比较两边都转换成number
- 对象如果和基本数据类型比较的话,先调用valueof方法,如果返回的是对象则调用tostring方法。注意,如果是数组调用toString是这样调用array.toString()
!和!!
!是非运算,!!是强制转换成boolean值。
面向对象
js有两种属性值:数据属性 访问器属性
数据属性
包含一个数据值的位置
四个特性
- Configurable表示能否通过delete删除属性从而重新定义属性,默认为true。
- Enumerable表示能否通过for-in循环返回属性,默认true
- writable表示能否修改,默认为true
- value包含属性数据值,默认为undefined
修改属性默认值
- Object.defineProperty(注意不是对象原型的方法)
- Object.defineProperties
var person = {};
Object.defineProperty(person,'name',{
writable:false, //不可重写
value:'xxx',
configurable:false //不能delete
})
访问器属性
四个特性
- Configurable表示能否通过delete删除属性从而重新定义属性,默认为false。
- Enumerable表示能否通过for-in循环返回属性,默认false
- get读取属性时调用函数,默认undefined
- set写入属性时调用函数,默认undefined
读取属性特性
- 使用Object.getOwnPropertyDescriptor
var person = {};
Object.defineProperties(person,{
_age:{
value:20
},
state:{
value:'young'
},
age:{
get:function(){
return this._age;
},
set:function(newVal){
if (newVal>50) {
this._age=newVal;
this.state='old';
}else{
this._age = newVal;
}
}
}
})
var descriptor = Object.getOwnPropertyDescriptor(person,'_age');
descriptor.value //20
descriptor.configurable //false
深拷贝与浅拷贝
深拷贝是指完全拷贝下来包括堆内存中的数据,浅拷贝可能会将地址拷贝下来
浅拷贝
- Object.assign(target,source1,source2)将source1和2的属性复制到target中,如有重复则覆盖
let a = {
age:2,
jobs:{
first:'FE'
}
};
let b = Object.assign({},a)
a.age = 4;
b.age //2
- 展开运算符
let a = {
age:2
};
let b = {...a}
a.age = 4;
b.age //2
深拷贝
- JSON.parse(JSON.stringfy(object))
局限性:忽略undefined,忽略symbol,不可序列化函数只能序列化对象或者基本数据类型,不能解决循环引用(会无线相互引用) - 手写
function deepClone(obj){
function isObject(o) {
return (typeof o ==='object'||typeof o ==='function')&&o !==null
}
if (isObject(obj)) {
throw new Error('非对象')
}
//第一层拷贝
newObj = isArray(obj)?[...obj]:{...obj};
//判断newObj中的每个value值是否是对象,如果是继续拷贝
//由于需要完全拷贝包括不可枚举属性,因此使用Rflect.keys而不用Object.keys()
Reflect.ownKeys(newObj).forEach(key=>{
//如果Obj[key]使对象则对newObj[key]保存的是在堆内存中的地址,需要重新赋值,
newObj[key] = isObject(obj[key])?deepClone(obj[key]):obj[key]
})
return newObj
}
关于对象属性
- Reflect.ownKeys:只能own,不管能不能枚举,包括symbol属性
- Object.getOwnPropertyNames:只能own,不管能不能枚举,不包括symbol属性
- Object.getOwnPropertySymbols:只能own,不管能不能枚举,只能拿到symbol属性
- Object.keys:只能own,只能拿到枚举属性,不能拿到symbol属性
- for-in:可以拿到继承属性,只能可枚举的属性,拿不到symbol属性
数组
API
- 直接修改原数组的:push(), unshift(), pop(), shift(), splice(), reverse(), sort()
- 返回新数组的:concat(), slice()
- 返回字符串:join() //如果是字符串变数组用split()
- 位置或是否在数组内:indexOf() lastIndexOf() includes()
- 遍历方法:forEach()对所有元素执行一次,返回undefined, map()返回新数组, filter返回过滤成功的新数组,every()所有都满足返回true,some()有一个元素满足就返回true。
防抖和节流
防抖
在浏览器中如果出现要为频繁发生的时间(ex:滚动事件)绑定响应函数,那么需要写防抖函数。
思路:
- 如果在200ms内没有再次触发事件那么执行函数
- 如果在200ms内再次触发事件那么重新计时
window.onscroll = debounce(fn,200)
function debounce(fn,delay) {
//借助闭包,暴露出接口函数对函数内部的私有变量进行修改
let timer = null;
return function(){
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fn,delay);
}
}
防抖的定义:
- 短时间内连续触发的事件,某个时间期限内只触发一次。
节流
节流是指连续触发事件的话每过一定时间触发一次响应函数
思路:
- 设置valid,只有当valid是true时才触发函数,如果不是true则直接不执行了。
window.onscroll = throttle(fn,1000)
//定时器 尾执行
function throttle(fn,delay) {
//在节流函数内部创建私有变量
let valid = true;
let self = this;
let args = arguments;
//暴露的响应函数可以访问节流函数内部的私有变量valid
return function(){
//如果不生效,响应函数不执行
if (!valid) {
return false;
}
//将valid设置为false,隔一段时间valid才变成true并且执行响应函数
valid = false;
setTimeout(()=>{
valid = true;
fn.apply(args,self);
},delay)
}
}
//时间戳 首执行
function throttle(fn,delay) {
let previous = new Date();
let self = this;
let args = arguments;
return function () {
let cur = new Date();
if (cur-previous>delay) {
fn.apply(args,self)
}
}
}
//时间戳+定时器 首尾执行
function throttle(fn,delay) {
let previous = new Date();
let self = this;
let args = arguments;
let timer = null;
return function () {
let cur = new Date();
if (cur-previous>delay) {
fn.apply(args,self);
}else {
timer = setTimout(()=>{
timer = null;
fn.apply(args,self)
})
}
}
}
this
四种绑定规则
默认绑定(全局环境以及函数直接调用绑定到window)
- 全局环境,this绑定到window
- 函数直接调用,this绑定window
- 嵌套函数直接调用,this绑定到window
- 闭包中的this绑定到window
- 立刻执行函数,this绑定到window
隐式绑定
方法调用
显示绑定
通过call apply bind调用
new绑定
注意:new方式绑定的优先级最高,然后是bind函数,然后是方法调用,并且箭头函数的this不可换绑
闭包与作用域链
闭包
- 闭包就是可以读取到其他函数内部变量的函数,其实就是用到了作用域链向上查找的特点。
- 作用:可以让变量私有化,闭包只能读取到其他函数内部的变量,不可以修改,保证了数据安全性,并且让变脸一直保存到内存中。
执行环境
执行环境定义了变量或函数有权访问其他数据,决定了各自的权限。全局执行环境是window,则所有全局变量和函数都是window的属性和方法。每个执行环境有与之相关的变量对象,执行环境中所定义的所有变量和函数都保存在这个对象中。
作用域链
代码执行的过程中会创建变量对象的一个作用域链,作用域链的前端是当前执行环境,作用域链的尾端是全局执行环境。作用域链可以保证执行环境内有权访问的变量是有序的,作用域链只能向上访问。
原型链
什么是原型链
每个构造函数都有Prototype属性,这个属性对象有constructor属性指向构造函数。
通过new构造的对象拥有__proto__属性,这个属性指向原型对象(Prototype对象)
当在某个对象中能够获取变量或者方法时,如果该对象没有,则去该对象的原型中查找。
怎么判断是对象中的属性还是对象原型中的属性
hasOwnProperty返回true则是对象中的属性,如果返回false并且属性 in 对象返回true则是原型上的属性,返回false说明没有这个属性。
继承
继承的原理
每个函数都有一个原型对象,这个对象用来储存这个函数所创建的实例所共有的属性和方法。在读取某个对象属性时,从实例开始,如果实例中没有则去原型中寻找。通过实例只能访问原型对象的值,不要进行修改(除非大家都共享修改后的值)。
原型链继承
可以继承父类原型的属性和方法但不能继承父类构造函数上的属性和方法
缺点:1、新实例无法向父类构造函数中传参2、所有新实例虽然继承了父类实例的属性但是是共享的
var Person = function (name) {
this.name = name
}
Person.prototype.age = 10
function Per = function () {
this.name = 'ker'
}
Per.Prototype = new Person();//继承父类实例属性
借用构造函数继承
虽然继承了父类构造函数的属性并且不共享,但是还没有继承父类原型的属性
var Per = function() {
Person.call(this,'jer')
}
组合继承(借用构造函数和原型链)
优点:子类实例单独继承父类实例属性,共享继承父类原型属性
缺点:调用两次构造函数,子类构造函数代替原型上的父类构造函数
var Per = function() {
Person.call(this,'jer')
}
Per.prototype = new Person();
原型式继承
共享继承了父类的实例属性和原型属性
function content(obj) {
function F(){}
F.prototype = obj;
return new F();
}
var f = content(new Person());
寄生继承
和原型式继承差不多,套了一层
function content(obj) {
function F(){}
F.prototype = obj;
return F
}
function subobject(obj) {
var f = content(obj);
f.age = 10;
return f;
}
var f = subobject(new Person());
寄生组合式继承
子类实例共享继承父类原型属性,单独继承父类实例属性
//法一:自己写一个复制函数
function content(obj) {
function F(){}
F.prototype = obj;
return new F();
}
con = content(Person.prototype);//con共享继承父类原型属性
function Per() {
Person.call(this); //子类实例单独继承父类实例属性
}
Per.prototype = con; //子类实例共享继承父类原型属性
con.constructor = Per;
//法二:运用Object.create
function Per() {
Person.call(this);
}
Per.prototype = Object.create(Person.prototype,{
constructor:{
value:Per,
enumerable:false,
writable:false,
configurable:true
}
})
class继承
class Parent {
constructor (value) {
this.val = value
}
getValue () {
return this.val
}
}
class Child extends Parent {
constructor(value,bar) {
super(value) //类似Parent.call(this.value)
this.bar = bar
}
getbar(){
return this.bar
}
}
DOM
创建元素
- createHTML()
- appendChild()
- insertBefore(insertdom,chosendom)
获取元素
根据元素类型
- document.getElementById
- document.getElementsByTagName
- document.getElementsByClassName
- document.getElementsByName(根据name属性获取)
- document.querySelector
- document.querySelectorAll
根据关系树
- parentNode
- childNode
- firstChild
- lastChild
- nextSibling
- previousSibling
根据元素关系树
- parentElement
- children
- firstElementChild
- lastElementChild
- nextElementSibling
- previousElementSibling
如何给元素添加事件
- 在HTML元素中绑定事件οnclick=show()
- 在dom中绑定
- addEventListener(‘click’,show,false/true)(false:事件冒泡,true:事件捕获)
事件冒泡和事件捕获
事件是先捕获后冒泡,一般来说绑定事件默认在冒泡阶段触触发响应函数
事件捕获:从外部元素到内部元素触发
事件冒泡:从内部元素到外部元素触发
阻止事件冒泡 取消默认事件 阻止默认行为
- event.stopPropagation()
- event.preventDefault()
- return false(包括stopPropagation preventDefault)
获取DOM节点get系列和query系列那种性能好
get方式性能更好,因为可以直接返回指针,而query方式会对元素的进行遍历
ES6
Proxy
Proxy是ES6中新增的功能,可以用来自定义对象中的操作。
let p = new Proxy(target,handler);
target是需要代理的对象,handler用来自定义对象中的操作,比如可以自定义set和get操作