阿里电话面试

在今天接到了阿里的面试电话,最初很紧张,后来放松了一些。面试官很好,奈何自己水平不够,将这次的问题记录下来。长个记性。也明白了自己对原生js掌握还是不够熟练。写下此文,与君共勉

基础篇:

介绍一下对盒子模型的理解 :

我的回答:盒子模型有两种,一种是非IE浏览器的盒子模型、一种是IE的盒子模型;非IE浏览器盒子模型由 margin+border+padding+content 组成;而IE的盒子模型是将padding包含在了content中。

面试官回答:用谷歌浏览器的控制台用的多吗,里面可以看盒子模型,你刚刚的回答有误。

正解:

1.盒子模型分为 W3C 标准盒子模型和IE 的盒子模型

2.W3C盒子模型包括margin、border、padding、content,并且元素的width = content的宽度

3.IE的盒子模型与W3C盒子模型唯一区别就是元素的宽度和高度,IE盒子模型元素width = content+padding+border

总结:

1.自己之前就被问过这个问题,当时想着再问这种问题不会再犯同样的错误。结果还是犯了。只能说还是不够理解。

拓展:

1.个人觉得IE的盒子模型比W3C的要定义的更好,更加符合现实生活中的情况,宽度应该将内填充和border包含在内。

2.CSS3中也出现了box-sizing这个样式,border-box符合IE盒子模型,content-box符合W3C盒子模型;可能是W3C也觉得自己定义的不够好吧

严格模式和非严格模式有什么区别:

我的回答 : 定义变量必须要赋初始值,否则报错;(当时只想到这一个,并且还是个错的答案。。)

面试官回答 : 那直接下一个问题,可以去看一下严格模式还是比较有用的

正解 :

1.变量必须声明才能使用(在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种写法)

2.禁止使用with语句(因为with语句无法在编译时就确定,属性到底归属于哪个对象,严格模式有利于编译效率提高)

3.创建eval作用域(正常模式下,js有两种变量作用域,全局作用域和局部作用域,正常模式下eval语句作用域取决于它处于全局作用域还是函数作用域,严格模式下eval语句本身就是作用域,不能够生成全局变量,所生成的变量只能用于eval内部)

4.禁止this关键字指向全局对象(严格模式下全局作用域中定义的函数中的this为undefined)。例如:

     function f(){
       return !this; //返回的是false,因为this指向的是全局对象,!对象 == false
     }
     function f(){
       "use strict"
       return !this; //返回的是true,因为严格模式下,this的值为undefined,!undefined == true
     }

5.禁止在函数内部遍历调用栈( caller:调用当前函数的函数的引用,即外层函数的引用; )

    function f1(){
        "use strict";
        f1.caller; //报错
        f1.arguments; //报错
    }
    f1();

6.严格模式下无法删除变量。只有conifgurable设置为true的对象属性才能被删除

    "use strict"

    var x ;

    delete x; //严格模式下报语法错误

    var o = Object.create(null,{'x':{

        value: 1,

        configurable: true

    }})

    delete o.x; //删除成功

7.显示报错(正常模式下对一个对象的只读属性进行赋值,不会报错,只会默默失败。严格模式下将报错)

    "use strict";

    var o = {};

    Object.defineProperty(o,"v",{value: 1,writable: false});

    o.v = 2; //报错,因为o.v属性是不能被修改的,严格模式会报错,正常模式会失败但不报错

8.严格模式下,对禁止扩展的对象添加新属性,会报错

    "use strict";

    var o = {};

    Object.preventExtensions(o);//禁止o对象有拓展属性

    o.v = 1; //报错

9.严格模式下,删除一个不可删除的属性,报错

    "use strict";
    delete Object.prototype; //报错

10.对象拥有多个同名属性,严格模式报错。正常模式会默认值为最后一个

11.函数不能有重名的参数,严格模式会报错,正常模式可以通过arguments[i]来获取对应的参数

12.禁止八进制写法,正常情况下整数第一位为0代表八进制,严格模式下整数第一位为0则报错

13.不准对arguments赋值

14.严格模式下的arguments不在追踪参数的变化

    function fn(a){
        a=2;
        return [a,arguments[0]];
    }
    fn(1); //正常模式返回值 [2,2]
    "use strict"
    function fn(a){
        a = 2;
        return [a,arguments[0]];
    }
    fn(1); //严格模式返回值 [2,1] 参数传进来是多少就是多少,arguments不会变化

