12、原型,原型链,call, apply

原型,原型链,call/apply

原型(prototype)

1.定义:

原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。

// 函数也相当于一个对象 是对象就有属性和方法
Person.prototype  // 给函数增加属性方法  prototype:原型(也是一个对象) 
// Person.prototype = {}  在函数刚刚出生的时候就已经被定义好了 它是一个空对象  可以理解为这个空对象就是构造函数构造对象的爹  是祖先
Person.prototype.name = "hehe"; // 给空对象加属性
Person.prototype.say = function () { // 定义一个方法
    console.log('haha')
}
function Person() {  // 构造函数的特点:大驼峰式(其他函数时小驼峰)
    this.name = "zhang" // 如果函数自己定义了name属性,就会取自身的值,没有的情况下才会取祖先的值(跟AO/GO性质一样)
}
var person = new Person();  // 构造函数可以产生多个相似且独立的对象
// person是构造的对象 会继承祖先的属性 person中没有name属性 但是person.name会输出"hehe"  person.say() 输出haha (加小括号是函数执行)

var person1 = new Person();  // person1也会继承祖先的属性 person1.name也会输出hehe  person1.say() 输出haha
Person.prototype.LastName = "hehe"; 
Person.prototype.say = function () { 
    console.log('haha')
}
function Person(name, age, sex) {  
    this.name = name;
    this.age = age;
    this.sex = sex;
}
var person = new Person('zhangjie', 3, 'male');  // 对象有了自己的属性  也有继承来的属性(LastName) 访问person.LastName输出hehe

2.利用原型特点和概念,可以提取共有属性。

// 引用一:提取共有部分放到原型里面

// 创建多个对象都会执行中间三行没传参的代码,会造成代码冗余(耦合) 所以需要用到prototype给原型赋值 这个操作只会执行一遍  然后对象通过继承获取数据
Car.prototype.carName = "BMW";
Car.prototype.height = 1400;
Car.prototype.lang = 4900;
/*
  因为prototype就是一个空对象,所以上面的写法可以写成
  Person.prototype = {
  	carName : "BMW";
  	height : 1400;
  	lang : 4900;
  }
*/
function Car(color, owner) {  
    this.owner = owner;
    this.color = color;
}
// 这里访问到的carName、height、lang都是通过原型继承来的
var car = new Car('red', 'Cheng');
var car1 = new Car('green', 'Ye');
Person.prototype.lastName = "Cheng";
function Person(name) {
    this.name = name;
}
var person = new Person('Jaosn')

person.lastName = "XieNa"  // person是没有lastName属性的,这样写只会在person对象中增加lastName属性,通过对象无法修改原型的东西
Person.prototype.lastName = "Zhang";  // 只能通过原型自己来修改

// 无法通过构造的对象去增删改原型的  但是可以查
delete person.name  // 返回true 因为可以删除构造函数自身的属性
delete person.lastName  // 通过构造函数删除原型的lastName 返回true
person.lastName  // 但是访问lastName还在
// 这是因为构造函数只会在自己内部找lastName 没有这个属性会认为是没有的属性  删除没有的属性会返回true
delete person.abc  // 比如这个也会返回true  因为person也没有abc属性

3.对象如何查看原型—>隐式属性__proto__

__proto__ :原型链,指向的是索引,也就是原型(构造对象的爹)

__proto__ 的object对象中的__proto__ 中保存着prototype

// __proto__的作用
// 在查找一个属性时,如果构造的对象自身没有这个属性,就会查找__proto指向的索引找想要的属性
Person.prototype.name = 'abc'
function Person() {
    /* var this = {
    	__proto__ : Person.prototype
    }  */
}
var person = new Person();
person.name  // 这里访问person.name 但是person没有name属性 就会通过__proto__指向的索引找到Person.prototype中的name属性
// 更改__proto__会怎样
Person.prototype.name = 'abc'
function Person() {
    
}
var obj = {
    name : 'sunny'
}
var person = new Person();
person.__proto__ = obj  // 更改构造对象的__proto__属性的指向
person.name  // 再访问name就会输出sunny 因为继承的原型被修改了
Person.prototype.name = 'Jason'  // 给原型加属性
// var _private 在变量前加_说明这个变量有其他用途 提醒同事别乱改
function Person() {
    
}
/* __proto__里面放的就是原型 
在new的时候发生隐式三段式
1. var this = {  这个this并不是空的 里面有__proto__属性
	__proto__ : Person.prototype  
如果构造的对象在自身找不到要找的属性 就会通过__proto__指向的索引去找需要的属性  起到一个连接的作用
}
*/
var person = new Person();  // 控制台查看person会发现构造的对象person里面有一个隐式属性__proto__ 后面的object对象 对象里面有constructor指向Person(构造函数) 还有一个__proto__ 
Person.prototype.name = 'sunny';
function Person() {
    
}
Person.prototype.name = 'cherry';  // Person.prototype.name = 'cherry';写在构造函数之前和之后,访问person.name输出的都是cherry
var person = new Person();
// Person.prototype.name = 'cherry'; 访问person.name输出也是cherry
// 原型是个对象 属于引用值,本体存在栈中,__proto__拿到的就是指引它的房间号。新的原型在栈中重新开辟了一个新的房间,但__proto__拿着的还是老原型在栈中的房间号
Person.prototype.name = 'sunny';
function Person() {
   // var this = {__proto__ : Person.prototype} 这里指向的是之前的房间 之前的房间放的是sunny
}

