专栏首页前端迷中高级前端高频面试题分享

中高级前端高频面试题分享

使用setTimeout代替setInterval进行间歇调用

var executeTimes = 0;var intervalTime = 500;var intervalId = null;
// 放开下面的注释运行setInterval的DemointervalId = setInterval(intervalFun,intervalTime);// 放开下面的注释运行setTimeout的Demo// setTimeout(timeOutFun,intervalTime);
function intervalFun(){    executeTimes++;    console.log("doIntervalFun——"+executeTimes);    if(executeTimes==5){        clearInterval(intervalId);    }}
function timeOutFun(){    executeTimes++;    console.log("doTimeOutFun——"+executeTimes);    if(executeTimes<5){        setTimeout(arguments.callee,intervalTime);    }}

代码比较简单,我们只是在setTimeout的方法里面又调用了一次setTimeout,就可以达到间歇调用的目的。

重点来了,为什么作者建议我们使用setTimeout代替setInterval呢?setTimeout式的间歇调用和传统的setInterval间歇调用有什么区别呢?

区别在于,setInterval间歇调用,是在前一个方法执行前,就开始计时,比如间歇时间是500ms,那么不管那时候前一个方法是否已经执行完毕,都会把后一个方法放入执行的序列中。这时候就会发生一个问题,假如前一个方法的执行时间超过500ms,加入是1000ms,那么就意味着,前一个方法执行结束后,后一个方法马上就会执行,因为此时间歇时间已经超过500ms了。

var executeTimes = 0;var intervalTime = 500;var intervalId = null;var oriTime = new Date().getTime();
// 放开下面的注释运行setInterval的Demo// intervalId = setInterval(intervalFun,intervalTime);// 放开下面的注释运行setTimeout的DemosetTimeout(timeOutFun,intervalTime);
function intervalFun(){    executeTimes++;    var nowExecuteTimes = executeTimes;    var timeDiff = new Date().getTime() - oriTime;    console.log("doIntervalFun——"+nowExecuteTimes+", after " + timeDiff + "ms");    var delayParam = 0;    sleep(1000);    console.log("doIntervalFun——"+nowExecuteTimes+" finish !");    if(executeTimes==5){        clearInterval(intervalId);    }}
function timeOutFun(){    executeTimes++;    var nowExecuteTimes = executeTimes;    var timeDiff = new Date().getTime() - oriTime;    console.log("doTimeOutFun——"+nowExecuteTimes+", after " + timeDiff + "ms");    var delayParam = 0;    sleep(1000);    console.log("doTimeOutFun——"+nowExecuteTimes+" finish !");    if(executeTimes<5){        setTimeout(arguments.callee,intervalTime);    }}
function sleep(sleepTime){    var start=new Date().getTime();    while(true){        if(new Date().getTime()-start>sleepTime){            break;            }    }}

(这里使用大牛提供的sleep函数来模拟函数运行的时间) 执行setInterval的Demo方法,看控制台

doIntervalFun——1, after 500msVM2854:19 doIntervalFun——1 finish !VM2854:16 doIntervalFun——2, after 1503msVM2854:19 doIntervalFun——2 finish !VM2854:16 doIntervalFun——3, after 2507msVM2854:19 doIntervalFun——3 finish !VM2854:16 doIntervalFun——4, after 3510msVM2854:19 doIntervalFun——4 finish !VM2854:16 doIntervalFun——5, after 4512msVM2854:19 doIntervalFun——5 finish !

可以发现,fun2和fun1开始的间歇接近1000ms,刚好就是fun1的执行时间,也就意味着fun1执行完后fun2马上就执行了,和我们间歇调用的初衷背道而驰。

我们注释掉setInterval的Demo方法,放开setTimeout的Demo方法,运行,查看控制台

doTimeOutFun——1, after 500msVM2621:32 doTimeOutFun——1 finish !VM2621:29 doTimeOutFun——2, after 2001msVM2621:32 doTimeOutFun——2 finish !VM2621:29 doTimeOutFun——3, after 3503msVM2621:32 doTimeOutFun——3 finish !VM2621:29 doTimeOutFun——4, after 5004msVM2621:32 doTimeOutFun——4 finish !VM2621:29 doTimeOutFun——5, after 6505msVM2621:32 doTimeOutFun——5 finish !