15.禁止使用arguments.callee(无法在匿名函数内部调用自身了。arguments.callee指向的就是该函数本身)

    var f = function (){
        return arguments.callee;
    }
    f(); //报错

深拷贝和浅拷贝:

我的回答:浅拷贝对与引用类型的拷贝是指向同一个地址的,深拷贝则是全新的一个值,即使其中存在着引用类型

面试官回答:那怎样可以实现深拷贝?有哪些方式?说出你知道的深拷贝方式?

我的回答:

1.使用JSON.parse(JSON.stringfy( obj ))进行深拷贝

2.使用Object.assign({},obj)实现深拷贝(大错特错。。。。Object.assign()是浅拷贝,只拷贝属性值,如果属性值是引用,那也只拷贝那个引用值)

正解:

1.使用JSON.parse(JSON.stringfy(obj))

2.使用for in 实现深拷贝(for in会遍历出对象的所有可枚举属性,再加上递归就可以实现深拷贝)

    function deepClone(src){
        var clone = src;

        //如果是数组
        if(src instanceof Array){
            clone = [];
            for(let key in src){
                clone[key] = deepClone(src[key])
            }
            return clone;
        }

        //如果是对象
        if(src instanceof Object){
            clone = {};
            for(let key in src){
                if(src.hasOwnProperty(key)){ //判断是否为继承的属性
                    clone[key] = deepClone(src[key])
                }
            }
            return clone;
        }
        //都不是,直接返回当前值
        return src;
    }

对原型链的了解:

我的回答:js的继承是基于原型链的,每一个对象上面都有一个prototype属性,指向它的原型对象。

面试官:说一说下划线"__proto__"和"prototype"的区别

我的回答:“__proto__”是浏览器提供的指向原型的属性,prototype是原生js中指向原型的属性

正解:

1.每个对象都有一个私有属性([[Prototype]])它指向它的原型对象(prototype)。该prototype对象又具有一个自己的prototype,层层向上直到一个对象的原型为null。根据定义,null没有原型,并且作为原型链中的最后一个环节。

2."__proto__"是许多浏览器实现的属性,可以用来指向该对象的原型对象,现在的ECMAScript6可以通过Object.getPrototypeOf()和Object.setPrototypeOf()来访问原型属性[[Prorotype]]找到原型对象

3.不要把对象的[[Prortotype]]和构造函数的prototype属性弄混。someObject.[[Prototype]]是用于指向someObject的原型。被构造函数创建的实例对象的[[prototype]]指向构造函数的 prototype属性。构造函数的[[Prototype]]是Function.prototype例如:

var obj = {
    name:"张三"
}
Object.getPrototypeOf(obj) == obj.__proto__  ;//true
//都是指向obj的原型对象

function Person(name){
    this.name = name
}
var p1 = new Person('p1');
Object.getPrototypeOf(p1) == Person.prototype; //true
//  p1对象原型 == Person这个构造函数的prototype属性

//构造函数的[[Prototype]] === Function的原型对象
Object.getPrototypeOf(Person) === Function.prototype; //true

js中的数据类型:

我的回答:Number,String,Null,Object,boolean,Symbol

面试官:说一下Symbol的了解,怎样让两个Symbol相等

我的回答:Symbol是不可修改的值,没有两个相同的Symbol,可以用来作为对象属性,避免对象上的属性被覆盖。让两个Symbol相等这个不清楚

正解:

1.数据类型分为原始数据类型和引用数据类型,原始数据类型包括:Boolean、Null、Undefined、Number、String、Symbol。引用数据类型为Object。原始数据类型:除了Object以外的所有类型都是不可变的(值本身无法被改变)

2.Symbol是ES6中新增的一种数据类型,它的特性就是唯一并且不可修改。可以用来作为属性名,解决属性名容易冲突的问题。

3.让两个Symbol相等,使用Symbol.for('foo')这个方法,接收一个字符串作为参数,这样生成的新的Symbol会先在全局环境中查找有没有以'foo'为key是否存在,如果存在了,返回的就是之前的Symbol,否则返回新的Symbol;例如:

let s1 = Symbol(); 
let s2 = Symbol();
s1 == s2 //false