var person = new Person();

Person.prototype = {  // 这里是新创建了一个房间放cherry
    name : 'cherry'
}
person.name  // 输出sunny 因为__proto__还是指向原来的房间

// 上面的过程可以简化成以下三步
1.Person.prototype = {name : 'a'}; // 一开始定义了name属性
2.__proto__ = Person.prototype;  // 在name还是a的房间赋值给了__proto__ 所以__proto__存储的就是name=a
3.Person.prototype = {name : 'b'};  // prototype重新建了一个房间放b  但是__proto__指向的是a房间

// 换个位置看看
Person.prototype.name = 'sunny';
function Person() {
}
Person.prototype = {
    name : 'cherry'
}
var person = new Person();
person.name  // 输出cherry 因为一开始的房间放sunny,但是又在构造对象之前新建了一个房间放cherry 就会把新建的房间赋值给__proto__

4.对象如何查看对象的构造函数—> constructor

constructor指向的是构造函数的函数体

Car.prototype  // prototype(原型)在函数一出生就有
function Car() {
    
}
var car = new Car();
// constructor:构造器  系统自带的属性 指的的构造函数的函数体 function Car() {} 目的是构造的对象能找到自己的构造函数(自己的爹)
car.constructor  //  会返回构造这个对象的构造函数
Car.prototype  // prototype(原型)在函数一出生就有
function Car() {
    
}
var car = new Car();
// constructor:构造器  系统自带的属性 指的的构造函数的函数体 function Car() {} 目的是构造的对象能找到自己的构造函数(自己的爹)
car.constructor  //  会返回构造这个对象的构造函数
// 尝试修改constructor,让他认贼作父
function Person() {
    
}
Car.prototype = {
    constructor : Person;  // 修改成功(constructor可手动修改)
}
function Car() { 
}
var car = new Car()
car.constructor  // 访问car.constructor就是Person这个构造函数

原型链__proto__

如何构成原型链?

把原型连成链,访问顺序是依照链的顺序,像作用域一样的东西叫原型链。原型链的连接点就是prototype

原型链就是以对象为基准,以 __proto__ 为连接的这条链,一直到 Object.prototype 为止的这条链 叫做原型链

原型链就是 以对象原型为节点连起来的一条链条

__proto__指向原型路径,可读不可写(可以访问但是不能人为添加__proto__属性)

// Grand也不是原型链的头  Object.prototype才是所有对象的最终原型 所以Object.prototype中没有__proto__ 因为到头了

//Grand.prototype.__proto__ = Object.prototype
Grand.prototype.lastName = "Deng";
function Grand(){
    
}
var grand = new Grand();

Father.prototype = grand;  // father构造对象的原型是grand(构造对象)
function Father() {
    this.name = 'xuming'
}
var father = new Father();

Son.prototype = father  // son构造对象的原型是father(构造对象)
function Son() {
    this.hobbit = 'smoke'
}
var son = new Son();

console.log(son.hobbit)  // 打印smoke
console.log(son.name)  // 打印xuming 自身没有name属性 就往原型father上找
console.log(son.lastName)  // 打印Deng father也没有lastName属性 就往father的原型grand上找
son.toString  // 会返回Object.prototype对象中的toString返回 因为那是原型链的终端

原型链上属性的增删改查

基本与原型一致

Grand.prototype.lastName = "Deng";
function Grand(){
}
var grand = new Grand();

