更新中,以下整理于CSDN、掘金等博客以及b站视频
BFC,即Box(CSS布局的对象和基本单位)
基本类型:string number boolean null undefined Symbol(es6,表示唯一标识符,同一变量生成的值也不相同)BigInt(新增,末尾加n,与普通数字“”为true,严格模式“=”为false)
引用类型:function(_photo_Function.prototype)
object(普通对象,数组对象,正则对象,日期对象,Math数学函数对象)
判断类型:
typeof string number boolean undefined bigInt sysmbol function 返回各自类型(返回字符串)
null object array 和函数实例(new+函数)返回object
instanceof判断该对象是谁的实例,obj1 instanceof obj2,(返回布尔值)
constructor检测构造函数
Object.prototype.toString.call检测数据类型
<!--基本类型-->
console.log(typeof(33))//number
console.log(typeof('aa'))//string
console.log(typeof(null))//object
console.log(typeof(undefined))//undefined
console.log(typeof(true))//boolean
console.log(typeof(BigInt(33333333333333333333333333)))//bidInt
console.log(typeof(33333333333333333333333333333333333n))//bidInt
console.log(typeof(Symbol(3)))//sysmbol
<!--引用类型-->
console.log(typeof([1,2,3]))//object
console.log(typeof({'name':'aaa'})//object
console.log(typeof(function a(){}))//function
console.log([] instanceof Array)//true
console.log([] instanceof Object)//true数组也是一种对象
详见 JS中的数组方法
js的内存生命周期
(1)定义变量时完成了分配内存
(2)使用值的过程实际上是对分配内存进行读取和写入的操作,
(3)内存释放依赖GC机制(高级语言解释器嵌入“垃圾回收器”)
程序运行的时候,需要内存存放数据
系统会划分出两种不同的空间:堆(heap)和栈(stack)
V8是使用最广泛的JavaScript引擎,它被 使用在node.js和chrome浏览器中
这个引擎包含两个组件
(1)内存堆——这个是内存分配发生的地方
(2)调用堆栈——这是JS代码执行的数据帧所在的地方
基本数据类型的值会存储在当前作用域下
var a=12;
1)首先开辟一个空间,存储12
2)在当前作用域中声明一个变量a
3)让声明的变量和存储的a进行关联,把存储的12赋值给a(定义)
基本数据类型是按照值来操作的,把原有的值复制一份放在新的空间或位置上,和原来的值没有关系;
引用数据类型要先开辟一个空间
var obj={n:10}
1)首先开辟一个新的内存空间,把对象中的键值对依次存储起来,这个空间有一个16进制地址;
2)声明一个变量
3)让变量和空间地址关联在一起(把空间地址赋值给变量)
引用类型不是按照值来操作的,它操作的是空间的引用地址,把原来空间地址赋值给新的变量,但是原来的空间没有被克隆,还是一个空间,遮掩就会出现多个变量关联相同的空间,相互之间会存在影响;
函数被调用时,js引擎会为全局执行上下文和调用栈腾出空间
调用堆栈
JavaScript是一种单线程编程语言,它只有一个Call Stack(调用堆栈)
(1)所有同步任务都在主线程上执行,形成一个执行栈;
(2)主线程之外,还存在“任务队列”,只要有异步任务有了运行结果,就在“任务队列”之中放置一个事件。
(3)一旦“执行栈”中所有的同步任务执行完毕,系统就会读取“任务队列”,对应的异步任务,结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步
即:调用栈中的同步任务被执行完,栈就被清空,主线程栈空闲,就会去任务队列中按顺读取下一个任务放到栈中执行,每次栈内被清空,就会去读取任务队列中有没有任务,有就读取执行,一直循环读取-执行操作。=>event-loop(一个程序结构,用于等待和发送消息和事件)
所有任务分成两类:
同步任务(aynchronous):在主线程上排队执行的任务,前一个任务结束,才执行后一个任务;
异步任务(asynchronous):不进入主线程,而进入“任务队列”,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
异步任务又分两类
宏任务(macrotask):script(整体代码),setTimeout,setInterval,setImmediate(node独有),requestAnimationFrame(浏览器独有),I/O ,UI rendering(浏览器独有);
微任务(microtask):process.nextTick(node独有), Promise ,Object.observe , MutationObserve
同一次事件循环中,微任务永远在宏任务之前进行
console.log(a,b);//undefined undefined
var a = 12,b = 12;
function(){
console.log(a,b);//undefined 12
var a = b = 13;//相当于a声明并赋值,b只是赋值
console.log(a,b);//13 13
}
fn();
console.log(a,b);//12 13
第一行,声明a,b,fn,因此输出undefined (已声明,未赋值);
第二行,为a,b赋值
第三行,已经声明和定义函数,调至第8行
第八行,开始建立局部作用域,回到第四行
第四行,变量提升a,执行输出,此时a未赋值,是undefined,b在函数内未声明,向上级作用域找,如果一直不是,则继续向上直到window**=>作用域链**
第五行,给局部变量和全局变量赋值13
第九行,此时a和b都是全局变量,a未改变,但是b的值被改为13
console.log(a);//undefined
if(‘a’ in window){
var a= 12;
console.log(a);//12
}
(3)作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行;
(4)块语句指的是大括号“{}”之间的,if/switch条件语句或for/while循环不会创建一个新的作用域;
(5)块级作用域:通过新增命令let / const声明,所声明的变量在指定块的作用域外无法被访问;在一个函数内部/在一个代码块(一对花括号内部)=>不会变量提升/禁止重复声明/for循环中妙用
function fn1(){}
var fn1=function(){}
var fn1=function aaa(){}
var f=new Function('参数1','参数2',……,'函数体')
(function(){})();
(function fn1(){})();
var obj={
func1:function(){
console.log('aaa');
}
}
obj.func1();
function Star(){};
new Star();
btn.onclick=function(){}
setInterval(function(){},1000)
闭包是典型的高阶函数
作用:(1)获取函数内部的变量(延伸了变量的作用范围);(2)让这个变量的值始终保持在内存中(不会被垃圾回收机制回收)
注意:(1)闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。解决办法:在退出函数之前,将不使用的局部变量全部删除;
(2)闭包会在父函数内部,改变父函数内部变量的值。所以,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作私有属性,一定要细心
function fn(){
var num=10;
function fun(){
console.log(num);
}
return fun;
}
var f=fn();
f();
(1)循环注册点击事件
//点击一系列li标签的index顺序
var lis=document.querySelector(".nav").querySelector('li');
//1、利用动态添加属性
for(var i=0;i<lis.length;i++){
lis[i].index=i;
lis[i].onclick=function(){
console.log(this.index);//注意不能直接打印i,会一直输出4
}
}
//2、利用闭包
for(var i=0;i<lis.length;i++){
//立即函数也成为小闭包
(function(j){
lis[j].onclick=function(){
console.log(j);
}
})(i);
}
(2)循环中的setTimeout
//3秒中打印li中元素
var lis=document.querySelector(".nav").querySelector('li');
for(var i=0;i<lis.length;i++){
setTimeout(function(){
console.log(i);//3秒后打印4
console.log(lis[i].innerHTML);//报错,定时器中的任务是异步任务,最后i变成4,li中没有lis[4]
})
}
//小闭包
for(var i=0;i<lis.length;i++){
(function(i){
setTimeout(function(){
console.log(lis[i].innerHTML);
})
})(i)
}
(3)计算打车价格
//起步13(3公里),之后每多一公里增加5块
//有拥堵情况,总价格多收取10元
var car=(function(){
var start=13;//起步价
var total=0;//总价
return{
price:function(n){
if(n<=3){
totla=13;
}else{
total=start+(n-3)*5
}
return total;
},
block:function(flag){
return flag?total+10:total;
}
}
})
car.price(9);
car.block(true);
var name="the window";
var object={
name:"my object",
getNamefunc:function(){
return function(){
return this.name;
}
}
}
console.log(object.getNamefunc()())//the window
//=>相当于
var fn=object.getNamefunc();
//即
var fn=function(){
return this.name;
}
fn();//此时调用fn的是window,this指向window,没有用到闭包
var name="the window";
var object={
name:"my object",
getNamefunc:function(){
var that=this;
return function(){
return that.name;
}
}
console.log(object.getNamefunc()())//my object
//=>相当于
var fn=object.getNamefunc();//object调用函数getNamefunc(),先执行了var that=this(此时this指向object,赋给了that)
//即
var fn=function(){
return that.name;
}
fn();//此时有闭包产生
栈内存(执行上下文)
一般情况下,函数执行完,所形成的上下文被出栈释放掉
特殊情况下:当前上下文中的某些内容被上下文以外的事物占用了,此时不能出栈释放;
全局上下文:加载页面创建的,也只是有页面关闭才会被释放
堆内存
引用计数(以IE为主):在某些情况下回导致计数混乱,这样会造成内存不能被释放掉(内存泄漏);
检测引用(以谷歌为主):浏览器在空闲时候会依次检测所有的堆内存,把没有被任何事物占用的内存释放掉,以此来优化内存;
每一个上下文代码执行的时候,可能都会创建变量,所以在每一个上下文中都会有一个存储变量的空间VO
变量对象:存放当前上下文的变量
全局称为VO
私有上下文中称为AO
惰性函数表示函数执行的分支只会在函数第一次调用的时候执行。
如果一个函数需要判断场景去调用不同方法,可以避免重复进入函数内的if判断,也就是if判断只进行一次,之后函数就会被分支里的代码替换掉。
应用:
如dom添加监听事件时,有两种,需要判断是否ie浏览器
普通写法:
function addEvent(type, element, fun) {
if (element.addEventListener) {
element.addEventListener(type, fun, false);
}
else if(element.attachEvent){
element.attachEvent(‘on‘ + type, fun);
}
else{
element[‘on‘ + type] = fun;
}
}
这种写法每一次都要执行if语句判断是否符合;
惰性函数法
函数重写(当有同名函数时,会被替换为后执行的)
function addEvent(type, element, fun) {
if (element.addEventListener) {
addEvent = function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if(element.attachEvent){
addEvent = function (type, element, fun) {
element.attachEvent(‘on‘ + type, fun);
}
}
else{
addEvent = function (type, element, fun) {
element[‘on‘ + type] = fun;
}
}
return addEvent(type, element, fun);
}
柯里化函数是高阶函数的一种
//普通函数
function add(x,y){
return x+y;
}
//currying后
function curryingAdd(x){
return function(y){
return x+y;
}
}
add(1,2);
curryingAdd(1)(2);
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true
// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
上面的正则校验,如果有很多地方需要校验是否含有数字,将第一个参数reg进行复用,可以直接调用hasNumber,hasLetter函数。
(2)提前确认
(3)延迟执行
Function.prototype.bind=function(context){
var _this=this;
var args=Array.prototype.slice.call(arguments,1)
return function(){
return _this.apply(context,args)
}
}
js中经常使用的bind,实现机制就是curring
function curry(fn,args){
var _this=that;
var len=fn.length;
var args=args||[];
return function(){
var _args=Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args,_args);
//如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if(_args.length<len){
return curry.call(_this,fn,_args);
}
return fn,apply(this,_args);
}
}
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
闭包是典型的高阶函数
<script>
function fn(callback){
callback&&callback();//回调函数
}
fn(function(){alert('hi')})
</script>
<script>
function fn(){
return function(){}
}
fn();
</script>
此时fn就是一种高阶函数
函数也是一种数据类型,同样也可以作为参数,传递给另外一个参数使用,最典型的就是作为回调函数。
正则表达式是用于匹配字符串中字符组合的模式
字符串的正则方法
match()
replace()
search()
split()
^ 匹配开始
$ 匹配结束
(2)字符类
[abc] 包含a/b/c任一项即可
^ [abc]$ 三选一,只有是a,或者b,或者c
[^abc] 取反,不能包含a/b/c
(3)量词符
*0次或多次
+1次或多次(至少一个)
?0次或1次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次
单例模式
单例模式是最简单的模式之一,一个类只返回一个实例,一旦再次调用就直接返回,目的在于解决一个全局使用的类频繁地创建与销毁
适配器模式
代理模式
策略模式
发布-订阅者模式
装饰器模式
————————————
//创建类
class Star{
//构造函数
constructor(name){
this.name=name;
this.age=15;
}
//添加方法
sing(){
console.log(this.name+'唱歌');
}
}
//实例化
var tmp=new Star('LiMing');
console.log(tmp.name)//LiMing
tmp.sing();//调用方法,对象.方法名()
//子类继承父类
class Son extends Star{
constructor(){
this.age=super(age);
}
}
//调用子类
var tmpSon=new Son();
console.log(this.age);
!注意点:
类的本质:就是个函数,可以简单理解为类就是构造函数的另外一种写法;以下构造函数的特性类都有,只是更好理解,更符合面向对象,因此类是一种语法糖(更便捷的写法)
ES6之前通过 构造函数+原型实现面向对象 编程
(1)构造函数有原型对象prototype;
(2)构造函数原型对象prototype 里面有 constructor 指向构造函数本身;
(3)构造函数可以通过原型对象添加方法;
(4)构造函数创建的实例对象有_proto_原型链指向 构造函数的原型对象;
作用:共享方法,可以把共享的不变的方法放在prototype对象里,这样对象的实例可以共享这些方法;
对象原型_proto_ 只是指明一条线路,不能够赋值
对象都会有一个属性 _proto_指向构造函数的prototype原型对象,之所以可以使用prototype原型对象的属性和方法,就是因为对象有_proto_原型的存在;
constructor 只有原型对象有
console.log(Star._proto_===Object.prototype);//true
对象中都有一个属性_proto_,它指向上一级构造函数的prototype,直至Object._proto_为null,一层一层查找叫做原型链
按照原型链方式的查找规则
(1)当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性;有的话优先选中自身的值,就近原则;
(2)如果没有就查找它的原型链(也就是_proto_指向的prototype原型队象);
(3)如果还没有就查找原型对象的原型;
(4)一次类推直到找到Object为止(null);
(5)_proto_对象原型链的意义在于为对象原型查找机制提供一个方向,或者说一条路线。
function fn(){
console.log('aaa');
console.log(this);
//此时this指向window
}
fn.call();//打印出aaa
function fn(x,y){
console.log('aaa');
console.log(this);
//此时this指向对象o
console.log(x+y)
}
var o={
name:'LiMing'
}
fn.call(o,1,2);
fn.apply(thisArg,[argsArray])
//thisArg:在fun函数运行时指定的this值
//argsArray:传递的值,必须包含在数组里面
//返回的值就是函数的返回值,因为它就是调用函数
var arr=[1,55,44,212,8478];
Math.max.apply(Math,arr)//不需要改变this则重新指回Math
fn.bind(thisArg,arg1,arg2,……)
//thisArg:在fun函数运行时指定的this值
//arg1,arg2:传递其他参数;
//返回由指定的this值和初始化参数改造的原函数拷贝
function fn(x,y){
console.log('aaa');
console.log(this);
//此时this指向对象o
console.log(x+y)
}
var o={
name:'LiMing'
}
var f=fn.bind(o,1,2);//此时只是改变了this指向,没有调用函数
f();//调用函数
典型应用,有的函数不需要立即调用,又想改变函数内部this指向时应bind;
var btn=document.querySelector('button');
btn,onclick=function(){
this.disabled=true;//按钮点击时候禁用
setTimeout(function(){
this.disabled=true;//直接调用时,this指的window,window中没有disabled属性
}.bind(this),1000)//改变this指向,也可以用btn(不推荐),此时指向btn这个对象
//如果用call或者apply会立即调用
}
bind()可以代替全局变量存储this的时候
不同点:
(1)call和apply会调用函数,并且改变函数内部this指向;
(2)call和apply传递的参数不一样,call传递参数aru1,aru2形式,apply必须数组形式;
(3)bind不会调用函数,可以改变函数内部this指向
function Star(uname,age){
this.uname=uname;
this.age=age;
this.sing=function(){
console.log(this.uname+'会唱歌')
}
}
//实例化/创建对象
var tmp=new Star('LiMing',19);
tmp.sing();
console.log(tmp);
运行结果
构造函数中的属性和方法称为成员,成员可以添加
分为两种
(1)实例成员:构造函数内部通过this添加的成员,只能通过实例化的对象来访问;
function Star(uname,age){
this.uname=uname;
this.age=age;
this.sing=function(){
console.log(this.uname+'会唱歌')
}
}
//实例化/创建对象
var tmp=new Star('LiMing',19);
console.log(tmp.uname);//LiMing
console.log(Star.uname);//undefined
(2)静态成员:在构造函数本身添加的成员,静态成员只能通过构造函数来访问,不能使用对象访问。
function Star(uname,age){
this.uname=uname;
this.age=age;
this.sing=function(){
console.log(this.uname+'会唱歌')
}
}
Star.sex="男";
//实例化/创建对象
var tmp=new Star('LiMing',19);
console.log(tmp.sex);//undefined
console.log(Star.sex);//男
此时tmp和tmp2对象会分别开辟空间存放sing方法,同样的函数开辟了不同的空间,内存浪费
function Star(uname,age){
this.uname=uname;
this.age=age;
this.sing=function(){
console.log(this.uname+'会唱歌')
}
}
//实例化/创建对象
var tmp=new Star('LiMing',19);
var tmp2=new Star('John',23);
console.log(tmp.sing===tmp2.sing)//false,地址不同
function Star(uname,age){
//构造函数内部
this.uname=uname;
this.age=age;
}
//使用原型对象prototype,将共享的方法放入
Star.prototype.sing=function(){
console.log('我会唱歌')
}
//实例化/创建对象
var tmp=new Star('LiMing',19);
var tmp2=new Star('John',23);
console.log(tmp.sing===tmp2.sing)//true,地址相同
实例对象能调用prototype里的方法,是因为每一个实例对象中有_proto_属性(对象原型),指向构造函数的原型对象prototype
=>方法查找规则:
(1)首先先看tmp对象上是否有sing方法,如果有就执行这个对象上的sing();
(2)如果没有sing这个方法,因为有_proto_的存在,就去构造函数原型对象prototype上去查找sing();
一般情况下,公共属性定义到构造函数里面(比如上面的uname,age),公共的方法放到原型对象上
constructor 构造函数
对象原型(proto)和原型对象(prototype)里面都有一个constructor属性,成为构造函数,因为它指回构造函数本身
很多情况下,我们需要手动的利用constructor这个属性指回原来的构造函数
当方法比较多时,我们可能用对象方式来定义方法
//赋值,此时Star里面的constructor被覆盖了
Star.prototype = {
sing:function(){
console.log('我会唱歌');
}
dance:function(){
console.log('我会跳舞');
}
}
手动返回constructor构造函数
Star.prototype = {
constructor:Star,//手动返回constructor
sing:function(){
console.log('我会唱歌');
},
dance:function(){
console.log('我会跳舞');
}
}
function test(){
this.x=1;//这里的this就是window
console.log(this.x);//1,相当于声明了个全局变量
}
test();//等价于window.test()
var x=3;
function test(){
console.log(this.x)//1,此时this指的是对象O
console.log(x);//3 此时是全局变量x,相当于window.x
}
var o={
x:1,
m:test
};
o.m();
function test(){
this.x=3;
console.log(this);//{text:3}
}
var o=new test();
console.log(o.x);//3
(1)在构造函数中,里面的new指向的是实例对象;
(2) 原型对象函数里面的this,指向的是实例对象;
!注意如果返回值是一个对象(除null外),那么this指向返回的那个对象,如果返回值不是对象那么this还是指向函数实例
var x=0;
function hello(){
console.log(this.x)
}
var h={};
h.x=1;
h.m=hello;
h.m.apply();//0
h.m.apply(h);//1
面试题:
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';//obj调用了dose方法,将obj的name改为dose
return function(){//内部匿名函数,this指向window
return this.name;
}
}
}
console.log(obj.dose().call(this))//global
console.log(obj.name);//dose
(5)作为数组的一个元素,通过数组下标调用时,this指向这个数组
(6)函数作为window内置函数的回调函数调用时:this指向window.setTimeout(),setInterval()等,如果是匿名函数,指向window
(7)严格模式下(设置了“use strict”),this指向undefined
function hello() {
'use strict';
console.log(this); // undefined
}
hello();
浅拷贝只是拷贝一层,更深层次的对象级别的只拷贝引用
当修改其中任意一个数据都会影响另一个
var obj={
id:,
name:'aaa',
msg:{
age:18
}
}
//1、for in 拷贝
var o={},
for(var k in obj){
o[k]=obj[k];//msg中的嗯对象只是拷贝的一个引用地址
}
//2、es6语法槽拷贝Object.assign(target,...sources)
Object.assign(o,obj)
//3、es6中结构赋值
var o={...obj}
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
深拷贝拷贝多层,每一级别的数据都会拷贝
内存空间重新复制了一份
//1、JSON.stringify
//当对象中包含正则、函数、undefined时会失败
JSON.parse(JSON.stringify(obj))
//2、函数递归
function deepCopy(newobj,oldobj){
for(var k in oldobj){
//获取属性值 oldobj[k]
var item=oldobj[k];
//判断是否是数组
if(oldobj[k] instanceOf Array){
newobj[k]=[];
deepCopy(newobj[k],item);
}
//判断是否是对象
else if(oldobj[k] instanceOf Object){
newobj[k]={};
deepCopy(newobj[k],item);
}else{
//简单数据类型
newobj[k]=item;
}
}
}
deepCopy(o,obj)
es6之前不提供extends继承,可以通过构造函数+原型对象模拟实现继承,称为组合继承;
借用构造函数继承父类型属性
核心原理:通过call()把父类型的this指向子类的this,可以实现子类型继承父类型属性
//父构造函数
function Father(uname,age){
//this指向父构造函数的对象实例
this.uname=uname;
this.age=age;
}
//子构造函数
function Son(uname,age){
//this 指向子构造函数的对象实例
//通过call改变this的指向
Father.call(this,uname,age);
}
//实例化一个子构造函数对象
var son=new Son('LiMing',16);
console.log(son);
子构造函数里面继承了父构造函数的两个实例
继承父构造函数里面的方法
//父构造函数
function Father(uname,age){
//this指向父构造函数的对象实例
this.uname=uname;
this.age=age;
}
//父亲实例对象里面的方法
Father.prototype.money=function(){
console.log('父亲的钱')
}
//子构造函数
function Son(uname,age){
//this 指向子构造函数的对象实例
//通过call改变this的指向
Father.call(this,uname,age);
}
//子构造函数继承父构造函数
Son.prototype=Father.prototype;
//实例化一个子构造函数对象
var son=new Son('LiMing',16);
console.log(son);
此时子构造函数里继承了父构造函数的方法
但是,存在一个问题,此时子构造函数和父构造函数实际指向同一个地址,如果改变子构造函数,同时也会改变父亲(浅拷贝)
//给子构造函数添加一个方法
Son.prototype.score=function(){
console.log('孩子成绩是90');
}
console.log(son);
console.log(Father.prototype);
父构造函数里面也有score方法
因此不能直接赋值,通过构造函数给子构造函数赋值
//实际是创建了一个Father的实例对象,使Son的原型对象指向Father的实例对象
Son.prototype=new Father();
Son.prototype.score=function(){
console.log('孩子成绩是90');
}
console.log(son);
console.log(Father.prototype);
子继承了父的money方法,但是父里面没有子的score方法
如果利用对象的的方式改变了原型对象,要利用constructor指向回来
Son.prototype=new Father(){
constructor:Son,
};
递归函数:函数内部可以调用其本身,这个函数就是递归函数
递归很容易出现“栈溢出”(死循环),因此必须加退出条件,return;
应用
(1)利用递归函数求1~n的阶乘1 * 2 * 3 * 4*……n
function fn(n){
if(n==1){
return 1;
}
return n*fn(n-1)
}
fn();
(2)斐波那契数列
function feibonacci(n){
if(n<=0){
return 0;
}
if(n===1||n===2){
return 1;
}
return f(n-1)+f(n-2);
}
(3)根据id返回对应的数据对象
var data=[{
id:1,
name:'aaa',
goods:[{
id:l1,
gname:'hhh'
},
{
id:l2,
gname:'lll'
}
]
},
{
id:2,
name:'bbb',
}
]
function getId(json,id){
json.forEach(function(item){
if(item.id==id){
console.log(item)
o=item;
}else if(item.goods&&item.goods.length>0){
getId(item.goods,id)
}
})
return o;
}
array.forEach(function(vaue,index,arr){
})
//value:数组当前项的值
//index:数组当前项的索引值
//arr:数组对象本身
(2) map()
(3)filter() 筛选数组 return回来
filter()方法创建一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
它直接返回一个新的数组
array.filter(function(value,index,arr){
})
//value:数组当前项的值
//index:数组当前项的索引值
//arr:数组对象本身
(4)some() 查找数组中是否有满足条件的元素,有一个就不再执行循环
some()方法用于检测数组中的元素是否满足指定条件
返回值是布尔值,有则返回true,没有则返回false
array.some(function(value,index,arr){
})
//value:数组当前项的值
//index:数组当前项的索引值
//arr:数组对象本身
(5)every()
str.trim()
Object.defineProperty() 定义对象中新属性或修改原有的属性
Object.defineProperty(obj,prop,descriptor)
//obj:必需,目标对象
//prop:必需,需定义或修改属性的名字
//descriptor:必需,目标属性所拥有的特性
descriptor以对象{}书写,有以下几种属性
(1)value:设置属性的值,默认为undefined;
(2)writable:值是否可以重写,true|false 默认为false;
(3)enumerable:目标属性是否可以被枚举(是否能遍历),true|false 默认为false;
(4)configurable:目标属性是否可以被删除或是否可以再次修改特性 true|false 默认为false
例:
var obj={
name:'LiMing',
age:16
}
Object.defineProperty(obj,'sex',{
value:'男';//添加了sex
})
Object.defineProperty(obj,'age',{
value:18;//修改了了sex
})
console.log(obj);
Object.defineProperty(obj,'name',{
writable:false;//不允许修改,true 可修改
})
let
ES6中新增的用于变量声明的关键字
(1)let声明的变量只在所处于的块级有效(大括号内有用)
(2)不存在变量提升,必须先声明再使用
const
声明常量,常量就是值(内存地址)不会变化的值(js不需要实时监控,效率比较高)
(1)具有块级作用域
(2)声明常量时必须赋值
用来简化函数定义语法的
()=>{}
(1)函数体只有一句代码,且代码的执行结果就是返回值,可以省略大括号
const sum=(n1,n2)=>{
return n1+n2;
}
//可以简化为
const sum=(n1,n2)=>n1+n2;
(2)如果形参只有一个,形参外侧的小括号可以省略
const fn=n1=>n1*2;
(3)箭头函数不绑定this关键字,箭头函数中的his,指向函数定义位置的上下文this
var age=100;
var obj={
age:20,
say:()=>{
console.log(this.age);
}
}
obj.say();//100 对象不能产生作用域,实际是window
let arr=[1,2.3];
let ary2[a,b,c]=arr;
console.log(ary2)//[1,2,3]
(2)对象解构
使用变量的名字匹配对象的属性
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
const sum=(...args)=>{
let total=0;
args.forEach(item=>total+=item);
return total;
}
console.log(sum(10,20,30));//60
console.log(sum(10,20));//30
(2)剩余参数和结构配合使用
let ary1=['aaa','bbb','ccc'];
let [s1,...s2]=ary1;
console.log(s1);//aaa
console.log(s2);//['bbb','ccc']
let ary=[1,2,3]
console.log(...ary)//1 2 3,逗号被当做参数分隔符
//相当于
console.log(1,2,3)
(2)应用于合并数组
//方法一
let ary1=[1,2,3];
let ary2=[4,5,6];
let ary3=[...ary1,...ary2];
console.log(ary3);[1,2,3,4,5,6];
//方法二
ary1.push(...ary2)
(3)将类数组或可遍历对象转换为真正的数组
var odivs=document.getElementByTagName('div');
var arr=[...odivs]
Array的扩展方法
let arrayLikes={
'0':'a',
'1':'b',
'2':'c',
length:3
}
let arr2=Array.from(arrayLikes);
console.log(arr2);['a','b','c']
(2)还可以接收第二个参数,作用类似于数组中的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组;
let arrayLikes={
'0':1,
'1':2,
'2':3,
length:3
}
let arr2=Array.from(arrayLikes,item=>item*2);
console.log(arr2);//[2,4,6]
let ary=[{
id:1,
name:'aaa'
},{
id:2,
name:'bbb'
}];
let target=ary.find((item,index)=>item.id==2);
console.log(target);//{id: 2, name: "bbb"}
let ary=[1,2,3,8,9];
let index=ary.findIndex((value,index)=>value>3);
console.log(index);//3
String 的扩展方法
let str="aaabbb!";
str.startsWith('a')//true
str.endsWith('!')//true
'abc'.repeat(3);//abcabcabc
Set 数据结构
类似于数组,但是成员的值都是唯一的,没有重复的值
const s=new Set();
console.log(s)
const arr=new Set([1,2,3,4,5])
let arr=[1,6,4,5,1,2]
let arr2=new Set(arr);
console.log(arr2);//{1, 6, 4, 5, 2}
实例方法:
(1)add(value):添加某个值,返回Set结构本身
(2)delete(value):删除某个值,返回一个布尔值,表示删除是否成功
(3)has(value):返回一个布尔值,表示是否为Set的成员
(4)clear():清除所有成员,没有返回值
遍历
forEach()方法,对每个成员执行遍历操作,没有返回值
Promise对象:代表了未来某个将要发生的事件(通常是异步)
可以解决回调地狱的问题,减少回调,把配置和结果回调分开
Promise只能通过resolve或者reject函数来返回,并使用then()或者catch()获取,不能在new Promise里面直接return,这样是获取不到Promise返回值的
//创建成功的promise
var p1=Promise.resolve('这里是成功的promise对象');
var p2=new Promise(function(resolve,reject){
resolve('这里是成功的promise对象');
})
console.log(p1);
p1.then(function(res){
console.log(res)
})
//创建失败的promise
var p1=Promise.reject('这里是失败的promise对象');
var p2=new Promise(function(resolve,reject){
reject('这里是失败的promise对象');
})
console.log(p1);
p1.catch(function(error){
console.log(error)
})
Promise.all([promise1,promise2,promise3]).then((values)=>{
console.log(values)
})
Promise.race([p1,p2,p3]).then((res)=>{
})
async
用来声明一个异步的function
await
用于等待一个异步任务完成的结果
迭代器是种特殊的对象,每一个迭代器都有一个next(),该方法返回一个对象,包括value和done属性
生成器是函数,用来返回迭代器
xss全称叫 Cross Site Script ,顾名思义就是跨站脚本攻击,XSS攻击是通过在网站注入恶意的脚本,然后借助脚本改变原有的网页,当用户访问网页时,对浏览器一种控制和劫持,XSS攻击主要分为以下几种类型。
Cookie设置HttpOnly /
SecureOnly(只允许https请求读取)和HostOnly(只允许主机域名与domain一致)
检查输入内容(对用户输入的所有内容进行过滤和检查)
比如是否包含一些特殊字符、<、>等,将它们准话为实体字符;
将不可信数据作为URL参数值时需要对URL编码(encodeURIComponent)
CSRF全称Cross Site Request Forgery ,跨站请求伪造,通过劫持受信的用户(已登录)向服务器发送非用户本身意愿的请求,以此来完成攻击,生活中用户很多验证信息都是存在于cookie中,不法分子可以利用用户自己的cookie来通过安全验证。
具备以下两个特点
mvc:视图(view)—>路由/控制器(controller)—>数据(model) 单向绑定
mvp:model<=>presenter(中间人)<=>view
mvvm:view<=>viewModel<=>model 双向绑定
这三者都是框架模式,它们的设计目标都是为了解决Model和View的耦合问题
MVC模式出现较早应用在后端,如SpringMVC、ASP.NET MVC等,在前端领域的早期也有应用。有点事分层清晰,缺点是数据流混乱、灵活性带来的维护性问题
MVP
MVVM不仅解决MV耦合问题,还同时解决了维护两者映射关系的大量繁杂代码和DOM操作代码,在提高效率
1、核心点:Object.defineProperty
2、默认vue在初始化数据时,会给data中的属性使用Object.defineProperty重新定义所有属性,获取和设置时添加监听(拦截)
vue
3、原理
1、使用函数劫持的方法,重写了数组的方法通过原型链把数组的方法进行重写(拦截七个:push、pop、shift、unshift、splice、sort、reverse)只要调用了这几个方法,就通知视图更新,对插入的数据再次观测
2、vue将data中的数组,进行了原型链的重写,指向了自己定义的数组原型方法,这样当调用数组api时,可以通知依赖更新,如果数组中包含着引用类型,会对数组中的引用类型再次进行监控。数组里的对象也会导致数组更新
1、vue是组件级更新。如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能考虑,vue会在本轮数据更新后,再去异步更新视图。
nextTick()方法主要是使用了宏任务和微任务定义了一个异步方法,多次调用nextTick方法会将方法存入队列中,通过这个异步方法清空当前队列,所以这个nextTick方法就是异步方法
1、computed watch method的区别
computed 和 method
method (方法):只要每次一变化就会重新的数据渲染 性能开销大
computed(计算属性):具备缓存,渲染的时候如果依赖的属性没有发生变化,就不会导致方法重新执行
computed 和 watch
内部都使用watcher
先拿到用户定义的事件(函数)
lazy
watch中的deep:true
函数每次执行都会返回全新的data实例
vue组件可能存在多个实例,如果使用对象形式定义data,则会导致他们共用一个data对象,那么状态将会变更将会影响所有组件实例,这是不合理的;采用函数定义,在initData时将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染。而在Vue根实例创建过程中则不存在限制,因为根实例只有一个。
对象是对于内存地址的引用。直接定义个对象的话,组件之间都会使用这个对象,这样会造成组件之间数据相互影响。组件就是为了抽离开来提高复用的, 如果组件之间数据默认存在关系,就违背了组件的意义。 函数 return 一个新的对象,其实还是为 data 定义了一个对象, 只不过这个对象是内存地址的单独引用了,这样组件之间就不会存在数据干扰的问题。
结论:v-for高于v-if
如果同时出现,每次渲染都会先执行循环再判断,无论如何都不可避免
改进:1、把v-if放在外面一层
2、把判断放在数据渲染之前
源码:src\core/vdom/patch.js-updateChildred()
1、key的作用主要是为了高效的更新虚拟dom。其原理是vue在patch过程中通过key可以精准的判断两个节点是否是同一个,从而避免频繁更新不同元素,是的整个patch过程更高效,减少dom操作量,提高性能。
2、另外,若不设置key还可能在列表更新时引发一些隐蔽的bug;(比如说应该更新的但是没有更新)
3、vue中在使用相同标签名元素的过渡切换时,也会使用到key元素,其目的也是为了让vue可以区分它们,否则vue只会在其内部属性而不会触发过渡效果。
非vue专用,虚拟dom都会使用
1、必要性 lifecycle.js-mountComponent()
组件中可能存在很多个data中的key的使用
2、执行方式,patch.js-patchVnode()
patchVnode是diff发生的地方,整体策略:深度优先,同层比较
3、高效性,patch.js-undateChildren()
//全局定义
Vue.component('comp',{
template:'this is a component'
})
//组件定义
<template>
<div>
this is a component
</div>
</template>
vue-loader会编译template为render函数,最终导出的依然是徐建配置对象(js对象)
- 优点:维护性、测试性、维护性
lifecycle.js-mountComponent()
组件、watcher,渲染函数、更新函数之间的关系
每一个组件都有一个watcher,组件中的数据发生变化,只会调用这个组件的渲染函数、更新函数
合理切割组件粒度,经常渲染的组件独立出来,效率更好
-组件化实现:
构造函数:src\core\global-api\extend.js
实例化及挂载:src\core\vdom\patch.js-createElm()
1、组件是独立和可复用的代码组织单元。组件系统是Vue核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用;
2、组件化开发能大幅提高开发效率、测试性、复用性
3、组件按分类有:页面组件、业务组件、通用组件
4、vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函数,它们基于VueComponent,扩展于Vue;
5、vue中常见的组件化技术有:属性prop,自定义事件、插槽等,它们主要用于组件通信、扩展等;
6、合理的划分组件,有助于提高应用性能;
7、组件应该是高内聚、低耦合
8、遵循单向数据流原则
vue性能优化方法
- 1、路由懒加载
const router=new VueRouter({
routes:[
{path:'/foo',component:()=>import('./Foo.vue')}
]
})
-
2、keep-alive缓存页面
-
3、使用v-show复用DOM
-
4、v-for遍历避免同时使用v-if
-
5、长列表性能优化
如果列表时纯粹的数据展示,不会有任何变化,就不需要做响应化
如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域内容
-
6、事件销毁
Vue组件销毁时,会自动解绑它的全部指定
-
7、图片懒加载 v-lazy
图片需要显示时才去加载
-
8、第三方插件按需引入、无状态的组件标记为函数式组件、子组件分割
-
双向数据绑定
-
template模板渲染语法和原理(vue-loader,虚拟DOM)
-
指令和自定义指令
-
methods computed watch filters
-
class/style
-
条件渲染和循环渲染
-
事件处理
-
表单处理
-
组件(属性)
-
ref
-
生命周期
-
插槽slot
-
transition
-
渲染函数和jsx
-
插件编写
-
混入
-
devtools
vue-router
- 基础知识
- 动态路由
- 编程式导航
- 命名路由和命名容器
- 导航守卫
- HASH/BRWSER路由
- 路由原理
##、## 单元测试
SSR服务器渲染nuxt.js
UI组件库
react
creat-react-app
- 配置
- 优化
react基础
- JSX语法(虚拟DOM)
- 状态
- 属性
- ref
- 组件
- 生命周期
- PureComponent
- Hooks
react-router-dom
redux
- redux
- react-redux
- 中间件
dva
umi
typescript
UI组件
SSR服务器渲染next.js
算法
- 数组去重
- 排序算法
- 递归调用
手写代码
防抖和节流
- 防抖:
将多次触发变成最后一次触发,在n秒内只能执行一次
function debounce(fn,wait){
let timer=null;
return function(){
let context=this;
let args=arguments;
clearTimeout(timer);
timer=setTimeout(function(){
fn.call(context,args)
},wait)
}
}
function fn(context,args){
console.log("防抖");
}
addEventListener('scoll',debounce(fn,1000))
- 节流:
将多次执行变成每隔一个时间点执行一次,特定的时间只执行一次
//时间戳版
function throttle(fn,wait){
let prev=Date.now();
return function(){
let context=this;
let args=arguments;
let now=Date.now();
if(now-prev>=wait){
fn.apply(context,args);
prev=Date.now();
}
}
}
function fn(context,args){
console.log("节流");
}
addEventListener('scroll',throttle(fn,1000));
//定时器版
function throttle(fn,wait){
let timeout;
return function(){
let context=this;
let args=arguments;
if(!timeout){
timeout=setTimeout(function(){
timeout=null;
fn.apply(context,args)
},wait)
}
}
}
浅拷贝和深拷贝
- 浅拷贝
let oldobj={
name:'aaa',
age:18,
goods:{
color:'red'
}
}
let newobj={};
//1、for in 循环
for(let k in oldobj){
newobj[k]=oldobj[k];
}
//2、es6解构赋值
newobj={...oldobj}
//3、es6assign
Object.assign(newobj,oldobj)
- 深拷贝
//1、JSON.stringify
//当对象中包含函数、正则、undefined时会失败
newobj=JSON.parse(JSON.stringify(oldobj));
//2、递归拷贝
function deepCopy(newobj,oldobj){
for(var k in oldobj){
let item=oldobj[k];
if(item instanceof Array){
newobj[k]=[];
deepCopy(newobj[k],item)
}else if(item instanceof Object){
newobj[k]={};
deepCopy(newobj[k],item)
}else{
newobj[k]=item
}
}
return newobj;
}
deepCopy(newobj,oldobj)
数组扁平化
let arr=[
[1,2,3],
[4,5,6],
[7,8,[9,10,[11,12],13]]
]
1、es6中flat()方法
arr=arr.flat(Infinity);
//括号中代表扁平化几级,默认1级,Infinity无限级
2、toString()方法
arr=arr.toString().split(',').map(item=>parseFloat(item))
//.map(item=>parseFloat(item))转换为数字
3、JSON.stringify()方法
arr=JSON.stringify(arr).replace(/(\[|\])/g,'').split(',').map((item)=>parseFloat(item))
4、join()方法
arr=arr.join(',').split(',').map((item)=>parseFloat(item))
5、some()方法判断,扩展运算符
while(arr.some(item=>Array.isArray(item))){
arr=[].concat(...arr)
}
6、递归。递归遍历每一项,若为数组则重新递归
function flat(arr){
var res=[];
res.map(item=>{
if(Array.isArray(item)){
res=res.concat(flat(item))
}else{
res.push(item);
}
})
return res;
}
单例模式
一个类有且只有一个实例(确保多次构造函数时,都返回一个实例)
即满足以下条件
function A(){
}
var a1=new A();
var b1=new A();
a1===b1//true
核心思路:每次调用构造函数,返回指向同一个对象的指针。即,只在第一次调用时创建新对象,之后调用返回该对象,即,缓存初次创建的变量对象海报
1、使用构造函数的静态属性
function A(name){
if(typeof(A.instance)==='Object'){
return A.instance;
}
this.name=name;
//缓存
A.instance=this;
return this;
}
2、利用闭包(当对象第一次被创建后,重写构造函数,在重写后的构造函数里访问私有变量)
function A(name){
var instance=this;
this.name=name;
//重写构造函数
A=function(){
return instance;
}
//方法一:原型链继承
A.prototype=this;
//方法二:直接指向旧的原型
A.prototype=this.constructor.prototype;
instance=new A();
//调整构造函数指针
instance.constructor=A;
return instance;
}
数组去重
let arr=[1,,null,1,2,'null',undefined,undefined,{},{},NaN,NaN,3,5,null,true,false,true,false]
1、利用es6中Set()方法(es6常用)
let array=new Set(arr);
let array=Array.from(new Set(arr));
let array=[...new Set(arr)]
//无法去掉空对象
2、for循环用splice
function unique(arr){
for(var i=0;i<arr.length;i++){
for(var j=i+1;j<arr.length;j++){
if(arr[i]==arr[j]){
arr.splice(j,1)
j--;//使用splice删除原数组后,数组会塌陷,给j--或创建一个新数组存放
}
}
}
return arr;
}
//NaN,{}没有去重,null和true直接消失
3、indexOf
function unique(arr){
var array=[];
for(var i=0;i<arr.length;i++){
if(array.indexOf(arr[i])==-1){
array.push(arr[i])
}
}
return array;
}
//NaN,{}无法去掉
4、sort()
function unique(arr){
arr=arr.sort();
var array=[arr[0]];
for(var i=0;i<arr.length;i++){
if(arr[i]!==arr[i-1]){
array.push(arr[i]);
}
}
return array;
}
//null,NaN,{}没有去重
5、利用对象属性不能相同
function unique(arr){
var array=[];
var obj={};
for(var i=0;i<arr.length;i++){
if(!obj[arr[i]]){
array.push(arr[i]);
obj[arr[i]]=1;
}else{
obj[arr[i]]++;
}
}
return array;
}
//"null"去掉了
6、利用includes
function unique(arr){
var array=[];
for(var i=0;i<arr.length;i++){
if(!array.includes(arr[i])){
array.push(arr[i]);
}
}
return array;
}
//{}没有去掉
7、hasOwnProperty
function unique(arr){
var obj={};
return arr.filter(function(item,index,arr){
return obj.hasOwnProperty(typeof item+item)?false:(obj[typeof item+item]=true)
})
}
//都去掉了
8、filter
function unique(arr){
return arr.filter(function(item,index,arr){
return arr.indexOf(item,0)===index;
})
}
//都去掉了
手写promise.all和promise.race
- promise.all
Promise.all = function (iterator) {
let count = 0//用于计数,当等于len时就resolve
let len = iterator.length
let res = []//用于存放结果
return new Promise((resolve,reject) => {
for(var item of iterator){
Promise.resolve(item)//先转化为Promise对象
.then((data) => {
res[count++] = data
if(count === len){
resolve(res)
}
})
.catch(e => {
reject(e)
})
}
})
}
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
- promise.race
Promise.race = function (iterators) {
return new Promise((resolve,reject) => {
for (const p of iterators) {
Promise.resolve(p)
.then((res) => {
resolve(res)
})
.catch(e => {
reject(e)
})
}
})
}
var promise1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then(function(value) {
console.log(value);
// Both resolve, but promise2 is faster
});
模拟实现new
/*
* 1.创建一个空对象
* 2.链接到原型
* 3.绑定this值
* 4.返回新对象
*/
// 第一种实现
function createNew() {
let obj = {} // 1.创建一个空对象
let constructor = [].shift.call(arguments)
// let [constructor,...args] = [...arguments]
obj.__proto__ = constructor.prototype // 2.链接到原型
let result = constructor.apply(obj, arguments) // 3.绑定this值
// let result = constructor.apply(obj, args)
return typeof result === 'object' ? result : obj // 4.返回新对象
}
function People(name,age) {
this.name = name
this.age = age
}
let peo = createNew(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)
实现call/apply/bind
call
Function.prototype.mycall = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let arg = [...arguments].slice(1)
let result = context.fn(...arg)
delete context.fn
return result
}
apply
Function.prototype.myapply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let result
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind
// 思路:类似call,但返回的是函数
Function.prototype.mybind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let arg = [...arguments].slice(1)
return function F() {
// 处理函数使用new的情况
if (this instanceof F) {
return new _this(...arg, ...arguments)
} else {
return _this.apply(context, arg.concat(...arguments))
}
}
}
模拟实现Object.create()
// 思路:将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
千分位分隔符
1、只适用于整数
//正则表达式
function format (num) {
var reg=/\d{1,3}(?=(\d{3})+$)/g;
return (num + '').replace(reg, '$&,');
}
function format(num){
num=num+'';//数字转字符串
var str="";//字符串累加
for(var i=num.length- 1,j=1;i>=0;i--,j++){
if(j%3==0 && i!=0){//每隔三位加逗号,过滤正好在第一个数字的情况
str+=num[i]+",";//加千分位逗号
continue;
}
str+=num[i];//倒着累加数字
}
return str.split('').reverse().join("");//字符串=>数组=>反转=>字符串
}
2、小数部分不变
let miliFormat = (() => {
let DIGIT_PATTERN = /(^|\s)\d+(?=\.?\d*($|\s))/g
let MILI_PATTERN = /(?=(?!\b)(\d{3})+\.?\b)/g
return (num) => num && num.toString()
.replace(DIGIT_PATTERN, (m) => m.replace(MILI_PATTERN, ','))
})()
在这里插入代码片
实现三角形
实现三栏布局/双栏布局