对象是由属性和方法构成的 (ps:也有说法为:对象里面皆属性,认为方法也是一个属性)
. 语法
student.name 获取到name属性的值,为:“李白”student.say 获取到一个函数[] 语法
student[“name”] 等价于student.namestudent[“say”] 等价于student.say2种方式的差异:
.语法更方便,但是坑比较多(有局限性),比如:. 后面不能使用js中的关键字、保留字(class、this、function。。。); . 后面不能使用数字 var obj={}; obj.this=5; //语法错误 obj.0=10; //语法错误 []使用更广泛 o1[变量name][“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属性的值为"男" //例 student.isFemale=true student["children"]=[1,2,5] student.toShanghai=function(){ console.log("正在去往上海的路上") }以上例子中,Object、Date、Array都是内置的构造函数
说明:p1就是根据【Person构造函数】创建出来的对象
任何函数都可以当成构造函数 : function CreateFunc(){ }
只要把一个函数通过new的方式来进行调用,我们就把这一次函数的调用方式称之为:构造函数的调用
new CreateFunc(); 此时CreateFunc就是一个构造函数CreateFunc(); 此时的CreateFunc并不是构造函数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]通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承 并不是所谓的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、2个方法无所谓,但是如果方法很多会导致过多的代码冗余
注意点:
a、一般情况下,应该先改变原型对象,再创建对象 b、一般情况下,对于新原型,会添加一个constructor属性,从而不破坏原有的原型对象的结构
场景:有时候想使用某个对象中的属性,但是又不能直接修改它,于是就可以创建一个该对象的拷贝 实际运用:
jquery:$.extend:编写jquery插件的必经之路基于jquery封装一个表格控件 var o1={ age:2 }; var o2 = o1; o2.age=18; //1、修改了o2对象的age属性,由于o2对象跟o1对象是同一个对象,所以此时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:$.extendes6中有了对象扩展运算符仿佛就是专门为了拷贝继承而生
var source={name:"李白",age:15} //让target是一个新对象,同时拥有了name、age属性 var target={ ...source } var target2={ ...source,age:18 }使用方式:空对象: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.prototypearr.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(){ console.log(gender); //可以访问 console.log(age); //可以访问,但值为undefined console.log(height); //不能访问 return function(){ //-->2级作用域 console.log(gender); //可以访问 console.log(age); //可以访问 console.log(height); //可以访问,但值为undefined var height=180; //-->3级作用域 } 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:
var name="张三"; function f1(){ console.log(name); var name="abc"; } f1();举例4:
var name="张三"; function f1(){ return function(){ console.log(name); } var name="abc"; } var fn=f1(); fn();举例5:
var name="张三"; function f1(){ return { say:function(){ console.log(name); var name="abc"; } } } var fn=f1();函数执行完毕后,作用域中保留了最新的a变量的值
上下文模式应用场景:
一些需要指定this的情况,比如$.each方法回调函数内部的this判断数据类型: Object.prototype.toString.call(1); 在ES6的箭头函数之前的时代,想要判断一个函数内部的this指向谁,就是根据上面的四种方式来决定的1、首先查看本身有没有length属性 2、如果本身没有该属性,那么去它的原型对象中查找 3、如果原型对象中没有,那么就去原型对象的原型对象中查找,最终一直找到根对象(Object.prototype) 4、最终都没有找到的话,我们认为该对象并没有该属性,如果获取该属性的值:undefined
typeof只能判断:数字、字符串、布尔值、undefined、函数
global是es中全局作用域中的根对象
但是nodejs里面,global全是表示全局变量的载体浏览器端的js里面,全局变量都放在了window中,浏览器中不存在global对象