Father.prototype = grand;
function Father() {
    this.name = 'xuming';
    this.fortune = {
        card1 : 'vise'
    };
    this.num = 100
}
var father = new Father();
Son.prototype = father 
function Son() {
    this.hobbit = 'smoke'
}
var son = new Son();
// 增加原型链
father.prototype.abc = "111"  // 只能自己增加自己的原型链属性
son.prototype.abc = "111"     // 无法增加不是自己的原型链属性 这样写只会给son的原型增加abc属性

// 删除原型链
delete father.prototype.name  // 只能自己删除自己的原型链属性
delete son.prototype.name     // 无法删除不是自己的原型链属性

// 修改原型链
father.prototype.name = "wang"  // 只能自己修改自己的原型链属性
son.prototype.name = "wang"     // 无法修改不是自己的原型链属性
<一种特殊的修改方式>
son.fortune = 200  // 这样只会给son增加fortune这个属性
son.fortune.car2 = 'master' // 调用fortune然后增加car2属性 这是引用值自身的修改  所以会修改fortune中的值

son.num ++  // father中的num还是100  这里是把father中的num取出来给son 然后++ 所以son.num中的值是101
    
// 查看原型链
<一直往上找,直到找到需要的属性,如果终端都没有,就会返回null>
// xxx.sayName 
// sayName里面的this指向: 谁调用这个方法,this就指向谁
Person.prototype = {
    name : "a",
    sayName : function () {
        console.log(this.name);
    }
}
function Person() {
    
}
var person = new Person();
person.sayName  // 打印a 因为自己没有,所以指向原型中的sayName

Person.prototype = {
    name : "a",
    sayName : function () {
        console.log(this.name);
    }
}
function Person() {
    this.name = "b"  
}
var person = new Person();
person.sayName  // 打印b 自己又sayName 就打印自己的sayName
Person.prototype = {
    height : 100
}
function Person() {
    this.eat = function () {
        this.height ++;
        return 123;  // 不设return值会默认返回undefined
    }
}
var person = new Person();
person.eat()  // 调用eat方法之后访问person会发现增加了一个height属性 值是101  原型上的height还是100 无法修改原型的属性值 只能改自己的
// 凡是对象都有原型 对象字面量写法更简单 所以基本上都用这种写法var obj = {}
1. var obj = {}  // 系统内部会new Object()
2. var obj1 = new Object()  // 这两种写法效果一样
// obj1.proto__ ----> object.prototype  obj1的构造函数时object 所以obj1的__proto__存储的原型路径就是object.prototype 这也是原型链的终端  所以会继承toString方法
Person.prototype = {}  -->object.prototype
function Person() {
    
}

绝大多数对象的最终都会继承自Object.prototype

 Object.create(null)  // null 不是对象 不能做原型  这样写返回结果是Object {}  但是这个对象里面啥都没有  构造的这个对象没有原型
var obj = Object.create(null);  // obj没原型
obj.toString  // 访问报错 因为没原型所以没有toString方法
obj.__proto__ = {name : "sunny"}  // obj是对象 可以认为添加__proto__属性 但是这个属性是显式的(自己定义的 不是系统定义的)
obj.name  // 返回undefined  因为人为定义的__proto__系统不会读
var obj = Object.create(123);  // 报错 原始值不行 得是Object.prototype(原型)
// undefined和null不能使用toString方法,因为他们没有原型
123.toString  //能调用toString方法是因为他们会通过包装类一层层向上访问,包装类包装起来可以是个对象,对象的终端是Object.prototype

Object.create(原型)

// Object.create()也能创建对象  括号里面必须写原型
// var obj = Object.create(原型),用obj接收创建的对象
// 这种创建对象的方法更加灵活
var obj = {name : "sunny", age : 123};
var obj1 = Object.create(obj)  // obj1的原型就是obj

Person.prototype.name = "sunny";
function Person() {
    
}
var person = Object.create(Person.prototype)  // 写构造函数的原型

toString

// 不是对象的调用toString方法
true.toString()  // 返回"true"
var num = 123; num.toString();  // 返回“123”
num.toString(); -->包装类:new Number(num).toString();
/*
new Number(num) Number就是num的构造函数 所以num的原型是Number.prototype  
Number.prototype上面就有toString方法  Number.prototype.toString = function () {
}
所以num.toString调用的是自己重写的名字相同但是功能不同的toString方法
*/
Number.prototype.__proto__ = Object.prototype  // Number的原型是Object.prototype

