前端面经,自己整理

!长文慎入!!

行内元素

a ,b ,button ,i ,span ,img ,input ,textarea ,select

块级元素

article ,dic ,form ,h1-h6 ,header ,hr ,ul ,li ,p ,table

postion 值

relative, static, absolute, sticky

JS 数据类型

number, boolean, string, undefined, null, object, symbol

script标签的 async 参数和 defer 参数的差别

defer: 表示script文件可以先下载,但是延迟执行(顺序执行),但是会早于DOMContentLoaded执行。晚于页面解析渲染,早于全部结束。

async: 告诉外部的script文件下载后,直接执行,不会根据顺序执行,所以script文件间出现相互依赖会出现问题。

css 选择器

#id : id选择器

.class : class选择器

tag : 标签选择器

:link : 伪类选择器

缓存

  1. 强缓存
    Cache-Control: max-age > Expires
  2. 协商缓存
    Etag / If-None-Match > Last-Modified / If-Modified-Since

js事件循环

同步任务 > 微任务(Promise.then, MutaionObserver) > 宏任务(setIntervalsetTimeout)

http 请求过程

  1. 包装请求header
  2. 找缓存
  3. 本地HOST和DNS
  4. 根DNS、域名DNS,找到服务器IP
  5. TCP链接
  6. 寻找80端口
  7. 服务器转发
  8. 发送http请求
  9. 服务器响应,判断缓存,判断重定向
  10. 服务器返回
  11. 断开连接
  12. 页面加载,渲染

https 请求过程

  1. 向服务器发送协议、版本、客户端随机数
  2. 返回协议、公钥、服务端随机数
  3. 客户端验证服务端证书合法
  4. 客户端用服务器发来的公钥加密随机数后发送
  5. 服务器私钥解密后,用信息加密传输数据发送
  6. 客户端解密

状态码

1xx 信息
2xx 成功
301 重定向
302 临时重定向
303 未修改
404 资源不存在
403 拒绝服务
408 请求超时
500 服务器错误
502 代理服务器错误
504 网关超时

数组去重

function  toSingeItem(arr){
    return Array.from(new Set(arr))
}

数组扁平化

// 第一种 递归
let res = [];
function flat(arr) {
    arr.forEach(item => {
        if (item instanceof Array) {
            flat(item)
        } else {
            res.push(item)
        }
    })
}
console.log(res);

// 第二种 方法
arr.toString().split(',').map(item=>{
	return Number(item)
})

大数相加

function add(num1, num2) {
    if (num1.length < num2.length) {
        [num1, num2] = [num2, num1]
    }
    num1 = num1.split('').reverse().join('');
    num2 = num2.split('').reverse().join('');
    let res = '';
    let f = 0;
    for (let i = 0; i < num1.length; i++) {
        let a = Number(num1[i]);
        let b = 0;
        if (num2.length > i) {
            b = Number(num2[i])
        }
        res += (parseInt((a + b + f) % 10)).toString();
        f = (parseInt((a + b) / 10))
    }
    if (f != 0 ) {
        res += f.toString()
    }
    return res.split('').reverse().join('');
}

> 相乘同理

隐式转换

[0] == 0        //true
[ ] == 0        //true
'' == 0         //true
'' === []       //true
1 == 2 == 3     //false
1 == 2 == 0     //true
...

webpack 优化

  1. 减少编译体积 ContextReplacementPugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。
  2. 并行编译 happypack、thread-loader、uglifyjsWebpackPlugin开启并行
  3. 缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin开启缓存、babel-loader开启缓存
  4. 预编译 dllWebpackPlugin && DllReferencePlugin、auto-dll-webapck-plugin

性能优化

  1. 减少编译体积 Tree-shaking、Scope Hositing。
  2. hash缓存 webpack-md5-plugin
  3. 拆包 splitChunksPlugin、import()、require.ensure

css 自适应正方形

1.padding撑开
.box{
    width: 50%;
    height: 0px;
    padding-bottom: 50%;
}
2. 新方法的属性
.box{
    width:50%;
    height:50vw;
    // 1vw = 1% viewport width; 1vh = 1% viewport height
}
3. 伪元素
.box{
    width:50%;
    overflow:hidden;
}
.box:before{
    content:'';
    display:block;
    margin-top:100%;
}