这下终于正常了,fun1和fun2相差了1500ms = 1000 + 500,fun2在fun1执行完的500ms后执行。

闭包

  1. 实现私有变量 如果我们写一个函数,里面有一个name值,我们可以允许任何人访问这个name属性,但是只有少部分人,可以修改这个name属性,我们就可以使用闭包,可以在setName值中,写哪些人具有修改的权限。
var person = function(){       //变量作用域为函数内部,外部无法访问,不会与外部变量发生重名冲突       var name = "FE";          return {      //管理私有变量          getName : function(){              return name;          },          setName : function(newName){              name = newName;          }       }   }; 
  1. 数据缓存 假如说我们执行一个计算量很大函数,返回一个值,而这个值在其他函数中还有应用,这种情况下使用闭包,可以将该数据保存在内存中,供其他的函数使用(这是在其他博客中看到的,具体不是很清楚,如果有兴趣,可以自己查阅相关文献)。

缺点: 造成内存消耗过大,如果处理不当,会造成内存泄漏

数组中的forEach和map的区别

大多数情况下,我们都要对数组进行遍历,然后经常用到的两个方法就是forEach和map方法。 先来说说它们的共同点

  • 相同点 都是循环遍历数组中的每一项 forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项),index(索引值),arr(原数组) 匿名函数中的this都是指向window 只能遍历数组 都不会改变原数组
  • 区别 map方法

1.map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。 2.map方法不会对空数组进行检测,map方法不会改变原始数组。 3.浏览器支持:chrome、Safari1.5+、opera都支持,IE9+,