123.toString();  // 会报错 因为.可以表示计算符或者调用 但是计算符.优先级最高  所以这里会被认定为是浮点型float 所以系统会认为.后面是数字  浮点型后面加字母是会报错的 所以数字需要先转换成变量 num = 123

// 对象调用的toString是Object.prototype上的toString
var obj = {};
obj.toString();  // 返回“[[object Object]]”
Person.prototype = {
}
function Person () {
    
}
var person = new Person();
person.toString();  // 访问的是终端的toString方法 返回的是"[[object Object]]"

// 重写(写一个名字一样但是功能不同的方法)
Person.prototype = {
    toString : function () {
        return "hehe";
    }
}
function Person () {
    
}
var person = new Person();
person.toString();  // 访问的是Person自己定义的toString方法 返回hehe

// 或者直接修改系统自带的toString方法
Object.prototype.toString = function () {
    return 'haha';
}
Person.prototype = {
    
}
function Person () {
    
}
var person = new Person();
person.toString();  // 返回haha

call

Object.prototype.toString  // 原生的toString方法
Number.prototype.toString  // 重写的toString方法
Array.prototype.toString   // 重写的toString方法
Boolean.prototype.toString // 重写的toString方法
String.prototype.toString  // 重写的toString方法

// 如果已经重写了toString怎么访问系统原生的toString方法?
Object.prototype.toString.call(123)  // call的意思就是让123去调用Object.prototype.toString(终端)这个方法 返回"[object Number]" 真正顶端的toString其实没什么用 只会返回"[object 类型]" 所以子孙重写方法
Object.prototype.toString.call(true)  // 返回"[[object Boolean]]"
Number.prototype.toString = function () {
    return '成哥身体好'}
num = 123;
num.toString();  //返回成哥身体好  调用自己重写的toString方法
// document.write输出的并不是这个东西,而是输出这个东西调用方法后的结果
var obj = {}
document.write(obj);   // 打印[object Object] 说明了document.write会调用隐式的toString方法
document.write(obj)  ==> document.write(obj.toString());

var obj = Object.create(null);  // 让对象变成自己创建出来的create(null)
document.write(obj);   // 报错 因为null没有原型 更没有toString方法

var obj = Object.create(null);
obj.toString = function () {
    return '成哥身体好'
}
document.write(obj);   // 这里就会打印成哥身体好
// 因为document.write(obj) ==> document.write(obj.toString())

Float浮点类型

0.14 * 100  // 输出14.000000000000002 JavaScript精度不准的bug 无法解决 这是精度丢失 计算机保存的都是二进制的数,0.14不能用完整的二进制表示,导致最后的结果出现误差 所以尽量不要用浮点数进行判断

Math.ceil()  // 向上取整
Math.floor()   // 向下取整

Math.ceil(123.234)   // 返回124 小数点后有值就会加1
Math.floor(123.234)  // 返回123 小数点后的值不看

Math.random()  // 随机生成0~1之间的数[0,1) 取不到1

for(var i = 0; i < 10; i ++) {
    var num = Math.random().toFixed(2) * 100  // toFixed(2)是截取到第二位 这里是随机生成0~99的数 不含100
    console.log(num);  // 打印多个会发现精读不准的问题
}

// 所以先*100再取整 用floor向下取整 不用toFixed
for(var i = 0; i < 10; i ++) {
    var num = Math.floor(Math.random() * 100) ;
    console.log(num);  // 这样打印就都是整数
}
// 可正常计算的范围:小数点前16位或者后16位
0.000000000000001 + 0.000000000000001  // 返回2e-15
// -15表示0后面总共有15个数,最后一位是2
0.000000000000000001 + 0.000000000000000001  // 返回2e-18

0.000000000000000011 + 0.000000000000000011  // 返回2.2e-18
// -18表示0后面总共有18个数,最后一位是2.2

// 十五位数
100000000000001 + 100000000000001  // 返回20000000000002
// 十六位数
1000000000000001 + 1000000000000001  // 返回20000000000000002
// 十七位数
10000000000000001 + 10000000000000001 //返回20000000000000000

// 十五位数
0.000000000000001 + 0.000000000000001  //返回0.00000000000002
// 十六位数
0.0000000000000001 + 0.0000000000000001  //返回0.00000000000000002
// 十七位数
0.00000000000000001 + 0.00000000000000001 //返回0.00000000000000000

call/apply

作用,改变this指向。

借用别人的函数实现自己的功能