垂直居中

1.绝对定位
.outer{
    position: relative;
}
.inner{
    left: 50%;
    top: 50%;
    transform: transate(-50%,-50%)
}

2. flex布局
.outer{
    display: flex;
    align-items: center;
    justify-content: center;
}

节流函数

// 延迟执行
let debounce = function(fnc, wait){
    let timer = null;
    return function(){
        if(timer){
            clearTimeout(timer)
        }
        timer = setTimeout(fnc, wait) 
    }
}

// 立即执行
let debounce = function(fnc, wait){
    let timer = null;
    return function(){
        if(timer){
            clearTimeout(timer)
        }
        if(!timer){
            fn();
        }
        timer = setTimeout(()=>{
            timer = null;
        }, wait) 
    }
}

防抖函数

let thorrlte = function(fnc, wait){
    let timer = new Date();
    return function(){
        let now =new Date();
        if(now - timer > wait){
            fnc();
            timer = now;
        }
    }
}

创建DOM树

//样例数据
let elementObj = ({
    tagName: 'ul',
    props: {'class': 'list'},
    children: [
        ({tagName: 'li', children: ['douyin']}),
        ({tagName: 'li', children: ['toutiao']})
    ]
});

function createNode(elementObj){
    let dom = document.createElement(elementObj.tagName);
    Object.keys(elementObj.props).forEach(key=>{
        dom.setAttribute(key, elementObj.props[key]);
    });
    if(elementObj.content){
        dom.innerText = elementObj.content;
    }
    if(elementObj.children.length != 0){
        elementObj.children.forEach(child => {
            dom.appendChild(createNode(child));
        })
    }
    return dom
}

视口设置

<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, user-scalable=no">

浏览器本地存储

1. 大小不同:

cookies:4K

localStorage:5M

sessionStorage:5M

2. 有效期不同:

cookies: 设置,超过即失效。

localStorage: 永久有效,除非手动删除。

sessionStorage: 在当前会话下有效,关闭页面或者浏览器时会被清空。

3. 与服务器通信:

cookies:参与服务器端通信,每次都会携带http的头信息中。

localStorage不参与服务器端的通信。

sessionStorage不参与服务器端的通信。

JS的继承

  1. 原型链继承
// 父类 Person
let Person = function (name, sex, age) {
   this.name = name;
   this.sex = sex;
   this.age = age;
}
// 父类方法
Person.prototype.getName = function () {
   return this.name;
}
let nys = new Person('nys', 'male', '23')

// 子类 学生
let Student = function (stu_id) {
   this.stu_id = stu_id;
}
// 继承
Student.prototype = nys;
let stu = new Student('xh123')

// 优点
1. 简单
2. 基于原型链,从上到下关系一样
// 缺点
1. 来自原型对象的所有属性被所有实例共享
2. 无法实现多继承
3. 创建子类实例时,无法向父类构造函数传参
  1. 构造函数继承
// 父类 Person
let Person = function (name, sex, age) {
   this.name = name;
   this.sex = sex;
   this.age = age;
}
// 子类 Student
let Student = function(name, sex, age, stu_id){
   this.stu_id = stu_id;
   Person.call(this, name, sex, age);
}

let stu = new Student('nys', 'male', 25, 'xh22150546');
console.log(stu instanceof Person)

// 优点:
1. 创建子类实例时,可以向父类传递参数
2. 可以实现多继承(call多个父类对象)

// 缺点:
1. 实例并不是父类的实例,只是子类的实例
2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
  1. 实例继承
// 父类 Person
let Person = function (name, sex, age) {
   this.name = name;
   this.sex = sex;
   this.age = age;
}
// 子类 Student
let Student = function(name, sex, age, stu_id){
    let p = new Person(name, sex, age);
    p.stu_id = stu_id;
    return p;
}

let stu = new Student('nys', 'male', 25, 'xh22150546');
// 优点:
1. 简单
2. 反正就是简单

// 缺点:
1. 不能多继承
2. 实例是父类的实例,不是子类自己的
  1. 拷贝继承
