js

2021/4/25 18:28:24

本文主要是介绍js,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、哪些操作可能造成内存泄漏

javascript是一种基于原型的面向对象语言,其它语言则是基于类的面向对象。

1、意外的全局变量

function(){
    a=444; //a成为一个全局变量,不会被回收
}

2、闭包

因为内层哈数引用外层函数的局部变量,导致变量不会被回收,直到关闭页面。

3、没有清理的dom元素引用

4、定时器没有及时清除

5、缓存(因为缓存会不会被回收的,要做大小限制、及时清除)

6、死循环

二、闭包

1、什么是闭包

即函数外部可以访问函数内部的局部变量。

一个函数中return另一个函数,内层函数引用外层函数的变量,那么这个变量就不会被马上回收,而是被保存在内存中。

2、作用:

1、函数外部能够读取函数内部的变量,保护局部变量不会受到全局污染,利用代码的封装;

2、变量会被保存在内存中。

缺点:消耗内存,造成网页性能问题。

方法:退出函数之前,把不使用的局部变量置为null。

3、使用场景:
<!DOCTYPE html>
<html>
<head>
     <meta charset="UTF-8">
</head>
<body>
    <button>Button0</button>
    <button>Button1</button>
    <button>Button2</button>
    <button>Button3</button>
    <button>Button4</button>
</body>
</html>
<script>
var btns = document.getElementsByTagName('button');
for(var i = 0, len = btns.length; i < len; i++) {
    btns[i].onclick = function() {
        alert(i);  //每次都会弹出5,因为alert是被异步触发的,每次触发时,for循环早已结束
    }
}
</script>

 

解决方法:

1、采用立即执行函数创建作用域

for(var i=0;i<btns.length;i++){
    (function(){
        btns[i].onclick = function() {
        alert(i);  //依次弹出0、1、2、3、4,
    }
    })(i)
}

 

2.把var改为let

 

三、js的数据类型

1、基本类型:String 、Boolean 、Number 、Undefined 、Null 、Symbol(表示独一无二的值)

基本数据类型:按值存放,栈内存中,直接访问

堆(heap):堆是在程序运行时,申请某个大小的内存空间,动态分配。数据结构.像倒过来的树。 栈(stack): 先进后出的数据结构。像桶。系统分配。 队列(queue):先进先出。特殊线性表。

Undefined==Null

Undefined==false

Null=false

Undefined:变量被声明了,但没有被赋值

Null :Null 类型的值只有一个,即null,表示一个空对象引用

2、引用类型:Object (对象、数组、函数)

保存的是一个地址指针,堆内存,根据地址指针去获取数据。obj1和obj2如果指向同一个内存地址,那么修改其中一个,另一个也会受到影响。

数据类型转换:

 var arr = [undefined , true, 'world', 123 , null, new Object ,  function () {}]
  for( i = 0; i < arr.length; i ++) {
        console.log(typeof (arr[i]));
   }
      输出的结果为:undefined , boolean , string , number , object , object , function
      
      typeof() 弊端:null、object、[]返回的都是object

基本数据类型--名值都存在栈内存中。 引用数据类型--名在栈中,值存在堆内存中。栈内存会提供一个引用地址指向堆内存中的值。

 

四、深拷贝、浅拷贝

1、定义

深拷贝:拷贝后的数据不会影响原来的数据。(对基本数据类型的拷贝)

浅拷贝:拷贝的只是一个引用地址,修改拷贝后的数据会影响原来的数据。(对引用数据类型(Object、Array)的拷贝)。

浅拷贝是只复制一层对象的属性(Object.assign({},obj)),,而深拷贝则递归复制所有的层级。

2、如何实现深拷贝

1、JSON.parse(JSON.stringify())

let obj={name:'zs',sex:'男'};
let obj2=JSON.parse(JSON.Stringify(obj));
//先把obj转换为json字符串, 相当于基本数据类型的拷贝。
//缺点:obj中不能包含函数,因为JSON.parse、JSON.Stringify不能处理函数。

2、deepCopy (递归复制所有的层级)

a instanceof Object //true 判断a是不是Object的实例

function deepClone(obj){
    if(obj instanceof Object){
        //如果是个对象
        let tep={};
        for(var key in obj){
            if(obj.hasOwnProperty(key)){
                tep[key]=deepClone(obj[key])
            }           
        }
        return tep;
    }else if(obj instanceof Array){
        //如果是个数组
        let arr=[];
        for(let i=0;i<obj.length;i++){
            arr.push(deepClone(obj[i]));
        }
        return arr;
    }else if(obj instanceof Function){
        //如果是个函数
        return new Function('return '+obj.toString());
    }else{
        //如果是基本数据类型
        return obj;
    }
}
 