function Person(name, age) {  // this默认是指向window
    this.name = name;
    this.age = age
}
var person = new Person('deng', 100)
var obj = {
    
}
Person.call(obj,'cheng', 300);  // call可以传东西 这里把obj传进去 会引导Person函数中的所有this都变成obj  然后实参传进去
// 访问obj打印的对象为 obj借别人的方法给自己增加了属性
Object {
    name : 'cheng';
    age : 300
}

/*
function test() {  
}
test() --> test.call();  //函数执行隐藏的步骤
*/
function Person(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 另外一个方法也需要上面的属性,但是自身还有其他属性
function Student(name, age, sex, tel, grade) {
// var this = {} Person执行完之后这里面就多了name,age,sex属性
    Person.call(this, name, age, sex)  // 这里的this就是隐式的this对象 执行Person 把参数传进去 利用Person的方法实现自己的功能
    // 然后再执行自己的两行
    this.tel = tel;
    this.grade = grade;
}
var student = new Student('sunny', 123, 'male', 139, 2017);
// 造车工厂 不同方法造不同部分
function Wheel(wheelSize, style) {
    this.style = style;
    this.wheelSize = wheelSize;
}
function Sit(comfortable, sitColor) {
    this.comfortable = comfortable;
    this.sitColor = sitColor;
}
function Model(height, width, len) {
    this.height = height;
    this.width = width;
    this.len = len;
}
// 组装起来
function Car(wheelSize, style, comfortable, sitColor, height, width, len) {
    Wheel.call(this, wheelSize, style);
    Sit.call(this, comfortable, sitColor);
    Model.call(this, height, width, len);
}
var car = new Car(100, '花里胡哨的', '真皮', 'red', 1800, 1900, 4900)
// 访问car对象 会发现car中所有属性都有

区别,后面传的参数形式不同。

call需要把实参按照形参的个数传进去

apply需要传一个arguments(数组) 且数组中包含实参

// 造车工厂 不同方法造不同部分
function Wheel(wheelSize, style) {
    this.style = style;
    this.wheelSize = wheelSize;
}
function Sit(comfortable, sitColor) {
    this.comfortable = comfortable;
    this.sitColor = sitColor;
}
function Model(height, width, len) {
    this.height = height;
    this.width = width;
    this.len = len;
}
// 组装起来
function Car(wheelSize, style, comfortable, sitColor, height, width, len) {
    Wheel.apply(this, [wheelSize, style]); // call是一个一个的传实参 apply必须传数组  数组里面包含实参
    Sit.apply(this, [comfortable, sitColor]);
    Model.apply(this, [height, width, len]);
}
var car = new Car(100, '花里胡哨的', '真皮', 'red', 1800, 1900, 4900)
// 访问car对象 会发现car中所有属性都有

练习

// JavaScript的call和apply方法是做什么的,两者有什么区别?
作用:改变this指向
区别:传参列表不同
arguments是js中函数内置的一个对象,而执行函数方法的实参中值都存储在arguments中

// 下面代码中console.log的结果是[1,2,3,4,5]的选项的是:A、C、D
// A
function foo(x) {
    console.log(arguments)
    return x
}
foo(1,2,3,4,5)

// B  没有函数调用 不报错也不执行
function foo(x) {
    console.log(arguments)
    return x
}(1,2,3,4,5)

// C  立即执行函数 
(function foo(x) {
    console.log(arguments)
    return x
})(1,2,3,4,5)

// D
function foo(){bar.apply(null,arguments)}  // 执行bar中的打印
function bar(x){console.log(arguments)}
foo(1,2,3,4,5)

练习

// JavaScript的call和apply方法是做什么的,两者有什么区别?
作用:改变this指向
区别:传参列表不同
arguments是js中函数内置的一个对象,而执行函数方法的实参中值都存储在arguments中

// 下面代码中console.log的结果是[1,2,3,4,5]的选项的是:A、C、D
// A
function foo(x) {
    console.log(arguments)
    return x
}
foo(1,2,3,4,5)

// B  没有函数调用 不报错也不执行
function foo(x) {
    console.log(arguments)
    return x
}(1,2,3,4,5)

// C  立即执行函数 
(function foo(x) {
    console.log(arguments)
    return x
})(1,2,3,4,5)

// D
function foo(){bar.apply(null,arguments)}  // 执行bar中的打印
function bar(x){console.log(arguments)}
foo(1,2,3,4,5)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好好学习_fighting

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值