// 父类 Person
let Person = function (name, sex, age) {
   this.name = name;
   this.sex = sex;
   this.age = age;
}
// 子类 Student
let Student = function(name, sex, age, stu_id){
    let p = new Person(name, sex, age);
    for(let key of p){
        Student.prototype[key] = p[key]
    }
    Student.prototype['stu_id'] = stu_id
}

let stu = new Student('nys', 'male', 25, 'xh22150546');
// 优点:
1. 简单,就是把父类有的复制过来
2. 可以实现多继承

// 缺点:
1. 不能访问父类到不可以枚举方法
2. 如果父类过大,效率过低
3. 付费新增方法无法获取
  1. Class继承
class Person {
    constructor(name, sex, age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    getName(){
        return this.name;
    }
}

class Student extend Person {
    constructor (name, sex, age, stu_id){
        super( name, sex, age );
        this.stu_id = stu_id;
    }
    getID(){
        return this.stu_id;
    }
}

instanceof

function instanceof(sub, sup){
    let sup_p = sup.prototype;
    let sub_p = sub.__proto__;
    while(true){
        if(sub_p == null){
            return false
        } else if(sub_p == sup_p){
            return true
        }
        sub_p = sub.__proto__;
    }
}

typeof

000:对象
1  :整数
010:浮点数
100:字符串
110:布尔
undefined:用 - (−2^30)表示。
null:对应机器码的 NULL 指针,一般是全零。

// 所以是分不清 Array 和 Object 的

Object.prototype.toString.call( )
// 这样可以检查区分开

call、apply、bind

Function.prototype.newCall = function (exector) {
   exector.fn = this;
   let res = exector.fn([...arguments].slice(1));
   return res
}

Function.prototype.newApply = function(exector){
   exector.fn = this;
   let res = arguments[1] ? exector.fn(...arguments[1]) : exector.fn()
   return res
}

Function.prototype.newBind = function(exector){
   let self = this
   let args = [...arguments].slice(1)
   return function(){
       self.apply(exector, args)
   }
}

import和require

import require
规范 ES6,需兼容 CommonJS/AMD
调用时间 编译时调用 运行时调用
本质 解构过程 赋值过程

new 原理

  1. 创建一个空对象
  2. 链接到原型
  3. 绑定this值
  4. 返回新对象
function nnew(){
   let newObj = new Object();
   let constructor = [...arguments].shift();
   let args = [...arguments].slice(1)
   newObj.__proto__ == constructor.prototype;
   return constructor.apply(newObj, args)
}

动画

  1. 最早的 setTimeoutsetInterval
  2. css的 animation
  3. requestAnimationFrame(function(){})
动画属性
animation-name 规定需要绑定到选择器的 keyframe 名称。
animation-duration 规定完成动画所花费的时间,以秒或毫秒计。
animation-timing-function 规定动画的速度曲线。
animation-delay 规定在动画开始之前的延迟。
animation-iteration-count 规定动画应该播放的次数。
animation-direction 规定是否应该轮流反向播放动画。

for…in 和 for…of

  1. for…in 遍历数组的下标和对象的值
  2. for…of 遍历Array、Map、Set、String、Number的值,不可以遍历对象

flex

含义 默认 可选
flex-direction 方向 row row 、row-reverse 、column 、column-reverse
flex-wrap 换行 nowrap nowrap、 wrap、 wrap-reverse
flex-flow 简写模式 [row nowrap] [ flex-direction flex-wrap]
justify-content 主轴对齐方式 flex-start flex-start、 flex-end、 center、 space-between(俩端对齐)、 space-around(分散对齐)
align-items 交叉轴对齐方式 stretch flex-start、 flex-end、 center、 baseline、 stretch
align-content 多轴对齐方式 stretch flex-start、 flex-end、 center、 space-between、 space-around、 stretch
order 排序 0 任意
flex-grow 放大倍数 0(默认不放大) 任意>0
flex-shrink 缩小倍数 1(宽度不够默认缩小) 任意>0
flex 简写模式 [0 1 auto] (后俩属性可不填) [ <‘flex-grow’> ?<‘flex-shrink’> ?<‘flex-basis’> ]
align-self 自己对齐方式 auto继承 auto、 flex-start、 flex-end、 center、 baseline、 stretch

元素大小属性

// content + padding
element.clientWidth
element.clientHeight

// border
element.clientTop
element.clientLeft

// content + padding + border
element.offsetWidth
element.offsetHeight

// margin + 定位偏移
element.offsetTop
element.offsetLest

封装请求

function reqest({
    url,
    method = 'GET',
    headers = {},
    params = {},
    async = true,
}) {
    return new Promise((reslove, rejexr) => {
        let xhr = new XMLHttpRequest();
        xhr.open(method, url, async);
        Object.keys(headers).forEach(header => {
            xhr.setRequestHeader(key, header[header])
        });
        xhr.send(params);
        xhr.onreadystatechange = () => {
            if (xhr.readyState == 4 && xhr.status == 200) {
                reslove(xhr.response)
            }
        }
    })
}

清除浮动

