一、判断变量类型
基本数据类型: 1,null, 2 ,undefine 3, number,4,string,5,bool
6,symbol(独一无二的值) 由于每一个Symbol值都是不相等的,这意味着Symbol值可以用于对象的属性名,保证不会出现同名的属性
7,bigint 大数据
1、type of,
基本数据类型, type of可以判断,但是引用类型除了函数外,它的判断都是"object"。
2、instance of
instanceof运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上
所以,只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。
弊端:number,string,boolean这三种类型,只有通过构造函数定义才能检测出,
直接定义检测不出
3、constructor,
是原型prototype的一个属性,当函数被定义时候,js引擎会为函数添加原型prototype,并且这个prototype中constructor属性指向函数引用, 因此重写prototype会丢失原来的constructor。
construte 和 instanceof的区别和作用
constructor属性来判断一个实例对象是由哪个构造函数构造出来的,constructor所指向的的构造函数是可以被修改的
instanceof 运算符通俗来将就是判断两个对象是否属于实例关系,
4、使用Object.prototype.toString.call
二、作用域,作用域链 变量提升
JavaScript变量的作用域, 全局变量和局部变量
函数内部可以直接读取全局变量。另一方面,在函数外部自然无法读取函数内的局部变量。但是通过闭包,可以在函数外面访问到内部的变量!
作用域链
自由变量:函数中没有定义的变量
自由变量往父级作用域找,一层层往找,直至找到全局变量。
说下对变量提升的理解
1、所有申明都会被提升到作用域的最顶上;
2、同一个变量申明只进行一次,并且因此其他申明都会被忽略;
3、函数声明的优先级优于变量申明,且函数声明会连带定义一起被提升
三、闭包
函数内部可以直接读取全局变量。另一方面,在函数外部自然无法读取函数内的局部变量。但是通过闭包,可以在函数外面访问到内部的变量!
闭包就是将函数内部和函数外部连接起来的一座桥梁。
它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
1:由于闭包会使得函数中的变量都被保存在内存中
2:闭包会在父函数外部,改变父函数内部变量的值
闭包使用场景
1、函数作为返回值
2:函数作为参数传递
四、this
this:js中的执行环境主要有两种:全局执行环境和函数执行环境
普通函数跟箭头函数中this的指向问题
箭头函数和普通函数的区别如下。
普通函数:根据调用我的人(谁调用我,我的this就指向谁)
this总是代表它的直接调用者(js的this是执行上下文), 例如 obj.func ,那么func中的this就是ob
使用call,apply,bind(ES5新增)可以改变this指向
箭头函数:根据所在的环境(我再哪个环境中,this就指向谁)
this应用场景
1:作为构造函数执行
function Foo(name){
this.name=name
}
var f=new Foo('lily)
2:对象属性执行
var obj={
name;'A';
printF:function(){
console.log(this.name)
}
}
obj.printF
3:普通函数执行
function fn {
console.log(this)//this===window
}
fn()
4:call apply bind执行,改变this的值
function f(name,age) {
console.log(name)
console.log(this)
}
f.call({x:100},'lily',23) //this是{x:100},
五、原型链
原型规则
1:所有引用类型(数组,对象,函数)都有对象的特性.null除外
var obj={}
obj.a=100
var arr=[]
arr.a=100
function Foo(name,age) {
this.name=name;
this.age=age
console.log(this)
}
2:所有引用类型都有一个隐式属性:__proto__,属性值是一个普通对象
3:所有函数都有一个prototype显式原型属性,属性值也是一个普通对象
4:所有的引用类型的隐式属性__proto__指向的它构造函数的显式原型属性prototype属性
arr.__proto__===Array.prototype
5:当试图去得到一个对象的属性时,如果这个对象本身没有这个属性,会去它的_proto_去寻找(也就是它的构造函数prototype寻找),而这个它的_proto_又是一个对象,也有__proto__属性,就这样一直往上找,这就是原型链,直到找到Object的原型的时候就到了头,而Object.prototype=null,
六、创建对象有几种方法
//字面量
var o1=new Object({name:'01'}) //new Object一个对象
var o2={name:'o1'}
//构造函数
var M=function(name){
this.name=name
}
var 03=M('03')
//Object.create
var p={name:'p'}
var o4=Object.create(p)
//工厂模式,返回一个对象
function person(name,age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.play = function () {
console.log('玩游戏')
}
return obj
}
var per3 = person('王五','20')
//构造函数
function animal(name, age) {
this.name = name;
this.age = age;
this.food = function () {
console.log('骨头')
}
}
var ani1 = new animal('泰迪',3) // ani1称为实例化对象
七、new运算符原理
var M=function (name) {
this.name=name
}
var new2=function (func) {
var o=Object.create(func.prototype) . //1:生产一个空对象,空对象继承构造函数的原型对象
var k=func.call(o) //2:执行构造函数
if (typeof k==='object'){ 。 //3:判断构造函数执行结果是不是对象类型
return k;
} else {
return o
}
}
八、继承的方法
1)原型链实现继承的思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
}
function SubType(){
this.subproperty=false;
}
//通过创建SuperType的实例继承了SuperType
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subproperty;
}
var instance=new SubType();
alert(instance.getSuperValue()); //true
缺点:当一个实例修改会改变另一个实例,原型链上的原型对象是公用的
2)借用构造函数实现继承:子类型构造函数的内部调用父类型型构造函数,通过使用apply()和call()方法可以在新创建的对象上执行构造函数。
function SuperType(){
this.colors=["red", "blue", "green"];
}
function SubType(){
//继承SuperType
SuperType.call(this);
}
var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors); //red,bllue,green,black
var instance2=new SubType();
alert(instance2.colors); //red,blue,green
缺点:方法都在构造函数中定义,因此无法复用函数。在父类的原型中定义的方法,对子类型而言是不可见的。
3)组合继承 原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数的复用,又能够保证每个实例都有它自己的属性。
function SuperType(name){
this.name=name;
this.colors=["red", "blue", "green"];
}
SuperType.prototype.sayName=function(){
alert(this.name);
};
function SubType(name, age){
//继承属性 使用借用构造函数实现对实例属性的继承
SuperType.call(this,name);
this.age=age;
}
//继承方法 使用原型链实现
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
subType.prototype.sayAge=function(){
alert(this.age);
};
var instance1=new SubType("mary", 22);
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
instance1.sayName(); //mary
instance1.sayAge(); //22
var instance2=new SubType("greg", 25);
alert(instance2.colors); //red,blue,green
instance2.sayName(); //greg
instance2.sayAge(); //25
4)ES6 引入class,可以通过extend实现继承。
九、类数组
输出是个对象,但他的原型并不指向Array。
它的特性是:
- 1)具有length属性;
- 2)按索引方式存储数据;
- 3)没有内置方法,不具有数组的push()、pop()等方法
//类数组示例
var a = {'1':'gg','2':'love','4':'meimei',length:5};
Array.prototype.join.call(a,'+');//'+gg+love++meimei'
//非类数组示例
var c = {'1':2}; //没有length属性就不是类数组
javascript中常见的类数组有 arguments对象和 DOM方法的返回结果。比如 document.getElementsByTagName()。
转为数组
1:Array.prototype.slice.call(a)
2:Array.from(li)
九、高阶函数
接收函数作为参数或者返回函数的函数,都可成为高阶函数。所以常见的方法有:map,filter,bind,apply等。
需要了解一下,高阶函数实现AOP。
十、柯里化函数
柯里化,实现上,就是返回一个高阶函数,通过闭包把传入的参数保存起来。
当传入的参数数量不足时,递归调用 bind 方法;数量足够时则立即执行函数。
十一、for 循环效率
for > forEach > map
如何选择对应的循环呢:
- 如果需要将数组按照某种规则映射为另一个数组 map
- 如果需要进行简单的遍历 forEach 或者 for of
- 如果需要对迭代器进行遍历 for of
- 如果需要过滤出符合条件的项 filter