JavaScript不区分类和实例的概念
创建一个name对象
var name=“小明”;
创建一个函数的时候,函数也是对象
function foo() {
return 0;
}
创建对象的两种方式
var obj = new object(); var obj = {};写个小demo感受一下:
var test={
name:"xiaohua",
age:"18",
sex:"male",
health:100,
smoke:function(){
console.log("i am smoking")
this.health--;
console.log(this.health)
},
drink: function(){
console.log("i am drining")
this.health++;
}
}
这里创建了一个叫test的对象,里面装了叫小花的人名,性别,性别。
定义了两个函数体,分别用来控制小花的健康,抽烟呢健康就在100的基础上-1,喝酒呢就加一。
我们在控制台查看
在上面的代码上进行
”增加“
"删除"
一定要用delete
"查找"
”改”
也就是在原有基础上给变量赋值;
构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象。 即为对象变量赋初始值。每个构造函数的实例都将共享构造函数的初始值。 构造函数的出现是为了解决使用Object构造函数和字面量表示法不方便创建大量重复对象的问题。
构造函数的内部原理
在函数体前面隐式的加上this = {};
执行this.xxx = xxx ;
隐式返回this
构造函数的定义的格式:
修饰符 函数名(形式参数){
函数体...
}
构造函数 是没有返回值类型的。
构造函数的函数名必须要与类名一致
构造函数需要以一个大写字母开头,而非构造函数应该以一个小写字母开头,这个主要是为了区别构造函数和其它函数
构造函数其实本身也是函数,只是用来创建对象
调用构造函数千万不要忘记写new
如果一个类没有显式的写上一个构造方法时,那么java编译器会为该类添加一个无参的构造函数的。
任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数 ;
function Student(name) {
this.name = name; //只在被实例化后的实例中可调用
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');//this指向小明
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
普通函数与构造函数的区别“
1. 返回值类型的区别:
1. 构造函数是没有返回值类型 的,
2. 普通函数是有返回值类型的,即使函数没有返回值,返回值类型也要写上void。
2. 函数名的区别:
1. 构造函数的函数名必须要与类名一致,
2. 普通函数的函数名只要符合标识符的命名规则即可。
3. 调用方式的区别:
1. 构造函数是 在创建对象的时候由jvm调用的。
2. 普通函数是由我们使用对象调用的,一个对象可以对象多次普通 的函数,
4. 作用上的区别:
1. 构造函数 的作用用于初始化一个对象。
2. 普通函数是用于描述一类事物的公共行为的。
包装类
原始值没有属性和方法,但是如果强行添加属性或者访问属性的话,系统就会新建一个包装类,然后在包装类上进行操作,操作完成后进行销毁
JS的数据类型:
**值类型(基本类型)**字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
引用数据类型对象(Object)、数组(Array)、函数(Function)。
JS中只有对象才有属性和方法,原始值没有属性和方法
给基本类型添加属性和方法:
//给字符串添加方法 要写到对应的包装对象的原型下才行
var str= "hello world";
//若我们想在原型上设置一个属性long 保存字符串的长度
//var str = new String("hello world");// 1.找到基本包装对象,创建一个和字符串值相同的对象,
//String.prototype.long=str.length; // 2.通过这个对象找到了包装对象下的方法并调用
//str=null; // 3.这个刚创建的对象被销毁
String.prototype.long=str.length;// 执行到这一句,同样因为没有length属性 后台会偷偷的执行上述三步操作
console.log(str.long); //结果为:11
var str= "hello world";
var str2="我的长度也为11吗?";
String.prototype.long=str.length;
console.log(str2.long); //结果为:11
这样因为你是给基本类型string的原型上添加的属性,所以任意一个字符串都可以访问到这个属性及此值。(所以不提倡这种做法)
原始值没自定义有属性和方法 不能给原始值赋值(赋值也不会报错)
js中提供了三种特殊的引用类型(String Number Boolean)每当我们给原始值赋属性值时 后台自动调用包装类转换
String 默认有length属性而且不可赋值
var str = 'abc';
str+=1; //str = 'abc1';
var test = typeof str;//test = 'string';
if(test.length == 6){
// var obj = new String(;'abcd1');
// obj.sing = true;
// delete obj;
test.sign= true;
}
// var obj = new String(;'abcd1');
// console.log(obj.sing);
// delete obj;
console.log(test.sign);// undefined
test是字符串string test.length == 6 就是对的, test.sign会把test调用,然后test.sign输出true???
但是原始值是不能操作属性的,原始值赋属性值要调用包装类,
test是原始值,不会有length属性
代码执行在test.sign处会new string(test).sign=true
进行包装类之后就会删除销毁,所以到最后一句系统看test原始值没有sign属性,又会包装一遍new string(test).sign。
但这次操作跟上次操作不一样,系统执行语句进行包装类,执行完包装类之后就会销毁,在执行语句又会在创建包装类,那么你的这个新的包装类中并没有sign的属性。
因为进行了包装类已经将test变成一个构造对象了,打印对象中的属性,即使我并没有这个属性,系统一样不会报错,只会提示undefined。
所以输出undefied
var x =1,y=z=0;
function add(n){
return n =n+1;
}
y=add(x);
function add(n){
return n = n+3;
}
z=add(x);
xyz分别输出
输出1,4,4,
这里存在一个函数提升,第二个add(n)覆盖第一个add(n)
提取共有属性,避免代码冗余
例如
function Car(color,owner){
this.color = color;
this.owner = owner;
this.carName = "bmw";
this.height = 1400;
this.lang = 4900;
}
var car = new Car("red","bmw",1400,4900,"prof.ji");
var car1 = new Car("blue","bmw",1400,4900,"long");
在这段代码中car和car1和carName属性值都是相同的,我们可以把这些属性赋给函数原型
Person.prototype = {
height:1200;
lang:4900;
carName : "bmw";
}
function Car(color,owner){
this.owner = owner;
this.color = color;
}
var car = new Car("red","prof.ji");
var car1 = new Car("blue","long");
对象修改原型的属性时只能通过调用原型的属性进行改变
Person.prototype.lastname = "deng";
function Person(name){
this.name = name;
}
var person = new Person("sumong");
Person.prototype.lastname = "liu"; //改变原型的属性
原型的几个属性:
prototype属性:我们只要创建了一个函数,就会根据一组特定的规则为这个函数创建一个prototype属性。这个属性指向函数的原型对象。
constructor属性:创建了一个自定义构造函数后,其原型对象默认只会取得constructor属性。指向了构造函数。
_proto_属性:(这是一种隐式命名规则,是系统命名的): 这个属性指向了构造方法的原型对象。
1.prototypePerson.prototype.name="xiaohua";
function Person(){
}
var person =new Person();
var person1=new Person();
在这里原型是Person.prototype
祖先是Person.prototype={ }
任何一个prototype对象都有一个constructor属性,指向它的构造函数。每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
function Person(){
}
Car.prototype.say = "def";
function Car(){
}
//Car.prototype = {
// constructor : Person
//}
var car = new Car();
console.log(car.constructor);//function Car(){}
//console.log(car.constructor);//function Person(){}
//注释掉的部分:可以更改对象car的构造函数,
// 它本来是function Car(){}实例化的一个对象,
// 现在Car.prototype = {constructor : Person}让它指向构造函数function Person(){}
3._proto_,查看原型
new一个构造函数,相当于实例化一个对象,这期间其实进行了这三个步骤:
在构造函数的逻辑最顶端隐式的新建一个this对象,this其实不是不是一个 空对象,
var this = { proto : Person.prototype}//__proto__属性 指向的是对象原型。 (每个对象都有__proto__属性,该属性指向一个对象,就是构造函数Person的原型对象(Person.prototype))
去调用构造函数Person,从而设置对象的属性和方法并初始化。并返回this对象。(把this返回,这样每一个实例化的对象就有__proto__属性了。)
上面步骤完成后,这个对象就与构造函数Person再无联系,这个时候即使构造函数Person再加任何成员,都不再影响已经实例化了的对象了。(此时该对象有
了自己的属性之后,同时具有了构造函数Person的原型对象的所有成员)
a.用法:
Person.prototype.name = 'abc';
function Person(){
//var this = {
// __proto__ : Person.prototype//指向的是对象原型
// }
}
var obj ={
name : "sunny"
}
var person = new Person();
person.__proto__ = obj;
console.log(person.name);
当new一个对象的时候,开始查找属性name,
先看自己的构造函数里面有没有name属性,如果有就直接用,
如果没有就沿着this里面__proto__ 属性去对象的原型里面查找,这个时候我改变了person.proto = obj;让它指向对象obj,值就是sunny
b.’.'的写法改变原型对象属性的值,那么结果也会跟着改
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
// Person.prototype.name = 'cherry';
var person = new Person();
Person.prototype.name = 'cherry';
console.log(person.name)
无论Person.prototype.name = 'cherry’放在var person = new Person();上面还是下面,输出都是cherry,因为它改变的是__proto__ 指向的Person.prototype上的值。值都变了,person.name必然会变化。
c.改变原型让它指向另外的一个空间
var obj = {name : "a"};
var obj1 = obj;//obj和obj1先指向同一个房间,
obj = {name : "b"};//obj它又指向另外一个房间
console.log(obj1.name);//a
console.log(obj.name);//b
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
var person = new Person();
Person.prototype = { //这是把原型给改了,换了一个新对象
name : 'cherry'
}
console.log(person.name)//sunny
首先new一个对象person,调用构造函数,里面隐式的var this = {proto : Person.prototype}
让__proto__ 和 Person.prototype指向同一个空间,然后返回this,这个对象就构建完了。
然后Person.prototype = { name : ‘cherry’ }把自己的空间换了,但是 proto 没有换,__proto__它还是指向原来的Person.prototype的空间值就是sunny。
那么查找name属性的时候,去__proto__里面找到Person.prototype.name ,结果就是sunny.。
就像下面的过程:
Person.prototype = {name : "sunny"};
_proto_ = Person.prototype;
Person.prototype = {name : "cherry"};
console.log(_proto_.name);//sunny
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = {
name : 'cherry'
}
var person = new Person();
console.log(person.name)//cherry
这个就是Person.prototype = {name : ‘cherry’ }改变了原型对象思维指向,让它的值为cherry,然后再实例化一个对象的时候再调用都早函数的时候就会查找__proto__的Person.prototype为cherry。
d,.改变原型和改变原型属性的值综合起来:
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = { //改变原型
name : 'cherry'
}
Person.prototype.name = 'sunny';//改变原型属性的值
var person = new Person();
console.log(person.name)//sunny
首先是__proto__和 Person.prototype一起指向一个空间值是sunny
然后Person.prototype指向另外一个空间值是cherry,
然后再把第二次Person.prototype指向的空间的内容替换为sunny,最后实例化对象的时候,发现自己没有这个name属性,
根据__proto__ 去原型里面查找,找到的就是第二次指向的空间里里面的值sunny。
1.每当代码读取某个对象的某个属性的时候,都会执行一次搜索。首先从对象实例本身开始,如果在实例中找到了该属性,则返回该属性的值,
2.如果没有找到,则顺着原型链指针向上,到原型对象中去找,如果如果找到就返回该属性值。
3.如果为对象实例添加了一个属性,与原型中属性同名,该属性会屏蔽掉原型中的同名属性
4.使用delete可以删除自己实例中的属性,但是原型中的属性是删除不了的。
可以把对象串联起来的链式结构
function CreateDog(name,color){
this.name = name;
this.color = color;
}
CreateDog.prototype.say = function(){
console.log(this.color + this.name + '在叫!');
};
var dog = new CreateDog('萨摩耶','白色');
dog.say();
访问原型的方法
1.通过构造函数来访问
console.log(CreateDog.prototype);//Object { say: say(), … }
2. 通过实例化的对象来访问
console.log(dog.proto);//Object { say: say(), … }
js 在创建对象(任何对象,普通对象和函数对象)的时候,都有一个__proto__的属性,
这个属性用于指向创建他的函数对象的原型对象prototype
console.log(dog.proto === CreateDog.prototype);//true
同样的,CreateDog.prototype 对象也有一个__proto__ 指向创建他的函数的原型对象 (object)的prototype
console.log(CreateDog.prototype.proto === Object.prototype);//true
Object.prototype 也有一个__proto__ 指向null
console.log(Object.prototype.proto === null);//true
原型链 特点是:__proto__ 属性
Object.prototype是原型链的终端
字面量创建对象的方法其实和new Object()的方法是一模一样的。
var obj = {};
var obj1 = new Object();
它们是相等的。
obj.__proto__ ---->Object.prototype
obj1.__proto__ ---->Object.prototype
Person.prototype = {} ------->Object.prototype
function Person(){
}
函数的原型就是一个字面量的形式,所以原型链的终端就是Object.prototype
利用Object.create(原型)创建一个对象*
//var obj = Object.create(原型)
Person.prototype.name = "sunny";
function Person() {
}
var person = Object.create(Person.prototype);
console.log(person.name);//sunny
绝大多数对象的最终都会继承于Object.prototype,但是也有不继承的情况
Object.create(原型)里面的"原型"必须是一个Object对象或者空值。(放原始值会报错)
var obj = Object.create(null);
var obj1 = Object.create(123);//报错
现在创建一个对象,把null放进去,发现这个对象什么属性都没有了,没原型了。
调用toString()不行,它自己没有toString方法,它也没原型连__proto__属性都没有,所以根本找不到toString方法,就会报错。
人为加上_proto_属性,系统不认可
所以说是绝大多数对象继承于Object.prototype
,不是所有的对象,因为现在的这个Object.create(null)创建的对象根本没有继承属性,连原型都没有。
var num = 123;
num.toString()
可以调用tostring()方法,因为数字可以通过包装类来一层层访问,包装类肯定是对象,然后对象的原型链的终端是Object,它有tostring方法。
但是undefined和null也不是对象,也没有包装类,他就是原始值,没有原型,不可能可以调用tostring()方法。
数字num调用toString()方法的原理:
var num = 123;
//num.toString();------->new Number(num).toString();
//number重写toString
Number.prototype.toString = function() {}
//Number.prototype.__proto__ = Object.prototype
//Object.prototype.toString = function () {}
首先Object.prototype上有一个toString方法,每一个继承Object.prototype的都可以调用,但是他们自己也重写了这个方法,就是Number.prototype.toString = function() {},每次调用toString()就是调用的自己重写的toString()方法,方便打印出自己想要的结果。
document.write()本质上是调用tostirng方法
var obj = Object.create(null);
document.write(obj);//会报错
document.write(obj.toString());//会报错,因为它没有原型,更不会有toString()方法
-------------------------------------------------------
//现在人为的加上toString()方法
var obj = Object.create(null);
obj.toString = function() {
return '访问';
}
document.write(obj);//访问
document.write(obj.toString());//访问
//证明document.write()本质上是调用tostirng方法
call/apply
call() 会改变方法内部this的指向,指向第一个参数,后面的参数是正常传实参。
apply()第一个参数同样是指向的对象,但实参只能传一个数组形式的。
即:
call需要把实参按照行形参的个数传进去。
apply需要把实参放进一个arguments传进去。
两个方法的第一个参数必须是对象本身
/*call()方法*/
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);
call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法
/*apply()方法*/
function.apply(thisObj[, argArray])
apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法。
function myFunction(a, b) {
return a * b;
}
myObject = myFunction.call(myObject, 10, 2); // 返回 20
function myFunction(a, b) {
return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray); // 返回 20
在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。
在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。