最近面试了三家公司,就不提公司名字了,都还不错,但我由于自身原因没有通过面试,很遗憾,下面写一下我在这期间遇到的面试题
1、是否了解原型,原型的继承,this指向
实例有一个constructor,指向他的构造函数
面向对象的程序设计
对象字面量方式,创建一个对象
var person = {
name:"zhaoyahui",
age:25,
job:"FrontEnd Engineer",
sayName:function(){
alert("hello" + this.name);
}
}
ECMAscript包含数据属性和访问器属性
数据属性有 [[Configurable]]能否通过delete删除属性
[[Enumerable]]能否通过for-in循环属性
[[Writable]]能否修改属性的值
[[Value]]包含这个属性的数据值,读取时从这个位置读,写入值时,把新值保存在这个位置
只能通过Object.defineProperty(属性所在的对象,属性的名字,描述符对象)
访问器属性
[[Configurable]]能否通过delete删除属性
[[Enumerable]]能否通过for-in循环属性
[[Get]]能否修改属性的值
[[Set]]包含这个属性的数据值,读取时从这个位置读,写入值时,把新值保存在这个位置
包括一个getter和setter函数
工厂模式
function creatPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var person1 = creatPerson("zhaoyahui",25,"FrontEnd Engineer");
解决了多个相似对象的问题,但没有解决对象识别问题
构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("zhaoyahui",25,"FrontEnd Engineer");
与工厂模式相比,没有显式的创建对象,直接将属性和方法赋给了this,没有return
new操作符调用构造函数,会经过下面四个过程,不使用new操作符,属性和方法都会被赋值给window,this指向window。call和apply会改变函数作用域,即this的指向
1、创建一个新对象
2、this指向新对象(即把构造函数作用域赋给新对象)
3、执行构造函数内容(为这个构造函数添加属性)
4、返回这个新对象
实例person1会有一个constructor属性,指向Person
检测对象类型,可以使用instanceof person1.instanceof == Person //true person1.instanceof == Object//true
构造函数的缺点
每个方法都要在每个实例上创建一遍,每个实例创建的方法是不同的,但是功能又是相同的,都是console.log(this.name)
原型模式
我们创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是,可以让所有对象实例共享他所包含的属性和方法,将在构造函数中添加的定义实例的信息,放在原型对象中
function Person(){}
Person.prototype.name = "zhaoyahui";
Person.prototype.age = 25;
Person.prototype.job = "FrontEnd Engineer"
Person.prototype.sayName = function(){
console.log(this.name);
}
var person1 = new Person();
新对象的这些属性和方法由所有实例共享
Person.prototype.constructor == Person
下面详细解释一下,prototype,constructor,__proto__的区别
__proto__是连接实例与构造函数原型对象的
首先只有函数对象才有prototype属性,除函数对象之外的都是普通对象,prototype属性也叫原型对象
__proto__,所有的对象obj都具有proto属性(null和undefined除外),它指向创造obj对象的函数对象的prototype
function a (){}
a.prototype.__proto__ === Object.prototype //true
a.__proto__ === Function.prototype //true
constructor返回创建此对象的函数对象的引用
a.constructor === Function //true
a.prototype.constructor === a //true
思考题:var obj={}; obj.prototype.__proto__
指向谁?
obj为普通对象,不是函数对象,没有prototype,所以obj.prototype == undefined,undefined.__proto__,undefined没有__proto__,所以会报错
function a(){}; a.prototype.__proto__.constructor
指向谁?
1, a.prototype
指向a
的一个实例,我们已经多次强调了,而且属于普通对象
2, __proto__
定义为:指向创造obj
对象的函数对象的prototype属性,所以看下谁创造了a.prototype
,因为a.prototype
是普通对象,类型为object,那么是Object创造了它,
3, 那么显而易见a.prototype.__proto__
指向了Object.prototype
4, 那么题目简化为Object.prototype.constructor
指向谁
5, 继续分解题目,Object.prototype
为基本对象,那么就是Object
创造了它,那么它的constructor
就指向了Object
Object.prototype.constructor===Object //true
Object.prototype.__proto__===null //true
isPrototypeof()
Object.getPrototypeof()返回对象的原型
in操作符,会在通过对象能够访问给定属性时,返回true,无论是在原型,还是在实例中的属性
hasOwnProperty() 只有在属性存在于实例中时,才会返回true
当使用对象字面量的方式创建原型对象时,constructor属性就不指向原型对象了,而指向Object,可以重新把constructor指回去。
我们对原型对象的任何修改都能在实例上反映出来,哪怕先创建实例,再修改原型,但如果重写整个原型对象,就切断了实例与原型对象之间的联系。
原生对象的原型
所有原生引用类型(Array,Object,String)都在其构造函数的原型上定义了方法
通过原生对象的原型,不仅可以取得所有默认默认方法的引用,也可以定义新的方法
String.prototype.startsWith = function (text) {
return this.indexOf(text) == 0;};
var msg = "Hello world!";
alert(msg.startsWith("Hello")); //true
但重写时有可能会有命名冲突,以及意外重写原生方法。所以重写时,要判断执行条件,如果本身有这个方法,就不能重写
原型对象的问题
首先省略了为构造函数传递参数的过程,导致每个实例初始化时都具有相同的属性
原型中的所有属性是被共享的,对于其中的函数是很合适的,但对于引用类型属性,当一个属性对其进行修改后,所有实例得到的都是修改之后的值
组合使用构造函数和原型
构造函数用来定义实力属性,原型模式用来定义方法和共享属性。存在的问题是没有把所有信息封装在构造函数内
动态原型模式
检查某个应该存在的方法是否有效,来决定是否创建原型
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != 'function'){
Person.prototype.sayName = fuhnction(){
console.log(this.name);
}
}
}
继承
OO继承有两种方式,一种是接口继承(只继承方法签名),一种是实现继承(继承实际的方法)
ECMAscript只支持实现继承,通过原型链来实现继承
即使一个对象的原型等于其他类型的实例
不要忘记默认原型,所有引用类型都默认继承了Object,这个继承也是通过原型链实现的
所有函数的默认原型都是Object实例,默认原型都会包含一个指向Object.prototype的指针,都会继承toString(),valueOf()等默认方法
确定实例与原型的关系
instanceof 实例与原型链中出现的构造函数
isPrototypeOf() 只要是原型链中出现的原型
再通过原型链实现继承的时候,不能使用对象字面量的方法创建原型方法,这样会重写原型链
原型链的问题
引用类型的问题
创建子类型的实例的时候,没办法向超类型的构造函数传递参数
借用构造函数
使用call(),apply()
function super(name){
this.colors = ["aa","bb"];
this.name = name;
}
function sub(){
super.call(this,"zhaoyahui")
}
借用构造函数的问题
方法都在构造函数中定义,没办法复用。超类型中定的方法,在子类型中不可见
组合继承
组合原型链和借用构造函数
原型链继承原型的属性和方法,构造函数继承实例属性
function super(name){
this.name = name;
this.colors = ["aa","bb"];
}
super.prototype.sayName = function(){
console.log(this.name);
}
function sub(name){
//继承属性
super.call(this,name)
}
sub.prototype = new super();
sub.prototype.constructor = sub;//要重新指回来,否则指向的是super
var aaa = new sub("zhaoyahui");
aaa.sayName();
寄生组合式继承
组合继承的问题是无论什么时候都会调用两次超类型的构造函数,一次是在创建子类型的原型的时候,另一次是在子类型的构造函数内部
function inheritPrototype(sub,super){
var prototype = object(super.prototype);//拿到超类型原型的副本
prototype.constructor = sub;//弥补重写原型,失去的constructor
sub.prototype = prototype;//将副本,赋给子类型的原型
}
function super(name){
this.name = name;
this.color = ["aa","bb"];
}
super.prototype.sayName = function(){
console.log(this.name);
}
function sub(name,age){
super.call(this,name);
this.age = age;
}
inheritPrototype(sub,super);
sub.prototype.sayAge = function(){
console.log(this.age);
}
只调用了一次super构造函数,避免在sub.prototype上创造多余的属性,与此同时原型链保持不变,还能够正常使用instanceof和isPrototypeOf()
this指向问题
一、纯粹的函数调用
普通函数的调用,调用环境为全局,this指向全局
var x = 1;
function test() {
var x = 2;
console.log(this.x);
}
test();//1
二、作为对象方法的调用
函数可以作为对象的某个方法进行调用,this指向调用的对象
function test() {
var x= 2;
console.log(this.x);
}
var x = 3
var obj = {};
obj.x = 1;
obj.m = test;
obj.m();//1
function test() {var x= 2;
console.log(this.x);
}
var x = 3
var obj = {};
obj.m = test;
obj.m();//undefined
三、作为构造函数调用
通过new操作符,生成新对象,this指向新对象
var x = 2;
function test() {
this.x = 1;
}
var obj = new test();
console.log(x,obj.x);//2,1
四、apply,call的调用
调用时,参数为空则是全局调用,有对象,则指向参数对象
var x = 0;
function test() {
console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply() // 0
obj.m.apply(obj); //1
五、闭包中的this指向
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//The Window
每个函数在被调用时,都会获得2个特殊变量this,arguments,内部函数搜索这两个变量时,只会搜索到他的活动对象,不可能直接访问外部函数中的变量,但是如果把this保存在一个闭包能访问到的变量中,就可以让闭包访问该对象了,如下:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());// My Object
再变形
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return this.name;
}
};
object.getNameFunc()//My Object
(object.getNameFunc)()//My Object
(object.getName = object.getName)(); //"The Window",在非严格模式下
第一行代码跟平常一样调用了 object.getName(),返回的是"My Object",因为 this.name 就是 object.name。第二行代码在调用这个方法前先给它加上了括号。虽然加上括号之后,就好像只 是在引用一个函数,但 this 的值得到了维持,因为 object.getName 和(object.getName)的定义 是相同的。第三行代码先执行了一条赋值语句,然后再调用赋值后的结果。因为这个赋值表达式的值是 函数本身,所以 this 的值不能得到维持,结果就返回了"The Window"。
参考链接:https://blog.csdn.net/zhangliuxiaomin/article/details/54618626
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html
2、是否了解闭包,工作中如何应用过闭包
要理解闭包,首先要理解函数作用域,函数作用域包括全局变量和局部变量
函数内部可以直接访问全局变量,但是函数外部不能访问函数内的局部变量,要想读取函数内的局部变量,就需要闭包。
当在函数内部声明变量的时候,如果不用var,相当于声明了一个全局变量(这里可以引申出var与let与const的区别,引申出es6语法)
在函数内部在定义一个函数,返回这个函数
闭包就是能读取其他函数内部变量的函数,或者说是,定义在一个函数内部的函数,为函数内部与外部链接的桥梁。
闭包的用途
1、读取函数内部的变量
2、使变量一直留在内存中
function foo(){
var aaa = 1;
nAdd = function(){aaa += 1};//是一个匿名函数,同时也是一个闭包,对foo内的变量进行setter
//foo1为正经闭包,对foo内变量进行输出
function foo1(){
console.log(aaa);
}
return foo1;//此处必须把foo1进行输出
}
var result = foo();//result相当于是foo1
result();//result执行就是foo1执行,输出1
nAdd();//此处nAdd为全局函数,所以在此可以调用,对aaa的值进行了+1操作
result();//此处再次执行result,输出2,aaa的值已经+1了,这证明了foo内的局部变量aaa一直保存在内存中,没有被垃圾回收机制回收,没有在foo调用完成后进行销毁
foo是foo1的父函数,而foo1被赋给了一个全局变量result,这导致foo1一直在内存中,而foo1依赖于foo,所以foo也一直在内存中,不会在调用结束后,被垃圾回收机制回收。
使用闭包的注意事项
1、闭包会使得函数中的变量都被保存在内存之中,内存消耗很大,不能滥用闭包,在IE中可能导致内存泄漏,在退出函数之前,将不使用的局部变量删除。
2、闭包会在父函数外部,改变内部变量的值,如果把父函数当做对象,闭包当做公有方法,内部变量当做私有属性,要小心不要随意改变父函数内部变量的值。
工作中使用的闭包
1、循环绑定事件,dom绑定click
2、匿名函数
3、setTimeout
引申:
var,let,const的各自用法与优点
const定义的变量不可以修改,而且声明的时候必须初始化(声明的如果是对象可以进行添加,但不能整体修改,因为存储对象的时候,存储的是对象的指针地址,而不是对象本身,所以可以对这个对象的属性进行操作,但不能改变指针的指向。)
var定义的变量可以修改,会发生变量提升,允许一个变量多次声明
let 是块级作用域,只在函数内部起作用
js变量提升和函数提升问题
js引擎在读取js代码时有两个步骤,一是先解释,然后再运行
解释,就是扫描所有js代码,把所有声明提到顶端,然后在执行
变量提升只会提升函数名,函数提升会把整个函数提升到顶端,函数提升在变量提升之前
参考链接:https://blog.csdn.net/m0_37260875/article/details/56676278
https://blog.csdn.net/weixin_41399785/article/details/78903790
3、未知高度的垂直水平居中
一、
.parent3{
position: relative;
height:300px;
width: 300px;
background: #FD0C70;
}
.parent3 .child{
position: absolute;
top: 50%;
left: 50%;
color: #fff;
transform: translate(-50%, -50%);
}
二、
.parent-panel{
width:100%;
height:400px;
border:1px solid #888;
/**主要代码*/
display: flex;
align-items: center;
justify-content: center;
}
4、css定位有哪几个值,每个都是相对于什么定位
absolute 相对于距离最近的定位不是static的元素绝对定位,找不到符合条件的父元素,就相对于浏览器窗口,使用之后display为block,脱离文档流,父元素得不到子元素的高度
relative 相对于它本身会显示在的位置进行绝对定位
fixed 相对于浏览器窗口
static
5、Git commit 出错了(添加了不该提交的文件,commit信息填写错误)应该怎么做
先git log 查看之前的提交记录,找到最近一次的commit-id
git reset commit-id 撤销了commit,并且会对修改进行了保留
git reset --hard commit -id 撤销了commit,完全回退到了之前的版本,修改会丢失
git add了不该add的,但还没有commit的时候,可以使用:
git reset HEAD 文件名
6、对设计模式了解么
7、写一个函数输出斐波那契数列
8、写一个全排列
9、如果在低版本浏览器中数组的map方法不可用,实现一个map方法
原生对象的原型,既可以继承默认方法,也可以定义新方法
10、vue的双向绑定的实现原理11、redux由哪几部分组成,之间的关系是什么
12、redux的中间件和异步操作
13、less中如何定义一个变量
less是一门css预处理语言,添加了变量,函数,Mixin等特性,使css更易维护,可在node和服务端运行
Less中的变量有以下规则:
以@作为变量的起始标识,变量名由字母、数字、_和-组成
没有先定义后使用的规定;
以最后定义的值为最终值;
可用于rule值、rule属性、rule属性部件、选择器、选择器部件、字符串拼接;
定义时 "@变量名: 变量值;" 的形式;引用时采用 "@变量名" 或 "@{变量名}" 的形式;
存在作用域,局部作用域优先级高于全局作用域。
例如:
@base: #f938ab;
.box-shadow(@style, @c) when (iscolor(@c)) {
-webkit-box-shadow: @style @c;
box-shadow: @style @c;
}
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
.box-shadow(@style, rgba(0, 0, 0, @alpha));
}
.box {
color: saturate(@base, 5%);
border-color: lighten(@base, 30%);
div { .box-shadow(0 0 5px, 30%) }
}
编译为:
.box {
color: #fe33ac;
border-color: #fdcdea;
}
.box div {
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
14、px,em,rem的区别,如何实现自适应布局,rem如何实现,给样式添加-webkit-是怎么添加的
px相对长度单位,相对于屏幕分辨率
em相对长度单位,根据所在对象内文本的字体尺寸,如果未设置,则相对于浏览器的默认字体大小
任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1em=16px。那么12px=0.75em,10px=0.625em。为了简化font-size的换算,需要在css中的body选择器中声明Font-size=62.5%,这就使em值变为 16px*62.5%=10px, 这样12px=1.2em, 10px=1em, 也就是说只需要将你的原来的px数值除以10,然后换上em作为单位就行了。
rem这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。目前,除了IE8及更早版本外,所有浏览器均已支持rem。对于不支持它的浏览器,应对方法也很简单,就是多写一个绝对单位的声明。这些浏览器会忽略用rem设定的字体大小
15、写一个获取链接中参数的函数
16、webpack参数配置
18、圣杯布局的几种实现
父元素上的属性
flex-direction : row row-reserve column colunm-reserve
flex-wrap : nowarp warp warp-reserve
flex-flow :上两个的组合
justify-content : flex-start flex-end center space-between space-around
algin-items : flex-start flex-end center baseline stretch
algin-content :多轴线
子元素上的属性
order :定义项目的排列顺序,数值越小,越靠前,默认为0
flex-grow :项目的放大比例,默认为0(存在剩余空间也不放大)
flex-shrink :定义项目的缩小比例,默认为1(如果空间不足,将缩小)负值无效
flex-basis :再分配多余空间的之前,占据主轴的空间,默认为auto即项目本来大小,可以为具体的数值
flex :上面三个的组合
algin-self :允许单个项目有与其他不同的对齐方式 auto | flex-start | flex-end | center | baseline | stretch
19、1px问题
1px变粗的原因
因为不同手机有不同的像素密度
DPR devicePixelRatio = 物理像素 / 独立像素
方案一(对安卓和ios8以下的支持不好)
ios8及以上支持0.5px边框
js判断支持不支持0.5px边框,支持的话添加一个类,border:0.5px;
if(window.devicePixelRatio && window.devicePixelRatio >= 2){
var testElement = document.createElement("div");
testElement.style.border = "0.5px solid transparent"
document.body.appendChild(testElement);
}
if(testElement.offsetHeight == 1){//retina 屏的浏览器可能不认识0.5px的边框,将会把它解释成0px,没有边框
document.querySlector('html').classList.add("hairlines");
}else{
document.body.removeChild(testElement);
}
div {
border: 1px solid #bbb;
}
.hairlines div {
border-width: 0.5px;
}
方法二 box-shadow : 水平位移 垂直位移 模糊距离 阴影的尺寸 阴影的颜色 (颜色不好控制)
-webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5);
方法三 view-port + rem(适合新项目)
在devicePixelRatio = 2 时,输出viewport
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
在devicePixelRatio = 3 时,输出viewport
<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">
引申height,offsetHeight,clientHeight,scrollHeight,innerHeight,outerHeight的区别
按顺序如下图所示
outerheight获取元素集合中第一个元素的当前计算高度值,包括padding,border和选择性的margin
暂时想起这么多,其实上面的好几道题考的是相同的知识点,换个问法可能就比较有迷惑性,以及js基础真的特别重要!!!
后续会慢慢更新每个问题的答案,有不对的地方,欢迎大家指出