array.map(function(item,index,arr){},thisValue)
var arr = [0,2,4,6,8];var str = arr.map(function(item,index,arr){    console.log(this); //window    console.log("原数组arr:",arr); //注意这里执行5次    return item/2;},this);console.log(str);//[0,1,2,3,4]

若arr为空数组,则map方法返回的也是一个空数组。

forEach方法

  1. forEach方法用来调用数组的每个元素,将元素传给回调函数 2.forEach对于空数组是不会调用回调函数的。
Array.forEach(function(item,index,arr){},this)var arr = [0,2,4,6,8];var sum = 0;var str = arr.forEach(function(item,index,arr){    sum += item;    console.log("sum的值为:",sum); //0 2 6 12 20    console.log(this); //window},this)console.log(sum);//20console.log(str); //undefined

无论arr是不是空数组,forEach返回的都是undefined。这个方法只是将数组中的每一项作为callback的参数执行一次。

for in和for of的区别

遍历数组通常使用for循环,ES5的话也可以使用forEach,ES5具有遍历数组功能的还有map、filter、some、every、reduce、reduceRight等,只不过他们的返回结果不一样。但是使用foreach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。

Array.prototype.method=function(){  console.log(this.length);}var myArray=[1,2,4,5,6,7]myArray.name="数组"for (var index in myArray) {  console.log(myArray[index]);}

使用for in 也可以遍历数组,但是会存在以下问题:

  1. index索引为字符串型数字,不能直接进行几何运算
  2. 遍历顺序有可能不是按照实际数组的内部顺序
  3. 使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法method和name属性

所以for in更适合遍历对象,不要使用for in遍历数组。

那么除了使用for循环,如何更简单的正确的遍历数组达到我们的期望呢(即不遍历method和name),ES6中的for of更胜一筹.

Array.prototype.method=function(){  console.log(this.length);}var myArray=[1,2,4,5,6,7]myArray.name="数组";for (var value of myArray) {  console.log(value);}

记住,for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。

for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name

遍历对象 通常用for in来遍历对象的键名

Object.prototype.method=function(){  console.log(this);}var myObject={  a:1,  b:2,  c:3}for (var key in myObject) {  console.log(key);}

for in 可以遍历到myObject的原型方法method,如果不想遍历原型方法和属性的话,可以在循环内部判断一下, hasOwnPropery方法可以判断某属性是否是该对象的实例属性

for (var key in myObject) {  if(myObject.hasOwnProperty(key)){    console.log(key);  }}

同样可以通过ES5的 Object.keys(myObject)获取对象的实例属性组成的数组,不包括原型方法和属性。

Object.prototype.method=function(){  console.log(this);}var myObject={  a:1,  b:2,  c:3}Object.keys(myObject).forEach(function(key,index){      console.log(key,myObject[key])})

实现EventEmitter方法

EventEmitter 的核心就是事件触发与事件监听器功能的封装

class EventEmitter {    constructor() {        this.events = {};    }
    on(eventName, fn) {        let fnList = this.events[eventName] || [];        fnList.push(fn)        if (eventName) {            this.events[eventName] = fnList;        }    }
    emit(eventName, ...agr) {        let funcs = this.events[eventName];        if (funcs && funcs.length) {            for (let j = 0; j < funcs.length; j++) {                funcs[j](...agr);            }        }    }    off(eventName, fn) {        let funcs = this.events[eventName];        if (fn) {            this.events[eventName].splice(fn, 1);        } else {            delete this.events[eventName]        }    }}

let、var、const区别

var 第一个就是作用域的问题,var不是针对一个块级作用域,而是针对一个函数作用域。举个例子:

function runTowerExperiment(tower, startTime) {  var t = startTime;
  tower.on("tick", function () {    ... code that uses t ...  });  ... more code ...}

这样是没什么问题的,因为回调函数中可以访问到变量t,但是如果我们在回调函数中再次命名了变量t呢?

function runTowerExperiment(tower, startTime) {  var t = startTime;
  tower.on("tick", function () {    ... code that uses t ...    if (bowlingBall.altitude() <= 0) {      var t = readTachymeter();      ...    }  });  ... more code ...}

后者就会将前者覆盖。

第二个就是循环的问题。 看下面例子:

var messages = ["Meow!", "I'm a talking cat!", "Callbacks are fun!"];
for (var i = 0; i < messages.length; i++) {    setTimeout(function () {        document.write(messages[i]);    },i*1500);}

输出结果是:undefined 因为for循环后,i置为3,所以访问不到其值。

let 为了解决这些问题,ES6提出了let语法。let可以在{},if,for里声明,其用法同var,但是作用域限定在块级。但是javascript中不是没有块级作用域吗?这个我们等会讲。还有一点很重要的就是let定义的变量不存在变量提升。

变量提升 这里简单提一下什么叫做变量提升。

var v='Hello World'; (function(){     alert(v);     var v='I love you'; })()

上面的代码输出结果为:undefined。

为什么会这样呢?这就是因为变量提升,变量提升就是把变量的声明提升到函数顶部,比如:

(function(){     var a='One';     var b='Two';     var c='Three'; })()

实际上就是:

(function(){     var a,b,c;     a='One';     b='Two';     c='Three'; })()

所以我们刚才的例子实际上是:

var v='Hello World'; (function(){     var v;    alert(v);     v='I love you'; })()

所以就会返回undefined啦。

这也是var的一个问题,而我们使用let就不会出现这个问题。因为它会报语法错误:

{         console.log( a );   // undefined    console.log( b );   // ReferenceError!          var a;    let b;     }

再来看看let的块级作用域。

function getVal(boo) {    if (boo) {        var val = 'red'        // ...        return val    } else {        // 这里可以访问 val        return null    }    // 这里也可以访问 val}

而使用let后:

function getVal(boo) {    if (boo) {        let val = 'red'        // ...        return val    } else {        // 这里访问不到 val        return null    }    // 这里也访问不到 val}

同样的在for循环中:

function func(arr) {    for (var i = 0; i < arr.length; i++) {        // i ...    }    // 这里访问得到i}

使用let后:

function func(arr) {    for (let i = 0; i < arr.length; i++) {        // i ...    }    // 这里访问不到i}

也就是说,let只能在花括号内部起作用。

const 再来说说const,const代表一个值的常量索引。

const aa = 11;alert(aa) //11aa = 22;alert(aa) //11

但是常量的值在垃圾回收前永远不能改变,所以需要谨慎使用。

还有一条需要注意的就是和其他语言一样,常量的声明必须赋予初值。即使我们想要一个undefined的常量,也需要声明:

const a = undefined;

块级作用域 最后提一下刚才说到的块级作用域。

在之前,javascript是没有块级作用域的,我们都是通过()来模拟块级作用域。

(function(){ //这里是块级作用域 })();

但是在ES6中,{}就可以直接代码块级作用域。所以{}内的内容是不可以在{}外访问得到的。

我们可以看看如下代码:

if (true) {    function foo() {        document.write( "1" );    }}else {    function foo() {        document.write( "2" );    }}
foo();      // 2

在我们所认识的javascript里,这段代码的输出结果为2。这个叫做函数声明提升,不仅仅提升了函数名,也提升了函数的定义。如果你基础不扎实的话,可以看看这篇文章:深入理解javascript之IIFE

但是在ES6里,这段代码或抛出ReferenceErroe错误。因为{}的块级作用域,导致外面访问不到foo(),也就是说函数声明和let定义变量一样,都被限制在块级作用域中了。

事件循环

从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue

简要介绍:谈谈promise.resove,setTimeout,setImmediate,process.nextTick在EvenLoop队列中的执行顺序

1.问题的引出 event loop都不陌生,是指主线程从“任务队列”中循环读取任务,比如

例1:

setTimeout(function(){console.log(1)},0);
console.log(2)
//输出2,1

在上述的例子中,我们明白首先执行主线程中的同步任务,当主线程任务执行完毕后,再从event loop中读取任务,因此先输出2,再输出1。

event loop读取任务的先后顺序,取决于任务队列(Job queue)中对于不同任务读取规则的限定。比如下面一个例子:

例2:

setTimeout(function () {  console.log(3);}, 0);
Promise.resolve().then(function () {  console.log(2);});console.log(1);//输出为  1  2 3

先输出1,没有问题,因为是同步任务在主线程中优先执行,这里的问题是setTimeout和Promise.then任务的执行优先级是如何定义的。

2 . Job queue中的执行顺序 在Job queue中的队列分为两种类型:macro-task和microTask。我们举例来看执行顺序的规定,我们设

macro-task队列包含任务: a1, a2 , a3 micro-task队列包含任务: b1, b2 , b3

执行顺序为,首先执行marco-task队列开头的任务,也就是 a1 任务,执行完毕后,在执行micro-task队列里的所有任务,也就是依次执行b1, b2 , b3,执行完后清空micro-task中的任务,接着执行marco-task中的第二个任务,依次循环。

了解完了macro-task和micro-task两种队列的执行顺序之后,我们接着来看,真实场景下这两种类型的队列里真正包含的任务(我们以node V8引擎为例),在node V8中,这两种类型的真实任务顺序如下所示:

macro-task(宏任务)队列真实包含任务: script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering

micro-task(微任务)队列真实包含任务: process.nextTick, Promises, Object.observe, MutationObserver

由此我们得到的执行顺序应该为:

script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering

在ES6中macro-task队列又称为ScriptJobs,而micro-task又称PromiseJobs

3 . 真实环境中执行顺序的举例

(1) setTimeout和promise

例3:

setTimeout(function () {  console.log(3);}, 0);
Promise.resolve().then(function () {  console.log(2);});
console.log(1);

我们先以第1小节的例子为例,这里遵循的顺序为:

script(主程序代码)——>promise——>setTimeout 对应的输出依次为:1 ——>2————>3 (2) process.nextTick和promise、setTimeout

例子4:

setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){   console.log(2);   resolve();}).then(function(){console.log(3)}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);//输出2,6,5,3,4,1

这个例子就比较复杂了,这里要注意的一点在定义promise的时候,promise构造部分是同步执行的,这样问题就迎刃而解了。

首先分析Job queue的执行顺序:

script(主程序代码)——>process.nextTick——>promise——>setTimeout

I) 主体部分: 定义promise的构造部分是同步的, 因此先输出2 ,主体部分再输出6(同步情况下,就是严格按照定义的先后顺序)

II)process.nextTick: 输出5

III)promise: 这里的promise部分,严格的说其实是promise.then部分,输出的是3,4

IV) setTimeout : 最后输出1

综合的执行顺序就是: 2——>6——>5——>3——>4——>1

(3)更复杂的例子

setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){   console.log(2);   setTimeout(function(){resolve()},0)}).then(function(){console.log(3)}).then(function(){console.log(4)});
process.nextTick(function(){console.log(5)});
console.log(6);
//输出的是  2 6 5 1 3 4

这种情况跟我们(2)中的例子,区别在于promise的构造中,没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,因此3,4最后输出。

typeof和instanceof

ECMAScript是松散类型的,一次需要一种手段来检测给定变量的数据类型,typeof操作符(注意不是函数哈!)就是负责提供这方面信息的

typeof 可以用于检测基本数据类型和引用数据类型。

语法格式如下:

typeof variable

返回6种String类型的结果:

  • "undefined" - 如果这个值未定义
  • "boolean" - 如果这个值是布尔值
  • "string" - 如果这个值是字符串
  • "number" - 如果这个值是数值
  • "object" - 如果这个值是对象或null
  • "function" - 如果这个值是函数 示例:
console.log(typeof 'hello'); // "string"console.log(typeof null); // "object"console.log(typeof (new Object())); // "object"console.log(typeof(function(){})); // "function"

typeof主要用于检测基本数据类型:数值、字符串、布尔值、undefined, 因为typeof用于检测引用类型值时,对于任何Object对象实例(包括null),typeof都返回"object"值,没办法区分是那种对象,对实际编码用处不大。

instanceof 用于判断一个变量是否某个对象的实例

在检测基本数据类型时typeof是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大,通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。此时我们可以使用ECMAScript提供的instanceof操作符。

语法格式如下:

result = variable instanceof constructor

返回布尔类型值:

  • true - 如果变量(variable)是给定引用类型的实例,那么instanceof操作符会返回true
  • false - 如果变量(variable)不是给定引用类型的实例,那么instanceof操作符会返回false 示例:
function Person(){}function Animal(){}var person1 = new Person();var animal1 = new Animal();console.log(person1 instanceof Person); // trueconsole.log(animal1 instanceof Person); // falseconsole.log(animal1 instanceof Object); // trueconsole.log(1 instanceof Person);   //false

var oStr =  new String("hello world");console.log(typeof(oStr));      // objectconsole.log(oStr instanceof String);console.log(oStr instanceof Object);
// 判断 foo 是否是 Foo 类的实例
function Foo(){}var foo = new Foo();console.log(foo instanceof Foo);
// instanceof 在继承中关系中的用法console.log('instanceof 在继承中关系中的用法');

function Aoo(){}function Foo(){}
Foo.prototype = new Aoo();var fo = new Foo();
console.log(fo instanceof Foo);console.log(fo instanceof Aoo)

根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符会始终返回true。如果使用instanceof 操作符检测基本类型值时,该操作符会始终返回false,因为基本类型不是对象。

console.log(Object.prototype.toString.call(null));// [object Null]undefinedconsole.log(Object.prototype.toString.call([1,2,3]));//[object Array]undefinedconsole.log(Object.prototype.toString.call({}));// [object Object]

常见的继承的几种方法

原型链继承

定义 利用原型让一个引用类型继承另外一个引用类型的属性和方法 代码

function SuperType(){    this.property = 'true';}
SuperType.prototype.getSuperValue = function(){    return this.property;}
function SubType(){    this.subProperty = 'false';}
SubType.prototype = new SuperType();SubType.prototype.getSubValue = function(){    return this.subProperty;}
var instance = new SubType();alert(instance.getSuperValue());//true
  • 优点 简单明了,容易实现,在父类新增原型属性和方法,子类都能访问到。
  • 缺点 包含引用类型值的函数,所有的实例都指向同一个引用地址,修改一个,其他都会改变。不能像超类型的构造函数传递参数

构造函数继承 定义 在子类型构造函数的内部调用超类型的构造函数

代码

function SuperType(){    this.colors = ['red','yellow'];}
function SubType(){    SuperType.call(this);}
var instance1 = new SubType();instance1.colors.push('black');

var instance2 = new SubType();instance2.colors.push('white');
alert(instance1.colors);//'red','yellow','black'
alert(instance2.colors);//'red','yellow','white'
  • 优点 简单明了,直接继承了超类型构造函数的属性和方法
  • 缺点 方法都在构造函数中定义,因此函数复用就无从谈起了,而且超类型中的原型的属性和方法,对子类型也是不可见的,结果所有的类型只能使用构造函数模式。

组合继承

定义 使用原型链实现多原型属性和方法的继承,使用构造函数实现实例的继承

代码

function SuperType(name){    this.name = name;    this.colors = ['red','black'];}
SuperType.prototype.sayName = function(){   alert(this.name); }

function SubType(name,age){    SuperType.call(this,name);    this.age = age;}
SubType.protptype = new SuperType();SubType.protptype.sayAge = function(){    alert(this.age);
}
  • 优点 解决了构造函数和原型继承中的两个问题
  • 缺点 无论什么时候,都会调用两次超类型的构造函数

本文分享自微信公众号 - 前端迷(love_frontend),作者:frank

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 原生JS的知识系统梳理

    笔者最近在对原生JS的知识做系统梳理,因为我觉得JS作为前端工程师的根本技术,学再多遍都不为过。打算来做一个系列,一共分三次发,以一系列的问题为驱动,当然也会有...

    前端迷
  • 70个JavaScript面试问题

    它们是属于虚值,可以使用Boolean(value)或!!value将其转换为布尔值时,值为false。

    前端迷
  • 喜马拉雅、ctrip、b站、流利说、蜻蜓FM、爱回收前端面试经历

    我的回答是[1,2,6,4,3,5]。这道题目主要考对JS宏任务和微任务的理解程度,JS的事件循环中每个宏任务称为一个Tick(标记),在每个标记的末尾会追加一...

    前端迷
  • 【面试】386- JavaScript 面试 20 个核心考点

    Javascript是前端面试的重点,本文重点梳理下 Javascript 中的常考基础知识点,然后就一些容易出现的题目进行解析。限于文章的篇幅,无法将知识点讲...

    pingan8787
  • es6(三):es6中函数的扩展(参数默认值、rest参数、箭头函数)

    1.函数可以设置参数默认值 1 function test1(x,y=1){ 2 console.log(x,y) 3 } 4 te...

    用户1149564
  • 06JavaScript作用域与对象

    即使我们在定义这个函数之前调用它,函数仍然可以工作。这是因为在 JavaScript 中执行上下文的工作方式造成的。

    Dreamy.TZK
  • [第13期] 掌握前端面试基础系列一: ES6

    这里我们可以看到, 第一行中的a虽然还没声明, 但是我们用起来却不会报错。这种情况, 就是变量声明的提升。

    用户6900878
  • ES6 学习笔记之函数的拓展

    本文记录了一些 ES6 函数相关的改动,比较重要的就是箭头函数及箭头函数内部 this 的变化,其他一些不常见的概念我也仅仅是看了看,并没有实际操作测试效果。待...

    我与梦想有个约会
  • 横扫 JS 面试核心考点

    Javascript是前端面试的重点,本文重点梳理下Javascript中的常考知识点,然后就一些容易出现的题目进行解析。限于文章的篇幅,无法将知识点讲解的面面...

    grain先森
  • JS面向对象笔记二

    注意点:当构造函数里面有return关键字时,如果返回的是非对象,new命令会忽略返回的信息,最后返回时构造之后的this对象;   如果return返回的是与...

    tandaxia

扫码关注云+社区

领取腾讯云代金券