前端性能优化最佳实践 -凯发k8国际

2013-07-04  编辑 wangguo 有76692人浏览
如今浏览器能够实现的特性越来越多,并且网络逐渐向移动设备转移,使我们的前端代码更加紧凑,如何优化,就变得越来越重要了。

开发人员普遍会将他们的代码习惯优先于用户体验。但是很多很小的改变可以让用户体验有个飞跃提升,所以任何一点儿小小的优化都会提升你网站的性能。

前端给力的地方是可以有许多种简单的策略和代码习惯让我们可以保证最理想的前端性能。我们这个系列的主题就是要告诉你一些前端性能优化的最佳实践,只需要一分钟,就可以优化你现有的代码。(本文内容来自)

最佳实践1:使用documentfragments或innerhtml取代复杂的元素注入

dom操作在浏览器上是要付税的。尽管性能提升是在浏览器,dom很慢,如果你没有注意到,你可能会察觉浏览器运行非常的慢。这就是为什么减少创建集中的dom节点以及快速注入是那么的重要了。

现在假设我们页面中有一个
    元素,调用ajax获取json列表,然后使用javascript更新元素内容。通常,程序员会这么写:

    var list = document.queryselector('ul');
    ajaxresult.items.foreach(function(item) {
        // 创建
  • 元素 var li = document.createelement('li'); li.innerhtml = item.text; //
  • 元素常规操作,例如添加class,更改属性attribute,添加事件监听等 // 迅速将
  • 元素注入父级
      中 list.apppendchild(li); });


  • 上面的代码其实是一个错误的写法,将
      元素带着对每一个列表的dom操作一起移植是非常慢的。如果你真的想要 使用document.createelement,并且将对象当做节点来处理,那么考虑到性能问题,你应该使用documentfragement。

      documentfragement 是一组子节点的“虚拟存储”,并且它没有父标签。在我们的例子中,将documentfragement想象成看不见的
        元素,在 dom外,一直保管着你的子节点,直到他们被注入dom中。那么,原来的代码就可以用documentfragment优化一下:

        var frag = document.createdocumentfragment();
        ajaxresult.items.foreach(function(item) {
            // 创建
      • 元素 var li = document.createelement('li'); li.innerhtml = item.text; //
      • 元素常规操作 // 例如添加class,更改属性attribute,添加事件监听,添加子节点等 // 将
      • 元素添加到碎片中 frag.appendchild(li); }); // 最后将所有的列表对象通过documentfragment集中注入dom document.queryselector('ul').appendchild(frag);


      • 为documentfragment追加子元素,然后再将这个documentfragment加到父列表中,这一系列操作仅仅是一个dom操作,因此它比起集中注入要快很多。

        如果你不需要将列表对象当做节点来操作,更好的方法是用字符串构建html内容:

        var htmlstr = '';
        ajaxresult.items.foreach(function(item) {
            // 构建包含html页面内容的字符串
            htmlstr  = '
      • ' item.text '
      • '; }); // 通过innerhtml设定ul内容 document.queryselector('ul').innerhtml = htmlstr;


        这当中也只有一个dom操作,并且比起documentfragment代码量更少。在任何情况下,这两种方法都比在每一次迭代中将元素注入dom更高效。

最佳实践2:高频执行事件/方法的防抖

通常,开发人员会在有用户交互参与的地方添加事件,而往往这种事件会被频繁触发。想象一下窗口的resize事件或者是一个元素的onmouseover事件 - 他们触发时,执行的非常迅速,并且触发很多次。如果你的回调过重,你可能使浏览器死掉。

这就是为什么我们要引入防抖。

防抖可以限制一个方法在一定时间内执行的次数。以下代码是个防抖示例:

// 取自 underscorejs 实用框架
function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callnow = immediate && !timeout;
        cleartimeout(timeout);
        timeout = settimeout(later, wait);
        if (callnow) func.apply(context, args);
    };
  }
// 添加resize的回调函数,但是只允许它每300毫秒执行一次
window.addeventlistener('resize', debounce(function(event) {
    // 这里写resize过程
}, 300));


debounce方法返回一个方法,用来包住你的回调函数,限制他的执行频率。使用这个防抖方法,就可以让你写的频繁回调的方法不会妨碍用户的浏览器!

最佳实践3:网络存储的静态缓存和非必要内容优化