五、原型链

1、执行流程:

js去查找一个对象的属性时:

(1)先查找实例里的属性和方法,如果有就返回;

(2)若没有,就通过proto去到构造函数的原型对象(prototype)去找,没有的话就会返回undefined。

 

2、_ proto _属性和prototype属性的区别

prototype是构造函数的属性;

_ proto_ 是实例对象里的隐式属性,在new这个实例的时候, _proto _指向prototype所指的对象。

 

3、构造函数、原型对象和实例对象的关系

构造函数.prototype==原型对象;

原型对象.constructor==构造函数

构造函数.isPrototypeOf(实例对象) //判断某个实例是不是属于这个构造函数

function Person(name){}
    Person.prototype={
        constructor:Person,
        name:'ss',
        sayName:function(){
            console.log(33);
        }
    };
    var p1=new Person(); 
    var p2=new Person();
    console.log(p1.__proto__==Person.prototype); //true
    //prototype是p1和p2的共享的原型对象,p1和p2都有一个__proto__属性,指向Person的prototype
    console.log(Person.prototype);//Person
    console.log(Person.prototype.constructor);// ƒ Person(name){}
    //原型对象内部有一个指针(constructor属性)指向构造函数

 

六、继承

1、原型链继承:

将父类的实例作为子类的原型

function Father(){
     this.names = ["aa", "bb"]; //引用类型值
    this.say=function(){
        return this.name;
    }
}
Father.prototype={
    constructor:Father,
    hei:200,
    eat:function(){
        rturn '333'
    }
}
function Son(age){
    this.age=age;
}
​
Son.prototype=new Father();
var son1 = new Son();
son1.names.push("cc");
console.log(son1.names); //["aa","bb","cc"]
​
var son2 = new Son();
console.log(son2.names); //["aa","bb","cc"]

 

缺点:

1、不能给父类传参;

2、引用类型的属性被所有实例共享

 

2、借用构造函数继承
function Son(age){
    Father.call(this,age);
    this.age=age;
}

特点:

1、可以传参,也避免了引用类型的属性被所有实例共享;

2、但没有继承父类原型上的属性和方法

 

3、组合继承

原型+借用构造函数

特点:

1、调用两次父类构造函数

2、占用内存

function Father(name){
    this.name=name
}
function Son(name,age){   
    Father.call(this,name); //借用构造函数,执行父类构造函数
     this.age=age;
}

Son.prototype=new Father(); //原型继承,既继承父类的构造函数,又继承父类的原型

 

4、寄生组合继承

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto语法:Object.create(proto, [propertiesObject])

function Son(age){
    Father.call(this,age);//借用构造函数继承了父类构造函数里的属性和方法
    this.age=age;
}
​
//寄生继承了原型里的属性和方法
Son.prototype=Object.create(Father.prototype,{
    constructor:{
        value:Son
    }
})
//或者:
Son.prototype=Object.create(Father.prototype);
Son.prototype.constructor=Son
Son.prototype.sayHi=function(){}
​
//原型多继承:Object.assign()
 function Son() {
    Father.call(this);
    OhterClass1.call(this);
    OhterClass2.call(this);
  }
​
  Son.prototype = Object.create(Father.prototype);
  Object.assign(Son.prototype, OhterClass1.prototype, OhterClass2.prototype);
  Son.prototype.constructor = Son;
 

九、call和apply和bind

这三个方法都是Function.prototype下的方法,用于改变函数运行时的上下文,即改变this的指向。

window.name = '李一灵';
document.name = '李流云';
const obj = {name:'王二蛋'}
//声明一个函数
let fn = function (){
    console.log(this.name) 
}
​
fn()// 李一灵  
fn.call(this)//当前this 指向的浏览器的全局对象window   李一灵
fn.call(document) //  '李流云'
fn.call(obj) //王二蛋
//第一个参数为null或者undefined,则默认指向window
​
区别:
apply第二个参数为数组,call为参数列表
那么apply第二个参数可以写arguments,即实参列表。
function aa(num){
    fn.apply(this,arguments) //arguments==[5]
    fn.call(this,num)
}
aa(5);
​
bind与call和apply的区别:
bind也是改变函数上下文,但不是立即执行,而是等需要时再调用。

 