let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 == s2 //false 因为这里传的'foo'其实仅仅代表一个注释而已

let s1 = Symbol.for('foo') //返回新Symbol,并且将key='foo'登记在全局环境中
let s2 = Symbol.for('foo') //先找全局是否存在key='foo'的Symbol,如果有直接返回该Symbol,否则生成新Symbol
let s3 = Symbol.for('xxx') //先找key='xxx'的Symbol是否存在
s1 == s2 //true 因为他们的key都一样
s1 == s2 //false

4.Symbol() 和 Symbol.for() 的区别就在于Symbol.for()会登记在全局环境中供搜索,而Symbol()不会。所以每次Symbol() 必定得到的是不同的Symbol

遍历对象的方式:

我的回答:for in ,只想到了这一个。。

面试官:直接下一个问题

正解:

1.for in 遍历(以任意顺序遍历可枚举属性,包括原型链上的属性;注意:迭代过程中最好不要在对象上进行添加修改或者删除属性的操作,除非是对当前正在被访问的属性。因为for in遍历没有一定顺序,不保证迭代过程中新增的属性被访问到,也不保证修改后的属性会在修改前或者修改后被访问到。)

var  obj = {
    a:"a",
    b:"b",
    c:"c"
}
for(let i in obj){
    console.log(i);
}
// a , b , c

2.Object.keys() (返回给定对象的自身可枚举属性组成的数组,数组中属性名排列顺序和使用for in 循环遍历该对象时返回的顺序一致,和for in的区别就是只枚举对象自身可枚举属性)

var obj = {
    a:"a",
    b:"b",
    c:"c"
}
Object.keys(obj)
// ["a","b","c"]

3.Object.getOwnPropertyNames() (返回给定对象的所有自身属性的属性名组成的数组,包括不可枚举属性但是不包括Symbol作为名称的属性,只返回自身的,不返回继承来的属性)

var obj = {
    a:"a",
    b:"b",
    c:"c"
}
Object.getOwnPropertyNames(obj)
// ["a","b","c"]

4.Reflect.ownKeys() (静态方法,返回一个有目标对象自身属性键组成的数组,返回值等于 Object.getOwnPropertyNames() + Object.getOwnPropertySymbols() ,可得到的属性:自己的可枚举的、自己的不可枚举的、自己的Symbol为作为属性名的)

var sym = Symbol.for('sy')
var obj = {
    a:"a",
    b:"b",
    c:"c",
    sym:"symbol"
}
Reflect.ownKeys(obj)
// ["a","b","c","sym"]

var sym = Symbol.for('sy')
var obj = {
    a:"a",
    b:"b",
    c:"c",
    [sym]:"symbol"
}
Reflect.ownKeys(obj)
// ["a","b","c",Symbol(sy)]

对ES6的了解:

我的回答: 新增了let,const,以及promise,定义对象的方式使用class定义,解构赋值,剩余参数,箭头函数。

正解:

1.let,const,var的区别,let不会变量声明提升,并且let不能够重复声明一个变量,var变量声明提升,并且var可以重复声明一个变量。const是用来定义常量的,定义之后就不能够修改了并且const必须声明赋值同时进行。不能只声明然后赋值。

alert(a);
let a = '1';
//执行结果是报错,a is not defined

alert(b);
var b = '1';
//执行结果是alert(undefined),因为b的声明提升了,但是赋值还没开始就alert了。
//等价于如下代码
var b;
alert(b);
b = '1';

const c; //报错,Missing initializer in const declaration
const c=1; //使用const必须声明时就要初始化赋值

2.解构赋值

1.数组的解构赋值

注意:如果等号的右边不是数组(或者严格地说,不是可遍历的结构,即转成对象后不具备Iterator接口)那么将会报错

let [a,b,c] = [1,2,3]; //等价于let a=1;let b=2;let c=3;

let [foo, [[bar], baz]] = [1, [[2], 3]]; 
let [ , , third] = ['1' , '2', '3']; //third='3'
let [x, ,z] = [1,2,3] //x=1,z=3

let [one , ...other] = [1,2,3,4,5,6,7];
//one=1, other=[2,3,4,5,6,7]

let [foo] = [];
let [bar, foo] = [1];
// foo=undefined,  解构不成功,值为undefined