  1. 给父元素加高度
  2. clear: left| right| both
  3. :after => clear: botn
  4. overflow: hidden | auto

promise

function promise(exector) {
            let self = this
            self.value = null;
            self.status = 'pedding';
            self.callbacks = [];

            function resolve(value) {
                if (self.status == 'pedding') {
                    self.status = 'resolved';
                    self.value = value;
                    // 此处为异步方法,应比同步晚执行,放入异步队列。
                    setTimeout(() => {
                        self.callbacks.forEach(fn => {
                            fn.onResolved(self.value);
                        })
                    }, 0);
                }
            }

            function reject(error) {
                if (self.status == 'pedding') {
                    self.status = 'rejected';
                    self.value = error;
                    // 此处为异步方法,应比同步晚执行,放入异步队列。
                    setTimeout(() => {
                        self.callbacks.forEach(fn => {
                            fn.onRejected(self.value);
                        })
                    }, 0);
                }
            }

            try {
                exector(resolve, reject)
            } catch (e) {
                reject(e)
            }
        }

        promise.prototype.then = function (onResolved, onRejected) {
            let self = this
            // 穿透
            onResolved = typeof onResolved == 'function' ? onResolved : function () {return self.value }
            onRejected = typeof onRejected == 'function' ? onRejected : function () {return self.value }
            // 链式返回仍是 promise
            return new promise((resolve, reject) => {
                // 为了解决 resolve 被放入异步队列,状态还处于pedding的时候无法执行下面的方法。
                if (self.status == 'pedding') {
                    self.callbacks.push({
                        onResolved: val => {
                            try {
                                let res = onResolved(self.value);
                                resolve(res)
                            } catch (e) {
                                reject(e)
                            }
                        },
                        onRejected: val => {
                            try {
                                let res = onRejected(self.value);
                                resolve(res)
                            } catch (e) {
                                reject(e)
                            }
                        }
                    })
                }
                if (self.status == 'resolved') {
                    setTimeout(() => {
                        try {
                            let res = onResolved(self.value);
                            resolve(res)
                        } catch (e) {
                            reject(e)
                        }
                    }, 0);
                }
                if (self.status == 'rejected') {
                    setTimeout(() => {
                        try {
                            let res = onRejected(self.value);
                            resolve(res)
                        } catch (e) {
                            reject(e)
                        }
                    }, 0);
                }
            })
        }

promise.all

promise.prototype.newAll = function(arr){
    let result = [];
    return new promise((reslove, resject)=>{
        arr.forEach((item, index)=>{
            item.then((value)=>{
                result[index] = value;
                if( result.length == arr.length){
                    reslove(result)
                }
            }, reject)
        })
    })
}

promise.race

promise.prototype.newRace = function(arr){
    return new prmose((reslove,reject)=>{
        arr.forEach((item, index)=>{
            item.then((value)=>{
                reslove(value)
            }, reject)
        })
    })
}

JSONP

function jsonp(url, cb){
    let script = document.createElement('script');
    let script.src = `${url}?callback=${cb}`;
    document.querySelector('head').appendChild(script);

    cb = function(data){
        console.log(data.toString())
    }
}

移动端适配