十、for in与for循环

for in:

1、for in,index索引为字符串而非数字

2、遍历的顺序可能不按数组的顺序,

3、把实例和原型里可枚举属性都遍历,性能开销增大(constructor是不可枚举的,enumerable:fasle)

 

forEach、for in、for of 三者区别

forEach更多用来遍历数组

for in用来遍历对象或json

for of 数组对象都可以遍历,遍历的是value

for in 遍历的是key

 

十一、new

1、创建了一个新的空对象;

2、新对象的proto指向构造函数的prototype

3、构造函数内this指向新创建的对象;

4、返回新对象的地址。

function Person(name) {
  console.log(this) // this==p;  this.name=='ss'
  this.name = name;
}
var p=new Person('ss');
p._proto_==Person.prototype;
 

十二、this

this是什么:

this是函数被调用时自动生成的一个内部对象,只能在函数内部使用。

普通函数:一般指向调用时所处的环境对象(谁调用我,我就是谁);

箭头函数:指向定义时所处的环境对象。

1、全局作用域或者普通函数中,this指向window
//直接打印
console.log(this) //window
​
//function声明函数
function bar () {console.log(this)}
bar() //window
​
//function声明函数赋给变量
var bar = function () {console.log(this)}
bar() //window
​
//自执行函数
(function () {console.log(this)})(); //window
2、方法调用中,谁调用this指向谁
//对象方法调用
var person = {
  run: function () {console.log(this)}
}
person.run() // person
​
//事件绑定
var btn = document.querySelector("button")
btn.onclick = function () {
  console.log(this) // btn
}
//事件监听
var btn = document.querySelector("button")
btn.addEventListener('click', function () {
  console.log(this) //btn
})
​
//jquery的ajax
$.ajax({
  self: this,
  type: "get",
  url: url,
  async: true,
  success: function (res) {
    console.log(this) // this指向传入$.ajxa()中的对象obj
    console.log(self) // window
  }
});
//这里说明以下,将代码简写为$.ajax(obj) ,在obj中this指向window,因为在在success方法中,独享obj调用自己,所以this指向obj
3、构造函数中,this指向构造函数的实例
//不使用new指向window
function Person(name) {
  console.log(this) // window
  this.name = name;
}
Person('inwe')
//使用new
function Person(name) {
  this.name = name
  console.log(this) //people
  self = this
}
var people = new Person('iwen')
console.log(self === people) //true
//这里new改变了this指向,将this由window指向Person的实例对象people
4、箭头函数中,指向外层作用域的this(箭头函数没有自己的this和arguments,所以它引用的是外层的this和arguments)
var obj = {
  foo() {
    console.log(this);
  },
  bar: () => {
    console.log(this);
  }
}
​
obj.foo() // {foo: ƒ, bar: ƒ}
obj.bar() // window
 

 

十三、作用域和作用域链

作用域:有权访问的一个范围。

作用域链:每个执行环境可以通过向上查找,搜索变量和函数,而不能向下搜索,就近原型,最顶层是window。

 

十四、防抖、节流

防抖:将多次操作合并为一个操作。原理:维护一个定时器,规定在delay时间后执行,如果在这个时间段内触发,就会重新设置定时器,只有最后一次操作会被执行。缺点:如果在规定时间内不断出发,则调用方法会被不断延迟。

function debounce(fn,delay){
    var timer=null;
    clearTimerOut(timer); //每次进来都先清除定时器,再开始计时
    return function(){
        timer=setTimerOut(()=>{
            fn.call(this,arguments);
        },delay);
    }
}
​
function handleScroll(){
    console.log('函数防抖');
}
​
window.addEventListener('scroll',debounce(handleScroll,2000));

 

节流:规定在多久后再执行,如果当前有正在执行的回调函数,则return。防止重复点击导致发送多个请求。

function throttle(){
    let ajaxFlag=false;
    return function(){
        if(ajaxFlag) return;
        ajaxFlag=true;
        setTimeOut(()=>{
            ajaxFlag=false; //成功之后设置为fasle,表示可以再次执行了
        },1000);
    }
}

 

 

十六、懒加载

原理:

只加载窗口可视区域的图片,<img>设置一个自定义属性存放图片路径,当浏览器可视区域移动到此图片时,再将data-src的路径赋值给src,此时图片才会真正的加载。

作用:

加快当前可视区域图片的加载速度,优化用户体验。

减少同一时间发送服务器的请求数量,减小服务器的压力。

 