web storage的api曾经是cookie api一个显著的进步,并且为开发者使用了很多年了。这个api是合理的,更大存储量的,而且是更为健全理智的。一种策略是去使用session存储来存 储非必要的,更为静态的内容,例如侧边栏的html内容,从ajax加载进来的文章内容,或者一些其他的各种各样的片断,是我们只想请求一次的。

我们可以使用javascript编写一段代码,利用web storage使这些内容加载更加简单:

define(function() {
    var cacheobj = window.sessionstorage || {
        getitem: function(key) {
            return this[key];
        },
        setitem: function(key, value) {
            this[key] = value;
        }
    };
    return {
        get: function(key) {
            return this.isfresh(key);
        },
        set: function(key, value, minutes) {
            var expdate = new date();
            expdate.setminutes(expdate.getminutes()   (minutes || 0));
            try {
                cacheobj.setitem(key, json.stringify({
                    value: value,
                    expires: expdate.gettime()
                }));
            }
            catch(e) { }
        },
        isfresh: function(key) {
            // 返回值或者返回false
            var item;
            try {
                item = json.parse(cacheobj.getitem(key));
            }
            catch(e) {}
            if(!item) return false;
            // 日期算法
            return new date().gettime() > item.expires ? false : item.value;
        }
     }
});


这个工具提供了一个基础的get和set方法,同isfresh方法一样,保证了存储的数据不会过期。调用方法也非常简单:

require(['storage'], function(storage) {
    var content = storage.get('sidebarcontent');
    if(!content) {
        // do an ajax request to get the sidebar content
        // ... and then store returned content for an hour
        storage.set('sidebarcontent', content, 60); 
    }
});


现在同样的内容不会被重复请求,你的应用运行的更加有效。花一点儿时间,看看你的网站设计,将那些不会变化,但是会被不断请求的内容挑出来,你可以使用web storage工具来提升你网站的性能。

最佳实践4:使用异步加载,延迟加载依赖

已经迎来了异步加载和amd格式的巨大浪潮。xmlhttprequest(该对象可以调用ajax)使得资源的异步加载变得流行起来,它允许无阻塞资源加载,并且使 onload 启动更快,允许页面内容加载,而不需要刷新页面。

我所用的异步加载器是john hann的。curl加载器是基本的异步加载器,可以被配置,拥有很好的插件。以下是一小段curl的代码:

// 基本使用:  加载一部分amd格式的模块
c {
    dom.setelementcontent('.social-container', social.loadwidgets());
});
// 定义一个使用google analytics的模块,该模块是非amd格式的
define(["js!//google-analytics.com/ga.js"], function() {
    // return a simple custom google analytics controller
    return {
        trackpageview: function(href) {
            _gaq.push(["_trackpageview", url]);
        },
        trackevent: function(eventname, href) {
            _gaq.push(["_trackevent", "interactions", eventname, "", href || window.location, true]);
        }
    };
});
// 加载一个不带回调方法的非amd的js文件
c;
// 将javascript和css文件作为模块加载
c {
    prism.highlightall();
});
// 加载一个ajax请求的url
c {
    storage.set('sidebar', content, 60);
    dom.setelementcontent('.sidebar', content);
});


你可能早就了解,异步加载可以大大提高万展速度,但是我想在此说明的是,你要使用异步加载!使用了之后你可以看到区别,更重要的是,你的用户可以看到区别。

当你可以根据页面内容延迟加载依赖的时候,你就可以体会到异步加载的好处了。例如,你可以只加载twitter,facebook和google plus到应用了名为social的css样式的div元素中。“在加载前检查是否需要”策略可以为我的用户节省好几kb的莫须有的加载。

最佳实践5:使用array.prototype.join代替字符串连接

有一种非常简单的客户端优化方式,就是用array.prototype.join代替原有的基本的字符连接的写法。在上面的“最佳实践1”中,我在代码中使用了基本字符连接:

htmlstr  = '
  • ' item.text '
  • ';


    但是下面这段代码中,我用了优化:

    var items = [];
    ajaxresult.items.foreach(function(item) {
        // 构建字符串
        items.push('
  • ', item.text, '
  • '); }); // 通过innerhtml设置列表内容 document.queryselector('ul').innerhtml = items.join('');


    也许你需要花上一点儿时间来看看这个数组是做什么用的,但是所有的用户都从这个优化中受益匪浅。

    最佳实践6:尽可能使用css动画

    网站设计对美观特性和可配置元素动画的大量需求,使得一些javascript类库,如jquery,mootools大量的被使用。尽管现在浏览器支持css的transformation和keyframe所做的动画,现在仍有很多人使用javascript制作动画效果,但是实际上使用css动画比起javascript驱动的动画效率更高。css动画同时需要更少的代码。很多的css动画是用gpu处理的,因此动画本身很流畅,当然你可以使用下面这个简单的css强制使你的硬件加速:

    .myanimation {
        animation: someanimation 1s;
        transform: translate3d(0, 0, 0); /* 强制硬件加速 */
    }


    tansform:transform(0,0,0)在不会影响其他动画的同时将通话送入硬件加速。在不支持css动画的情况下(ie8及以下版本的浏览器),你可以引入javascript动画逻辑:

    
    

    在上例中,ie-animations.js文件必须包含你自定义的jquery代码,用于当css动画在早期ie中不被支持的情况下,来替代css动画完成动画效果。完美的通过css动画来优化动画,通过javascript来支持全局动画效果。

    最佳实践7:使用事件委托

    想象一下,如果你有一个无序列表,里面有一堆
  • 元素,每一个
  • 元素都会在点击的时候触发一个行为。这个时候,你通常会在每一个元素上添加一个事件监听,但是如果当这个元素或者你添加了监听的这个对象会被频繁的移除添加呢?这个时候,你在移除添加元素的同时需要处理事件监听的移除和添加。这个时候,我们就需要引入事件委托了。

    事件委托是在父级元素上添加一个事件监听,来替代在每一个子元素上添加事件监听。当事件被触发时,event.target会评估相应的措施是否需要被执行。下面我们给出了一个简单的例子:

    // 获取元素,添加事件监听
    document.queryselector('#parent-list').addeventlistener('click', function(e) {
        // e.target 是一个被点击的元素!
        // 如果它是一个列表元素
        if(e.target && e.target.tagname == 'li') {
            // 我们找到了这个元素,对他的操作可以写在这里。
        }
    });


    上面的例子是不可思议的简单,当事件发生的时候,它没有轮询父节点去寻找匹配的元素或选择器,且它不支持基于选择器的查询(例如用class name,或者id来查询)。所有的javascript框架提供了委托选择器匹配。重点是,你避免了为每一个元素加载事件监听,而是在父元素上加一个事件监听。这样大大的增加了效率,并且减少了很多维护!
  • 最佳实践8:使用data uri代替图片src

    提升页面大小的效率,不仅仅是取决于使用精灵或是压缩代码,给定页面的请求数量在前端性能中也占有了很不小的重量。减少请求可以让你的网站加载更快,而其中一种减少页面请求的方法就是用data uri代替图片的src属性:

     
     
    <-- 范例:  http://davidwalsh.name/demo/data-uri-php.php -->


    当然页面大小会增加(如果你的服务器使用适当的gzip内容,这个增加会很小),但是你减少了潜在的请求,同时也在过程中减少了服务器请求的数量。现在大多数浏览器都支持data uri,在css中的背景骨片也可以使用data uri,因此这个策略现在已经可以在应用层级,广泛应用。

    最佳实践9:使用媒体查询加载指定大小的背景图片

    直到css @supports被广泛支持,css媒体查询的使用接近于css中写逻辑控制。我们经常用css媒体查询来根据设备调整css属性(通常根据屏幕宽度调整css属性),例如根据不同的屏幕宽度来设置不同的元素宽度或者是悬浮位置。那么我们为什么不用这种方式来改变背景图片呢?

    /* 默认是为桌面应用加载图片 */
    .someelement { background-image: ; }
     
    @media only screen and (max-width : 1024px) {
        .someelement { background-image: ; }
    }


    上面的代码片段是为手机设备或是类似的移动设备加载一个较小尺寸的图片,特别是需要一个特别小的图片时(例如图片的大小几乎不可视)。

    最佳实践10:使用索引对象

    这一篇,我们将讲讲使用索引对象检索代替遍历数组,提高遍历速度。

    ajax和json一个最常见的使用案例是接收包含一组对象的数组,然后从这组数组中根据给定的值搜索对象。让我们看一个简单的例子,下面这个例子中,你从用户接收一个数组,然后你可以根据username的值来搜索用户对象:

    function getuser(desiredusername) {
        var searchresult = ajaxresult.users.filter(function(user) {
            return user.username == desiredusername;
        });
     
        return searchresult.length ? searchresult[0] : false;
    }
     
    // 根据用户名获取用户
    var davidwalsh = getuser("davidwalsh");
     
    // 根据用户名获取另一个用户.
    var techpro = getuser("tech-pro");


    上面这段代码可以运行,但是并不是很有效,当我们想要获取一个用户时,我们就要遍历一次数组。那么更好的方法是创建一个新的对象,对每一个唯一的值建立一个索引,在上面这个例子中,用username作为索引,这个数组对象可以写成:

    var userstore = {};
    ajaxresult.users.foreach(function(user) {
        userstore[user.username] = user;
    });


    现在当你想要找一个用户对象时,我们可以直接通过索引找到这个对象:

    var davidwalsh = userstore.davidwalsh;
    var techpro = userstore["tech-pro"];


    这样的代码写起来更好一些,也很简便,通过索引搜索比起遍历整个数组更加快捷。

    最佳实践11:控制dom大小

    这一篇中,我们要说如何控制dom的大小,来优化前端性能。

    dom很慢是众所周知的,使得网站变慢的罪魁祸首是大量的dom。想象一下,假如你有一个有着上千节点的dom,在想象一下,使用queryselectorall或者getelementbytagname,或者是其他以dom为中心的搜索方式来搜索一个节点,即使是使用内置方法,这也将是一个非常费力的过程。你要知道,多余的dom节点会使其他的实用程序也变慢的。

    我见过的一种情况,dom的大小悄然增加,是在一个ajax网站,它将所有的页面都存在了dom中,当一个新的页面通过ajax被加载时,旧的页面就会被存入隐藏的dom节点。对于dom的速度,将有灾难性的降低,特别是当一个页面是动态加载的。所以你需要一种更好的方法。

    在这种情况下,当页面是通过ajax加载的,并且以前的页面是存储在客户端的,最好的方法就是将内容通过string html存储(将内容从dom中移除),然后使用事件委托来避免特定元素事件。这么做的同时,当在客户端缓存内容的时候,可以避免大量的dom生成。
    通常控制dom大小的技巧包括:

    • 使用:before和:after伪元素
    • 延迟加载和呈现内容
    • 使用事件委托,更简便的将节点转换成字符串存储
    简单一句话:尽量使你的dom越小越好。

    最佳实践12:在繁重的执行上使用web workers

    这一篇我们将介绍web workder,一种可以将繁重操作移到独立进程的方法。

    web workders在前段时间被引入流行的浏览器中,但是好像并没有被广泛应用。web workers的主要功能是在一般浏览器执行范围外执行繁重的方法。它不会访问dom,所以你必须传入方法涉及的节点。

    以下是一段web workder的示例代码:

    /* 使用web worker */
    //  启动worker
    var worker = new worker("/path/to/web/worker/resource.js");
    worker.addeventlistener("message", function(event) {
        // 我们从web worker获取信息!
    });
     
    // 指导web worker工作!
    worker.postmessage({ cmd: "processimagedata", data: convertimagetodatauri(myimage) });
     
    /*  resource.js就是一个web workder */
    self.addeventlistener("message", function(event) {
        var data = event.data;
     
        switch (data.cmd) {
            case 'process':
                return processimagedata(data.imagedata);
    });
     
    function processimagedata(imagedata) {
        // 对图像进行操作
        // 例如将它改成灰度
     
        return newimagedata;
    }


    以上这段代码是一个教你如何使用web worker在其他进程中做一些繁重工作的简单示例。它要执行的是将一个图片从普通颜色转个灰度,因为这是一个比较繁重的过程,所以你可以将这个进程提交给web worker,使你的浏览器负载不是很大。data通过message事件传回。
    你可以仔细阅读以下,也许在你的网站上有一些功能可以移到其他的独立进程中去执行。

    最佳实践13:链接css,避免使用@import

    有时候,@import太好用以至于很难抗拒它的诱惑,但是为了减少令人抓狂的请求,你必须要拒绝它!最常见的用法是在一个"main"css文件中,没有任何的内容,只有@import规则。有时,多个@import规则往往会造成事件嵌套:

    // 主css文件(main.css)
    @import "reset.css";
    @import "structure.css";
    @import "tutorials.css";
    @import "contact.css";
     
    // 然后在tutorials.css文件中,会继续有@import
    @import "document.css";
    @import "syntax-highlighter.css";


    我们这样写css文件,在文件中多了两个多余链接,因此会使页面加载变慢。sass可以读取@import语句,链接css内容到一个文件中,减少了多余的请求,控制了css文件的大小。

    最佳实践14:在css文件中包含多种介质类型

    在上面第13个最佳实践中我们说过,多个css文件可以通过@import规则合并到一起。但是很多程序员不知道的是,多种css介质类型也可以合并到一个文件中。

    /* 以下全部写在一个css文件中 */
     
    @media screen {
        /* 所有默认的结构设计和元素样式写在这里 */
    }
     
    @media print {
        /* 调整打印时的样式 */
    }
     
    @media only screen and (max-width : 1024px) {
        /* 使用ipad或者移动电话时的样式设定 */
    }


    对于文件的大小,什么时候必须合并介质,或是什么时候必须分开设定,css并没有硬性规定,但是我会比较建议将所有的介质合并,除非其中一个介质所占的比例比起其他大了许多。少一个请求对于客户端和服务器都将轻松不少,而且在大多数情况下,附赠的介质类型相比主屏介质类型要相对小很多。


    我要 本文来自:

    相关推荐

    • 第二,什么才是最佳实践。我们通过编码风格和约定保持一致,可以减轻代码维护的工作量,并降低风险,在将来代码出现问题的时候,方便我们排查错误。我们要保持最佳的编码习惯和做法,确保优化页面的加载和性能,并能...

    • web性能优化、前端性能优化、web性能指标、性能测量、lighthouse

    • 如今浏览器能够实现的特性越来越多,并且网络逐渐向移动设备转移,使我们的前端代码更加紧凑,如何优化,就变得越来越重要了。...我们这个系列的主题就是要告诉你一些前端性能优化的最佳实践,只需

    • 针对难点问题的一些凯发k8国际娱乐官网入口的解决方案,在web i/o优化、应用调优、前端调优与后端调优配合的团队合作等方面分享了实际经验,在应用与数据库之间构建统一数据服务层,进行数据服务层调优,在性能调优方面总结分享了最佳实践...

    • 代码层面、webpack层面、web技术层面: vue 项目性能优化:

    • 转载:http://www.infoq.com/cn/interviews/hl-alibaba-front-end-performance-optimization   大家好,我现在在阿里巴巴园区采访阿里巴巴中文站架构师,兼b2b网站优化领域的负责人何崚。何崚你好,请简单

    • 性能检测作为性能优化过程中的一环,它的目的通常是给后续优化工作提供指导方向、参考基线及前后对比的依据。性能检测并不是一次性执行结束后就完成的工作,它会在检测、记录和改进的迭代过程中不断重复,来协助网站...

    • 本文将主要关注浏览器获取到资源后,进行渲染部分的相关优化内容。 7.1 页面渲染性能 页面渲染阶段对性能体验的应i昂与资源加载阶段同样重要,而对于设计高交互频次的应用来说可能更加重要。 本节将整个渲染过程划分...

    • 当然,他们在前端开发和性能优化中的地位举足轻重,但 javascript 和 css 对用户感知而言,并不是最重要的部分,图像才是。我们在公众号发布文章或用 ppt 进行演讲时,都知道一条高效传递信息的原则:字不如表,表...

    • 一、web前端开发概述 1. web前段开发入门难度并不高,但是初学者如果没有一个很好的学习和编码习惯,则开发水平的提高速度就会变得很慢。在这里我总结了如何高效的进行web前段的开发。 2. web前端开发的范畴: ...

    • vue 项目中的前端性能优化

    • 二、最佳实践是使用外部文件而非在html页面中直接嵌入javascript代码。 在html中嵌入javascript代码虽然没有问题,但一般认为最好的做法还是尽可能使用外部文件来包含javascript代码。浏览器能够根据具体的设置缓存...

    • 这篇文章将介绍您可以用来帮助改进前端优化的有用技术。通过专注于干净的代码、压缩图像、最小化外部请求、实施 cdn 和其他一些方法,您可以显着提高网站的速度和整体性能。 1.清理html文档 html 或超文本标记...

    • 这篇文章讲述可以帮助 改善优化前端的技术,非常有用。主要内容有清理代码、压缩图片、压缩外部资源、使用cdn,以及一些其它方法。这些方法会为你的网站带显著的速度提升和整体性能提升。 一. 清理 html 文档 html,...

    • 前端开发中的最佳实践和性能优化技巧对于构建高效、响应迅速的web应用程序至关重要。以下是10种解决前端开发中最佳实践和性能优化的方式,以及相应的代码示例。 1.使用压缩和合并技术 通过压缩和合并css和javascript...

    • 前端性能优化(一) 前端是庞大的,包括 html、 css、 javascript、image 、flash等等各种各样的资源。前端优化是复杂的,针对方方面面的资源都有不同的方式。那么,前端优化的目的是什么 ?  1. 从用户角度...

    • 管理系统是一种通过计算机技术实现的用于组织、监控和控制各种活动的软件系统。这些系统通常被设计用来提高效率、减少错误、加强安全性,同时提供数据和信息支持。以下是一些常见类型的管理系统: 学校管理系统: 用于学校或教育机构的学生信息、教职员工信息、课程管理、成绩记录、考勤管理等。学校管理系统帮助提高学校的组织效率和信息管理水平。 人力资源管理系统(hrm): 用于处理组织内的人事信息,包括员工招聘、培训记录、薪资管理、绩效评估等。hrm系统有助于企业更有效地管理人力资源,提高员工的工作效率和满意度。 库存管理系统: 用于追踪和管理商品或原材料的库存。这种系统可以帮助企业避免库存过剩或不足的问题,提高供应链的效率。 客户关系管理系统(crm): 用于管理与客户之间的关系,包括客户信息、沟通记录、销售机会跟踪等。crm系统有助于企业更好地理解客户需求,提高客户满意度和保留率。 医院管理系统: 用于管理医院或医疗机构的患者信息、医生排班、药品库存等。这种系统可以提高医疗服务的质量和效率。 财务管理系统: 用于记录和管理组织的财务信息,包括会计凭证、财务报表、预算管理等。财务管理系统

    • gb2312字符集 作用:国家简体中文字符集,兼容ascii。 位数:使用2个字节表示,能表示7445个符号,包括6763个汉字,几乎覆盖所有高频率汉字。 范围:高字节从a1到f7, 低字节从a1到fe。将高字节和低字节分别加上0xa0即可得到编码。 gbk字符集 作用:它是gb2312的扩展,加入对繁体字的支持,兼容gb2312。 位数:使用2个字节表示,可表示21886个字符。 范围:高字节从81到fe,低字节从40到fe。 gb18030字符集 作用:它解决了中文、日文、朝鲜语等的编码,兼容gbk。 位数:它采用变字节表示(1 ascii,2,4字节)。可表示27484个文字。 范围:1字节从00到7f; 2字节高字节从81到fe,低字节从40到7e和80到fe;4字节第一三字节从81到fe,第二四字节从30到39。

    • 毕业设计,安卓app,基于java开发的学生成绩课件管理系统app,包括pc端和安卓anroid手机app,内含java完整源码 安卓andriod学生成绩课件管理系统 系统开发环境: windows myclipse(服务器端) eclipse(手机客户端) mysql数据库 服务器也可以用eclipse或者idea等工具,客户端也可以采用android studio工具! 系统客户端和服务器端架构技术: 界面层,业务逻辑层,数据层3层分离技术,mvc设计思想! 服务器和客户端数据通信格式:json格式,采用servlet方式 【服务器端采用ssh框架,请自己启动tomcat服务器,hibernate会自动生成数据库表的哈!】 hibernate生成数据库表后,只需要在admin管理员表中加个测试账号密码就可以登录后台了哈! 下面是数据库的字段说明: 班级: 班级编号,班级名称,开办日期,班主任 学生: 学号,登录密码,所在班级,姓名,性别,出生日期,学生照片,联系电话,家庭地址 老师: 教师编号,登录密码,姓名,性别,出生日期,联系电话,邮件,地址,

    global site tag (gtag.js) - google analytics
    网站地图