  1. @media screen and (min-width: * px)
  2. rem
  3. vw、vh

事件委托

// 添加监听
element.addEventListener(event, func, useCapture)
// event:        绑定的事件名
// func:         触发操作
// useCapture    true-事件在捕获阶段执行;false-默认。事件在冒泡阶段执行

// 移除监听
element.removeEventListener(event, function,useCapture)

阻止冒泡

event.stopPropagation();

vue原理

  1. 响应式原理

判断是否被观测, 没有就去监测
对象: 循环使用 Object.defineProperty(object, key, func()); 递归观测,对data 循环遍历,get的时候收集依赖,set的时候更新试图
数组: 重写数组方法,原型链重定向到重写的原型,调用原方法,同时通知试图更新。

  1. 为什么异步渲染

解决问题:一个组件有非常多数据,如果修改多次或者修改多个数据,会触发多次页面渲染
解决方法:异步更新,dep.update => subs[i].update => queue => nextTick
把需要更新的组件放入队列,一个组件只存一次(根据组件id过滤),在 nextTick阶段 一次性渲染,会触发beforeupdate 和 updated 等钩子

  1. nextTick 原理

使用 Promise / MutationObserver / setImmediate / setTimeout
保证DOM渲染完成 后取DOM,可以获取最新值
异步渲染中使用到,queue会在nextTick中执行。

  1. computed(计算属性) 特点

带缓存
和 watch 一样 都是一个 依赖(watcher)
lazy = dirty = true ,默认不会执行计算方法,在取值的时候才会执行,执行后 dirty = fasle, 下次直接取值。
计算属性内值变化,导致更新, dirty = true 下次取值再次计算。

  1. 生命周期

beforeCreate 拿不到实例中的数据
Created 可以拿到数据,拿不到el,页面还未渲染,可发请求
beforeMount 挂载前 (SSR 无本阶段)
Mounted 挂载结束,可以拿到 DOM,通过新el替代旧el (SSR 无本阶段)
beforeUpdate 执行更新、重新渲染前,可以更改DOM,下个钩子一起更新
Updated 更新完成后,拿到最新DOM
beforeDestory 销毁前,取消定时器,事件绑定等等

  1. 模板编译原理

先把页面语法(html+ js)转化为 AST Tree => 遍历对象 => 拼接成字符串 => new Function => render() => vdom

  1. v-ifv-show 区别

v-if 渲染时判断是否渲染该节点
v-show 通过display:none 判断

  1. v-ifv-for 不要同时使用

优先级: v-for > v-if ,循环渲染的时候会每次渲染都去判断,性能很低

  1. diff 算法(双指针)

同级比较vdom (key和标签名)
相同的话,复用olddom
再比较子节点,再递归比较

  1. v-for 为什么要用 key

Vue 使用原地复用
删除的时候,会对预期结果进行检查,并可能出现删除最后一个的情况。

  1. data 为什么是函数

防止 data 被篡改
防止 子组件间的数据互相影响

  1. clickclick.native

.native修饰符是为了给组件绑定原生的事件方法。直接绑定无法触发

  1. v-model 实现原理

只对表单元素有用,可以说是 value+input

<temp v-model='check'></temp>
//实则传给子组件有个
model: {
    prop:'check',
    event:'input',
}
处理传过来的值,方法就是 event。

如果是表单组件,在编译的时候,会自己选择使用对应的方法来触发绑定的值。

  1. v-html 问题

导致xss攻击
内部子元素全部被覆盖

  1. 组件见通信

父 => 子:props
子 => 父:$on() + $emit()
直接获取组件实例: $parent, $children
依赖注入: provide, inject
Vuex

  1. Vue 中相同的逻辑如果抽离(Vue.mixin)

合并公共的 data,生命周期,方法…
都放在一个数组里,循环数组

  1. vuex

mutation : 同步更新
action : 异步处理

webpack