// 报错,因为右边的值都没有实现Iterator接口
let [foo] = 1;
let [foo] = false; 
let [foo] = NaN; 
let [foo] = undefined; 
let [foo] = null;
let [foo] = {}; 

//可以指定默认值(只有当右边的值 === undefined时,默认值才起作用)
let [x,y='b'] = ['a'];
let [x = 1] = [undefined]; //x=1
let [x = 1] = [null]; //x=null

 //默认值是表达式的话,这个表达式是惰性求值的,即默认值生效时才求表达式的值
 function fn(){
     console.log('aaa');
 }
 let [x = fn()] = [1];  //因为右边的 1 不为undefined,所以默认值不生效,压根不会执行fn() 方法,也就不会console.log('aaa')

2.对象的解构赋值

注意:对象与数组的解构不同,数组的元素必须是按次序排列,而对象属性没有次序,变量必须与属性同名才能赋值

let {foo,bar} = {foo:'aaa',bar:'bbb'}; 
//等价于,赋值真正的是赋值给 后面的 foo和bar,因为ES6允许属性和值同名只写一个,所以才是上面的那种简写
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" }; 

//默认值生效的条件是,对象的属性值严格等于undefined。
var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null

//如果解构失败,变量的值等于undefined。
let {foo} = {bar: 'baz'};
foo // undefined

3.字符串的解构赋值

注意:字符串实际上是被转换成一个类数组的对象,类数组对象的length属性亦可以进行解构赋值

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

let {length : len} = 'hello';
len // 5

4.数值和布尔值的解构赋值

注意:等号右边为数组和布尔值时,先将数值和布尔值转为对象,然后就和对象的解构赋值一样了

//解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

5.函数参数的解构赋值

注意:函数参数解构赋值的默认值要分辨清两种情况

//函数参数为 对象,给对象中的 x 和 y 属性默认值为0;  参数格式:{x=0,y=0}={}
function move({x = 0, y = 0} = {}) {
  return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]


//函数参数为 x,y 而不是对象;  参数格式:{x,y}={x:0,y:0} 
function move({x,y} = {x:0,y:0}){
    return [x,y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

解构赋值作用:

  • 交换变量值(在写排序的时候经常需要交换两个变量的值,需要定义一个临时变量作为中转,使用解构赋值可以少声明一个变量,并且阅读性更好)

    let x = 1;
    let y = 2;
    [x,y] = [y,x];
    
  • 从函数返回多个值(当一个对象中存在很多属性时,只需要其中某几个属性时,就可以只引用这几个 属性)

    //返回一个数组
    function example(){
        return [1,2,3];
    }
    let [a,b,c] = example();
    
    //返回对象
    function example(){
        return {
            foo:1,
            bar:2
        }
    }
    let {foo,bar} = example();
    
    //例子,Util构造函数中有很多工具方法,只用其中的fn1和fn2
    class Util{
        fn1:function(){
            console.log(1)
        }
        fn2:function(){
            console.log(2)
        }
        fn3:function(){
            console.log(1)
        }
        fn4:function(){
            console.log(1)
        }
    }
    let {fn1,fn2} = new Util();
    fn1(); //1
    fn2(); //2
    
  • 解构赋值可以方便地将一组参数与变量名对应起来。

    // 参数是一组有次序的值
    function f([x, y, z]) { ... }
    f([1, 2, 3]);
    
    // 参数是一组无次序的值
    function f({x, y, z}) { ... }
    f({z: 3, y: 2, x: 1});
    
  • 函数参数值(指定参数的默认值,就避免了在函数体内部再写 var foo = config.foo || 'default foo';这样的语句。)

    jQuery.ajax = function (url, {
     async = true,
     beforeSend = function () {},
     cache = true,
     complete = function () {},
     crossDomain = false,
     global = true,
     // ... more config
    } = {}) {
     // ... do stuff
    };
    
  • 遍历Map结构

    const map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    
    for (let [key, value] of map) {
     console.log(key + " is " + value);
    }
    // first is hello
    // second is world
    
    //只想获取到key或者value
    // 获取键名
    for (let [key] of map) {
     // ...
    }
    
    // 获取键值
    for (let [,value] of map) {
     // ...
    }
    

3.Promise对象

4.Iterator迭代器

5.Generator函数

6.async函数

7.class类

8.module模块机制

9.Decorator修饰器

results matching ""

    No results matching ""