十七、立即执行函数

定义:声明一个匿名函数,然后执行它

作用:创建一个独立的作用域,外部访问不到,保护了变量不被全局污染。

 

十八、事件代理

定义:

利用事件冒泡的原理,把事件处理函数绑定在父级上,目的是提高性能。

(html元素是嵌套结构,在触发内层元素的事件时,外部事件也会被由内到外触发,这种现象叫做事件冒泡)

作用:

1、减少事件注册,减少dom操作,节省内存.(

js中添加到页面的事件处理程序数量直接关系到页面的整体性能, 因为需要不断地与dom进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长页面的交互就绪时间。

js中每个函数都是一个对象,对象越多,内存占用就越大,性能开销越大。

2、动态生成的子元素不用为其添加事件。

var oUL=document.getElementById("test");
oUL.addEventListener("click",function(ev){
    var target=ev.target;
    while(target!=='oUL'){
        if(target.tagName.toLowerCase=='li'){
            doSomething()//
            break;
        }
        target=target.parentNode;
    },false  
    //true,事件会在捕获阶段执行
    //false,事件会在冒泡阶段执行(默认)
})

 

阻止冒泡
e.stopPropagation()
阻止默认事件
e.preventDefault()

 

DOM2.0模型事件处理流程:

捕获阶段=>目标阶段=>冒泡阶段

document=>body=>dom(捕获阶段,从上往下)

target(事件源)

target=>parent=>body=>document(冒泡阶段,从下往上)

 

浏览器渲染流程

html文档包括html标签、css、以及javascript,浏览器引擎会从上到下逐行解析,遇到html标签和css样式,就交给GUI渲染线程去执行,遇到javascript代码就交给js引擎线程去执行,其中GUI渲染线程和js引擎线程是互斥的,不能同时进行。

1、构建DOM树:根据html标签建立(文档对象模型);

2、构建css规则树:包括选择器和样式的属性;

3、根据dom树和css规则树,构建render tree;

4、布局:根据渲染树计算元素的位置和大小

5、渲染:根据render tree和布局进行渲染。

 

整个前端性能提升大致分几类

1、静态资源的优化

主要是减少静态资源的加载时间,主要包括html、js、css和图片。

a、减少http请求数:合并js、css、制作雪碧图以及使用http缓存;

b、减小资源的大小:压缩文件、压缩图片,小图使用base64编码等;

c、异步组件和图片懒加载;

d、CDN加速和缓存(bootCND):客户端可通过最佳的网络链路加载静态资源,提高访问的速度和成功率。

(CDN:通过在网络各处放置节点服务器构成的一层智能虚拟网络,可将用户的请求重新导向离用户最近的服务节点上)

 

2、接口访问的优化

1、http持久链接(Conection:keep-alive)

2、后端优化合并请求(如果页面中有两个请求返回数据一样的,那么可以考虑合并请求)

3、冷数据接口缓存到localstorage,减少请求

 

3、页面渲染速度的优化

1、由于浏览器的js引擎线程和GUI渲染线程是互斥的,所以在执行js的时候会阻塞它的渲染,所以一般

会将css放在顶部,优先渲染,js放在底部;

2、减少dom的操作:

a、vue中使用了虚拟DOM渲染方案,做到最小化操作真实的dom;

b、事件代理:利用事件冒泡原理,把函数注册到父级元素上。

3、减少页面的重绘和回流。

 

vue里:

1、souceMap关闭,只打包压缩后的文件;

2、compression-webpack-plugin,并设置productGzip:true,开启压缩;

3、路由懒加载(加快首屏加载速度,缺点:把多个js分开打包,导致http请求数增多)

4、v-if和v-show:

v-if是懒加载,只有为true时才加载,false时不会占据布局空间

v-show:不管是true还是false都会渲染,并会占据布局空间,优点:减少页面的重绘和回流。

5、为item设置key值:在列表数据进行遍历渲染的时候,方便vue将新值和旧值做对比,只渲染变化了的部分。

6、组件细分(比如轮播组件、列表组件、分页组件等):当数据变更时,渲染会加快;其次易于组件复用和维护。

7、使用loading和骨架屏加载,防止白屏和闪屏的情况。

8、服务端渲染:vue的页面渲染,通过模板编译,渲染出页面,而不是直出html,对于首屏有较大的损耗。

服务端渲染:现在服务端进行整个html的渲染,再将整个html输出到浏览器端。

9、DNS缓存,减少dns查询时间。

 



这篇关于js的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程