  1. entry 入口
//单入口
const config= {
    entry: {
        main: '../index.js' (main file path)
    }
}
//多入口
const config = {
    entry: {
        pageOne: '../indexOne.js',
        pageTwo: '../indexTwo.js',
    }
}
module.exports = config;
  1. output 出口
const config= {
    entry: {
        main: '../index.js' (main file path)
    },
+   output: {
+       filename: '[name].js',
+       path: __dirname+ '/dist'
+}
}
  1. mode 模式
const config= {
    entry: {
        main: '../index.js' (main file path)
    },
    output: {
        filename: '[name].js',
        path: __dirname+ '/dist'
    },
    mode: 'development'  // production
}
//在 俩个值中选择 ,或者在webpack --mode=production,打包的时候 CLI中传入
  1. module 模块
// 需要先安装对应模块
const config = {
    ......
    module: {
        rules: [
            { test: /\.css$/, use: 'css-loader' },
            { test: /\.ts$/, use: 'ts-loader' }
        ]
    }
}
// 也可以一个规则配置多个loader
const config = {
    ......
    module: {
        rules: [
            { test: /\.css$/, use: [
                {loader: 'css-loader'},
                {loader: 'style-loader'}
            ]
            { test: /\.ts$/, use: 'ts-loader' }
        ]
    }
}
  1. plugins 插件
const config = {
    ......
    plugins: [
        new webpack.optimize.UglifyJsPlugin(),
        new HtmlWebpackPlugin({template: './src/index.html'})
    ]
}
// npm 安装后直接调用实例即可,设置自定义。
// 更多方法插件 https://www.webpackjs.com/plugins/

常用webpack plugin

  1. HotModuleReplacementPlugin
    热更新插件此插件为webpack自带插件
plugins: [
  new webpack.HotModuleReplacementPlugin(),
]
  1. html-webpack-plugin
    会把 entry 和 extract-text-webpack-plugin 中生成的css和js文件插入到生成的html中
const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
  new HtmlWebpackPlugin({
    filename: 'index.html',
    template: path.join(__dirname, '/index.html'),
    minify: {
      // 压缩HTML文件
      removeComments: true, // 移除HTML中的注释
      collapseWhitespace: true, // 删除空白符与换行符
      minifyCSS: true, // 压缩内联css
    },
    inject: true,
  }),
]
  1. clean-webpack-plugin
    删除上一次打包生成的buudle文件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

plugins: [
  new HtmlWebpackPlugin({
    template: path.join(__dirname, '/index.html'),
  }),
  new CleanWebpackPlugin(), // 所要清理的文件夹名称
]
  1. extract-text-webpack-plugin
    把css单独生成文件
const ExtractTextPlugin = require('extract-text-webpack-plugin')

plugins: [
  new ExtractTextPlugin('css/index.css'),
]

5.UglifyJsPlugin
vue-vli 中使用的代码压缩插件

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

plugins: [
  new UglifyJsPlugin({
    uglifyOptions: {
      compress: {
        warnings: false
      }
    },
    sourceMap: true,  //是否启用文件缓存
    parallel: true   //使用多进程并行运行来提高构建速度
  })
  1. DefinePlugin
    定义全局变量
plugins: [
  new webpack.DefinePlugin({
    'process.env': 'development'
  }),
]

// 直接引用
console.log(DESCRIPTION)

copy-webpack-plugin
会把在index.html中自己引入的静态文件,复制到dist目录中

const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public/js/*.js',
          to: path.resolve(__dirname, 'dist', 'js'),
          flatten: true,
        },
      ],
    }),
  ],
}

深拷贝

let deepClone = (obj) => {
    let getType = (o) => {
        if (o == null) {
            return 'null';
        } else if (0 == undefined) {
            return 'undefined';
        } else {
            return Object.prototype.toString.call(o).slice(-7, -1)
        }
    }
    let result;
    if (getType(obj) == 'Object') {
        result = {};
    } else if (getType(obj) == 'Array') {
        result = [];
    } else {
        return obj
    }

    for (let key in obj) {  
        let copy = obj[key];
        if (getType(copy) == 'Object') {
            result[key] = deepClone(copy)
        } else if (getType(copy) == 'Array') {
            result[key] = deepClone(copy)
        } else {
            result[key] = obj[key]
        }
    }
    return result;
}

设计模式

  1. 单例模式
class student {
    constructor(name) {
        this.name = name;
    }
    getName = () => {
        return this.name;
    }
}

let singleMode = (()=>{
    let instance = null;
    return function(name){
        if(!instance){
            instance = new student(name)
        }
        return instance;
    }
})();

let p = new singleMode('nys')

你可能感兴趣的:(前端,css,ES6)