-
[“class”]、[“this”]都可以随意使用
obj["this"]=10
-
[0]、[1]、[2]也可以使用
-
obj[3]=50 = obj["3"]=50
-
思考:为什么obj[3]=obj[“3”]
-
甚至还可以这样用:[“[object Array]”]
-
jquery里面就有这样的实现
-
也可以这样用:[“{abc}”]
-
给对象添加了{abc}属性
设置属性
-
student["gender"]="男"
等价于:student.gender="男"
-
含义:如果student对象中没有gender属性,就添加一个gender属性,值为"男"
如果student对象中有gender属性,就修改gender属性的值为"男"
-
案例1:
student.isFemale=true
-
案例2:
student["children"]=[1,2,5]
-
案例3:
student.toShanghai=function(){
console.log(“正在去往上海的路上”)
}
删除属性
-
delete student[“gender”]
-
delete student.gender
构造函数创建对象的例子:
-
var xiaoming = new Object() --> var xiaoming = {};
-
var now = new Date()
-
var rooms = new Array(1,3,5) --> var rooms = [1,3,5]
-
var isMale=/123/;
==>var isMale=new RegExp("123")
-
isMale是通过RegExp构造函数创建出来的对象
-
isMale是RegExp构造函数的实例
-
以上例子中,Object、Date、Array都是内置的构造函数
- 构造函数
function Person(name,age){
this.name=name;
this.age=age;
}
var p1=new Person(“赵云”,18)
- 说明:
p1就是根据【Person构造函数】创建出来的对象
构造函数的概念
- 任何函数都可以当成构造函数
function CreateFunc(){ }
-
只要把一个函数通过new的方式来进行调用,我们就把这一次函数的调用方式称之为:构造函数的调用
-
new CreateFunc(); 此时CreateFunc就是一个构造函数
-
CreateFunc(); 此时的CreateFunc并不是构造函数
关于new Object()
- new Object()等同于对象字面量{}
构造函数的执行过程
var p1=new Person();
-
1、创建一个对象 (我们把这个对象称之为Person构造函数的实例)-
_p1
-
2、创建一个内部对象,
this
,将this指向该实例(_p1) -
3、执行函数内部的代码,其中,操作this的部分就是操作了该实例(_p1)
-
4、返回值:
-
a、如果函数没有返回值(没有return语句),那么就会返回构造函数的实例(p1)
-
b、如果函数返回了一个基本数据类型的值,那么本次构造函数的返回值是该实例(_p1)
function fn(){
}
var f1=new fn(); //f1就是fn的实例
function fn2(){
return “abc”;
}
var f2=new fn2(); //f2是fn2构造函数的实例
- c、如果函数返回了一个复杂数据类型的值,那么本次函数的返回值就是该值
function fn3(){
return [1,3,5];
//数组是一个对象类型的值,
//所以数组是一个复杂数据类型的值
//–>本次构造函数的真正返回值就是该数组
//–>不再是fn3构造函数的实例
}
var f3=new fn3(); //f3还是fn3的实例吗?错
//f3值为[1,3,5]
JS中继承的概念:
- 通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承
并不是所谓的xxx extends yyy
为什么要使用继承?
- 有些对象会有方法(动作、行为),而这些方法都是函数,如果把这些方法和函数都放在构造函数中声明就会导致内存的浪费
function Person(){
this.say=function(){
console.log(“你好”)
}
}
var p1=new Person();
var p2=new Person();
console.log(p1.say === p2.say); //false
继承的第一种方式:原型链继承1
Person.prototype.say=function(){
console.log(“你好”)
}
- 缺点:添加1、2个方法无所谓,但是如果方法很多会导致过多的代码冗余
继承的第二种方式:原型链继承2
Person.prototype = {
//切记不能忘记
constructor:Person,
say:function(){
console.log(“你好”);
},
run:function(){
console.log(“正在进行百米冲刺”);
}
}
-
注意点:
-
a、一般情况下,应该先改变原型对象,再创建对象
-
b、一般情况下,对于新原型,会添加一个constructor属性,从而不破坏原有的原型对象的结构
继承的第三种方式:拷贝继承(混入继承:mixin)
-
场景:有时候想使用某个对象中的属性,但是又不能直接修改它,于是就可以创建一个该对象的拷贝
-
实际运用:
-
jquery:$.extend:编写jquery插件的必经之路
-
基于jquery封装一个表格控件
var o1={ age:2 };
var o2 = o1;
o2.age=18;
//1、修改了o2对象的age属性
//2、由于o2对象跟o1对象是同一个对象
//3、所以此时o1对象的age属性也被修改了
var o3={gender:“男”,grade:“初三”,group:“第五组”,name:“张三”};
var o4={gender:“男”,grade:“初三”,group:“第五组”,name:“李四”};
//上述代码中,如果使用拷贝继承对代码进行优化会非常和谐
//实现拷贝继承:
//1、已经拥有了o3对象
//2、创建一个o3对象的拷贝(克隆):for…in循环
//3、修改克隆对象,把该对象的name属性改为"李四"
- 实现1:
var source={name:“李白”,age:15}
var target={};
target.name=source.name
target.age=source.age;
-
浅拷贝和深拷贝
-
浅拷贝只是拷贝一层属性,没有内部对象
-
深拷贝其实是利用了递归的原理,将对象的若干层属性拷贝出来
var students=[
{name:“”,age:“”},
{name:“”,age:“”}
]
- 上面的方式很明显无法重用,实际代码编写过程中,很多时候都会使用拷贝继承的方式,所以为了重用,可以编写一个函数把他们封装起来:
function extend(target,source){
for(key in source){
target[key]=source[key];
}
return target;
}
extend(target,source)
-
由于拷贝继承在实际开发中使用场景非常多,所以很多库都对此有了实现
-
jquery:$.extend
-
es6中有了 <对象扩展运算符> 仿佛就是专门为了拷贝继承而生:
-
优点:简单的令人发指
var source={name:“李白”,age:15}
//让target是一个新对象,同时拥有了name、age属性
var target={ …source }
var target2={ …source,age:18 }
继承的第四种方式:原型式继承:(道格拉斯在蝴蝶书中提出来的)
-
场景:
-
a、创建一个纯洁的对象:对象什么属性都没有
var parent={ age:18,gender:“男”};
var student=Object.create(parent);
//student.proto===parent
-
使用方式:
-
空对象:Object.create(null)
var o1={ say:function(){} }
var o2=Object.create(o1);
继承的第五种方式:借用构造函数实现继承
-
场景:适用于2种构造函数之间逻辑有相似的情况
-
原理:函数的call、apply调用方式
function Animal(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
}
function Person(name,age,gender,say){
this.name=name;
this.age=age;
this.gender=gender;
this.say=function(){
}
}
-
局限性:Animal(父类构造函数)的代码必须完全适用于Person(子类构造函数)
-
以上代码用借用构造函数实现
function Animal(name,age){
this.name=name;
this.age=age;
}
function Person(name,age,address){
Animal.call(this,name);
//this.name=name;
//this.age=age;
this.address=address;
}
- 寄生继承、寄生组合继承
-
概念:JS里面的对象可能会有父对象,父对象还会有父对象,。。。。。祖先
-
根本:继承
-
属性:对象中几乎都会有一个__proto__属性,指向他的父对象
-意义:可以实现让该对象访问到父对象中相关属性
-
根对象:Object.prototype
-
var arr=[1,3,5]
-
arr.proto:Array.prototype
-
arr.proto.__proto__找到了根对象
function Animal(){}
var cat=new Animal();
//cat.proto:Animal.prototype
//cat.proto.proto:根对象
- 错误的理解:万物继承自Object?
变量作用域
-
变量作用域的概念:就是一个变量可以使用的范围
-
JS中首先有一个最外层的作用域:称之为全局作用域
-
JS中还可以通过函数创建出一个独立的作用域,其中函数可以嵌套,所以作用域也可以嵌套
var age=18; //age是在全局作用域中声明的变量:全局变量
function f1(){
console.log(name); //可以访问到name变量
var name=“周董” //name是f1函数内部声明的变量,所以name变量的作用域就是在f1函数内部
console.log(name); //可以访问到name变量
console.log(age); //age是全局作用域中声明的,所以age也可以访问
}
console.log(age); //也可以访问
//多级作用域
//–>1级作用域
var gender=“男”;
function fn(){
//问题:
//gender:可以访问
//age: 可以访问
//height:不能访问
//–>2级作用域
return function(){
//问题:
//gender: 通过一级一级作用域的查找,发现gender是全局作用域中声明的变量
//age:
//height:
console.log(gender);
//–>3级作用域
var height=180;
}
var age=5;
}
作用域链
-
由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?这个问题就需要好好地探究一下了,我们把这个变量的查找过程称之为变量的作用域链
-
作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)
-
简单来说,作用域链可以用以下几句话来概括:(或者说:确定一个变量来自于哪个作用域)
-
查看当前作用域,如果当前作用域声明了这个变量,就确定结果
-
查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明
-
再查找上级函数的上级函数,直到全局作用域为止
-
如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
-
举例1:
var name=“张三”;
function f1(){
var name=“abc”;
console.log(name);
}
f1();
- 举例2:
var name=“张三”;
function f1(){
console.log(name);
var name=“abc”;
}
f1();
- 举例3:
总结
为了帮助大家更好温习重点知识、更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
内容包括html,css,JavaScript,ES6,计算机网络,浏览器,工程化,模块化,Node.js,框架,数据结构,性能优化,项目等等。
包含了腾讯、字节跳动、小米、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目,涵盖了初中级前端技术点。
前端面试题汇总
JavaScript
性能
linux