阿里电话面试
在今天接到了阿里的面试电话,最初很紧张,后来放松了一些。面试官很好,奈何自己水平不够,将这次的问题记录下来。长个记性。也明白了自己对原生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修饰器