JS高级01

本文深入探讨JavaScript中的预解析机制,详细解释了预解析如何提升变量和函数声明,以及代码段的独立执行特性。同时,介绍了执行上下文的概念,包括全局和局部执行上下文,以及作用域链的工作原理。此外,还讨论了变量的声明(var、let、const)、闭包、函数作为一等公民的角色,以及IIFE(立即调用的函数表达式)和高阶函数的应用。最后,涉及this的指向规则以及面向对象编程的基础知识,如原型链、继承和对象的创建方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS高级

预解析内容

  • 代码段是指一个script标签就是一个代码段。JS代码在执行时,是一个代码段一个代码段执行。
  • 代码段是相互独立的上面的代码段报错了,不会影响下面的代码段
  • 可以在下面代码段中使用上面代码段中的数据!
    <script>
        var a = '我是第一个代码段中的a';
        console.log(b);//b is not defined
        //在上面的代码段里不能使用下面定义的数据
    </script>

    <script>
        var b = '我是第二个代码段中的b';
        console.log(a);//打印'我是第一个代码段中的a'
        //在下面的代码段里能使用上面定义的数据
    </script>

什么是预解析

  • 浏览器在执行JS代码的时候会分成两个部分操作:预解析和逐行执行代码
  • 也就是浏览器不会直接执行代码,而是加工处理后再执行
  • 这个加工处理的过程就是预解析

预解析会做什么?

  • 预解析期间会提升变量,在代码段中加var 的变量 和 function声明的函数会提升到代码段的最前面,注意:变量提升只提升声明,函数提升的是函数整体!
  • 除了代码段中的全局数据会提升外,函数体内部的局部变量也会提升,注意:局部变量在函数内部提升只提升声明。
  • 结论:预解析会变量提升,变量只提升声明,函数提升整体!
  • 注意:提升的同名变量名和函数名,函数会覆盖变量,函数在JS中是一等公民优先级高
  • 注意:当函数名重复声明并赋值的话 只会提升第一个

预解析练习题

  • 题目1
    <script>
        //预解析提升:
        //var a; fn(){};
        console.log(a);//undefined
        var a = 110;   //给a 赋值为110
        console.log(a);//110

        fn();          //调用fn函数 打印 我是一个fn函数
        function fn(){
            console.log('我是一个fn函数');
        }
    </script>
  • 题目2
    <script>
        //预解析提升:
        //var g;
        g();           //g提升后值为 undefined 所以结果为g is not a function
        var g = function(){ //g 是一个变量 不是函数
            console.log('g函数执行了..');
        }
    </script>
  • 题目3
    <script>
        //预解析提升:
        //var i;
        console.log(i);//undefined
        for(var i = 0; i < 10; i++){}//循环给i赋值
        console.log(i);//i = 10
    </script>
  • 题目4
    <script>
        //预解析提升:
        //var a; fn(){};
        //var b; var a; var c;
        var a = 666;       //给全局变量a赋值 由undefined变为666
        fn();              //调用函数
        function fn() {
            var b = 777;   //给局部变量b赋值 由undefined变为777
            console.log(a);//打印 und
            console.log(b);//打印 777
            console.log(c);//打印 und
            var a = 888;
            var c = 999;
        }
    </script>
  • 题目5
    <script>
        //预解析提升:
        //var value; function value(){}
        console.log(value); //value由und 变为value函数体
        var value = 123;    //value由函数体变为123
        function value() {
            console.log('我是value函数...');
        }
        console.log(value); //打印123
        //结论:提升的同名变量名和函数名,函数会覆盖变量,函数在JS中是一等公民优先级高
    </script>
  • 题目6
    <script>
    //预解析提升:
        console.log(a);//a 没有加var 不提升 报错 a is not defined
        a = 666;
        console.log(a);//上面代码报错下面代码不执行
    </script>
  • 题目7
    <script>
    //预解析提升:
    //fn(){};
        function fn(){
            console.log(a);//找a 没有a  a is not defined
            a = 666;       //代码是从上到下执行的 没有运行到这一行 如果运行到这一行的话 会在VO中存一个 a = 666
            console.log(a);//不执行
        }
        fn();
        console.log(a);   //不执行
    </script>
  • 题目8
    <script>
    //预解析提升:
    //fn(){}
        function fn(){
            a = 110;
        }
        console.log(a); //函数没有执行 找不到a a is not defined
    </script>
  • 题目9
    <script>
    //预解析提升:
    //gn(){}
    //var k;
        gn();           
        function gn(){
            var k = 123;
            console.log(k); //123
        }
        console.log(k);     //k is not defined
    </script>

执行上下文

  • 全局执行上下文和局部执行上下文
  • 当全局代码执行时,就会产生一个全局的执行上下文,EC(G);
  • 当函数代码执行时,就会产生一个局部的执行上下文,EC(Fn)。只要调用一个函数,就会产生一个局部执行上下文。调用100个函数,就会产生100个执行上下文。
  • 在JS代码在执行时,肯定会先执行全局代码,就会产生ECG,这个ECG就要入栈。当我们调用一个函数,就会产生一个EC,这个EC就要入栈,当函数调用完毕后,EC就要出栈,又进入ECG,当全局代码执行完毕后,ECG也要出栈。
    <script>
        /*
            在JS代码在执行的时候会先执行全局代码,执行全局代码就会产生一个ECG,这个ECG就要入栈
            当我们调用函数的时候就会产生一个EC这个EC就要入栈,当函数调用完毕,就会出栈,又进入ECG
            当全局代码执行完毕后,ECG也要出栈。
        */
        /*
             ECG在执行代码之前会预解析然后把全局定义的变量函数放入VO(GO),GO中包括了内置的方法,window,以及自己声明的全局数据
             作用域链:ECG的作用域链就是VO本身
             
             ECF在执行代码解析前会创建一个AO对象AO中包含形参,arguments,函数定义和指向函数对象,定义的变量
             作用域链:ECF作用域链由AO和父级VO组成,查找数据会一层一层查找
        */
         /* 
            在函数内部不加var定义的变量为全局变量,全局变量只会保存在VO中
        */
    </script>

    <!-- <script>
        //原题
        var n = 110;
        console.log(n);
        console.log(window.n);
        m = 220;
        console.log(m);
        console.log(x);
    </script>

    <script>
        //分析
        //var n;
        //n = 110;
        var n = 110;
        console.log(n);//110
        console.log(window.n);//全局代码会放在GO中 GO就是window 所以n=110
        m = 220;
        console.log(m);//不加var的全局代码不会提升。m=220
        console.log(x);//x没有声明,x is not defined
    </script> -->



    <!-- <script>
        //原题
        var a = 1;
        var b = 'hello';
        function fn() {
            console.log('fn...');
        }
        var arr = ['a', 'b', 'c'];
        var obj = { name: 'wc', age: 100 }
    </script>

    <script>
        //分析
        //var a; var b; fn(){}; arr; obj;
        var a = 1;
        var b = 'hello';
        function fn() {
            console.log('fn...');
        }
        var arr = ['a', 'b', 'c'];
        var obj = { name: 'wc', age: 100 }
    </script> -->



    <!-- <script>
        //原题
        function fn(a) {
            console.log(a);
        }
        fn(110);
        console.log(a);
    </script> -->

    <!-- <script>
        //分析
        //fn(){};
        //var a;
        function fn(a) {    //函数形参(局部变量)变为110
            console.log(a); //110
        }
        fn(110);
        console.log(a);
    </script> -->



    <!-- <script>
        //原题
        var arr = [11, 22];
        function fn(arr) {
            arr[0] = 100;
            arr = [666];
            arr[0] = 0;
            console.log(arr);
        }
        fn(arr);
        console.log(arr);
    </script> -->

    <!-- <script>
        //分析
        //arr; fn(){}; 
        //arr; 
        var arr = [11, 22];//栈地址 0x666
        function fn(arr) { //给形参arr 赋值为 0x666
            arr[0] = 100;  //0x666指向的堆 数据变为[100,22];
            arr = [666];   //把新堆[666]的地址0x888赋值给 形参arr 此时形参中的arr指向了 0x888
            arr[0] = 0;    //修改ox888中的数据 ox888指向的堆的数据由[666]变为[0]
            console.log(arr); // 打印ox888堆中的数据 [0]
        }
        fn(arr);//形参arr 赋值为 0x666
        console.log(arr); //打印全局执行上下文中的arr 它指向ox666 数据为[100,22]
    </script> -->



    <!-- <script>
        //原题
        var a = 1;
        var b = 1;
        function gn() {
            console.log(a, b);
            var a = b = 2;
            console.log(a, b);
        }
        gn();
        console.log(a, b);
    </script> -->

    <!-- <script>
        //分析
        //var a; var b; gn(){}; 
        //var a;
        var a = 1;
        var b = 1;
        function gn() {
            console.log(a,b); //a 为und  b 没有提升在AO找不到 就去父级VO中找 父级VO中有b = 1 所以und 1
            var a = b = 2;
            console.log(a,b); //a 赋值为2 b赋值为2 所以父级中的b 由1 变成了2
        }
        gn();
        console.log(a, b);  //打印ECG中的a,b结果为:1,2
    </script>  -->



    <!-- <script>
        //原题
        var a = 1;
        var obj = { uname:'wangcai'}
        function fn(){
            var a2 = a;
            obj2 = obj;
            a2 = a;
            obj.uname = 'xiaoqiang';
            console.log(a2);
            console.log(obj2);
        }
        fn();
        console.log(a);
        console.log(obj);
    </script> -->

    <!-- <script>
        //分析
        //var a; obj; fn(){}; obj2
        //var a2; 
        var a = 1;
        var obj = { uname: 'wangcai' }
        function fn() {
            var a2 = a; //找a a在VO中值为1 赋值给a2 a2为1
            obj2 = obj; //把obj的地址0x666复制给了 obj2
            a2 = a;     //又把 VO中的1 赋值给了 a2
            obj.uname = 'xiaoqiang'; //0x666 中的wangcai变为了xiaoqiang 
            console.log(a2);        //打印a2 为1
            console.log(obj2);      //打印obj2 为ox666中的xiaoqiang
        }
        fn();
        console.log(a); //打印VO中的a 为1
        console.log(obj);//打印obj 为xiaoqiang
    </script> -->


    <!-- <script>
        //原题
        var a = [1,2];
        var b = a; //b 和 a 指向同一个堆
        console.log(a,b); //[1,2] [1,2]
        b[0] = 110; //[110,2]
        console.log(a,b);//[110,2] [110,2]
    </script> -->

    <!-- <script>
        //原题
        var a = [1,2];
        var b = [1,2];
        console.log(a == b);//false == 比较的是地址 不同的堆 地址是不可能一样的
        console.log(a === b);//false
    </script> -->

    <!-- <script>
        //原题
        var a = [1,2];
        var b = a;
        console.log(a == b);//a 和 b指向同一个堆 所以为true
        console.log(a === b);//a 和 b指向同一个堆 所以为true
    </script> -->

    <!-- <script>
        //原题
        console.log(a,b);//a变量提升本身应该为und 但是b没提升 b为 b is not defined 所以直接报错
        var a = b = 3;
    </script> -->

    <!-- <script>
        //原题
        var a = {m:666};
        var b = a;//把a的地址赋值给b
        b = {m:888};//把新地址赋值给b b的地址变了
        console.log(a);//a的值还是m:666没变化
    </script> -->

    <!-- <script>
        var a = {n:12};
        var b = a;
        b.n = 13;
        console.log(a.n);//13
    </script> -->

    <!-- <script>
        console.log(a); //a is not defined
        a = 110;
    </script> -->

    <!-- <script>
        var m = 1;
        n = 2;
        console.log(window.m);//1
        console.log(window.n);//2
    </script> -->

    <!-- <script>
        function fn(){
            var a = 111;
        }
        fn();
        console.log(window.a);//undefined
    </script> -->

    <!-- <script>
        var a = -1;
        if(++a){      //++a的值为新值 为0  0 为false
            console.log('666');// 不执行 
        }else{
            console.log('888');//打印888
        }
    </script> -->

    <!-- <script>
        //var a; var b;
        console.log(a,b);//und und
        if(true){
            var a = 1;
        }else{
            var b = 2;
        }
        console.log(a,b);//1 und
    </script> -->

    <!-- <script>
        var obj = {
            name:'wc',
            age:18
        }
        //in 为运算符 判断一个属性是否属于某个对象
        console.log('name' in obj);//true
        console.log('score' in obj);//false
    </script> -->

    <!-- <script>
        //var a;
        var a;
        console.log(a);//und
        if('a' in window){
            a = 110;
        }
        console.log(a);//110
    </script> -->

    <!-- <script>
        //var a;
        console.log(a);//und
        if('a' in window){
            var a = 110;
        }
        console.log(a);//110
    </script> -->

    <!-- <script>
        //var a; fn(){};
        //var a;
        var a = 100;
        function fn(){      //函数内部有a 并且加var了 所以提升 先提升再执行 
            console.log(a); //所以还没有执行到return 打印undefined
            return;
            var a = 100;
        }
        fn();
    </script> -->

    <!-- <script>
        //提升
        //var n; foo(){};
        //foo中把全局变量中的n的值改成了200
        var n = 100;       //这两个n是同一个n
        function foo(){
            n = 200;       //这两个n是同一个n
        }
        foo();//调用完函数后把全局变量中的n的值改成了200
        console.log(n);//打印全局变量n = 200
    </script> -->

    <!-- <script>
        //fn(){}; 
        //var a;
        function fn(){
            var a = b = 100;//var a = 100; b = 100; b在AO中没有定义
        }                   //所以到VO中找 但是VO中也没有 所以报错        
        fn();
        console.log(a);//100
        console.log(b);//b is not defined
    </script> -->

    <script>
        //var n; fn(){}; gn(){};
        //
        var n = 100;//全局变量n = 100
        function fn(){
            console.log(n);//打印n 自己里面没有n 按照作用域链到父级找
                           //找到GO中全局变量n = 100
        }
        function gn(){
            var n = 200;
            console.log(n);//200
            fn()//fn()在全局代码段中调用 父级是GO 
        }
        gn();//200 100
        console.log(n);//100
    </script>

深入变量

加var变量和不加var变量的区别:

  • 加var的全局变量会在预编译期间提升到代码段的最前面,不加var的变量不会提升。
  • 加var的变量有可能是全局变量,也有可能是局部变量,不加var的变量只可能是全局变量。
  • 加var的局部变量不会作用到window的属性。

let声名的变量:

  • let声明的变量不会提升
  • let声明的变量不会作用到window的属性
  • let和{}配合产生局部作用域 局部作用域外部访问不到{}里面的数据

const声明的变量:

  • const主要声明常量,const声明必须初始化赋值
  • const声明的变量不可以改变
  • const声明的变量不会提升
  • const和{}配合会形成块级作用域,块里面的数据外部访问不到
  • const声明的常量也不会挂载到GO上

闭包

什么是闭包

  • 闭包就是一个不能被销毁的栈空间(内存)局部EC
  • 一个普通函数可以访问外层作用域的自由变量,那么这个函数就是一个闭包
  • 闭包的作用:
  • 延长闭包中数据的生命周期,所对应的内存会一直存在不会被释放掉,造成内存泄漏
  • 外面想去访问里面的数据,是访问不了的

函数是一等公民

  • 在JavaScript中,函数是非常重要的,并且是一等公民 高阶函数就是函数可以作为另一个函数的参数,也可以作为另外一个函数的返回值来使用

IIFE

IIFE就是立即函数调用表达式的意思。

    <script>
        //IIFE 就是立即调用函数表达式的意思
        //函数后加小括号 然后整体用小括号包起来
        //01(function say(){ console.log('hello');}())
        //02+function say(){ console.log('hello');}()
        //03-function say(){console.log('hello');}()
        //04!-function say(){console.log('hello');}()
        //05(function say(){console.log('hello');})()
        //如果IIFE前面是return +-!()可以不写
        //也就是IIFE是函数内部的return后的函数的时候 +-!()可不写
    </script>

高阶函数:

  • 一个函数的参数是函数 或 他的返回值是函数,那么这个函数就是高阶函数。
  • 自己手写的高阶函数 函数的参数是一个函数
function fn(a){
	a();
};
function gn(){
	console.log(666);
}
fn(gn);
  • 数组中的高阶函数
  • filter 过滤函数返回值是过滤得到的新数组
  • map 映射函数
  • forEach快速遍历
  • find查找函数 返回的是找的到元素 找不到返回und
  • findIndex 查找函数 返回的是 数组下标 找不到返回-1
    <script>
        let nums = [2, 23, 77, 45, 3, 8, 10];
        // nums.filter() 表示调用函数
        // nums.filter(function(){}) 把一个匿名函数作为函数的实参 传给filter
        //filter是JS中内置好的高阶函数
        //filter的返回值是一个数组
        //匿名函数作为filter高阶函数的实参 这个匿名函数也有自己的形参item item 表示数组的每一项
        //就相当于 匿名函数的形参item 在匿名函数里的arguments 包含了所有的实参
        let newArray = nums.filter(function (item) {
            // return true; 表示把item中的每一项都放到新数组中
            return item % 2 == 0;
        })
        console.log(newArray);
    </script>
    <script>
        //map 映射 对数组中的每一项进行加工
        let nums1 = [2, 23, 77, 45, 3, 8, 10];
        let newArray1 = nums1.map(function(item){
            return itme * 10;
        })
        console.log(newArray1);
    </script>
    <script>
        //forEach
        let nums2 = [2, 23, 77, 45, 3, 8, 10];
        let newArray2 = nums2.forEach(function(item){
            console.log(item);
        })
    </script>
    <script>
        //find
        let nums3 = [2, 23, 77, 45, 3, 8, 10];
        let itemm = nums3.find(function(item){
            return item === 8;
        })
        console.log(itemm);//返回的是查找的元素 找不到返回的是 und
    </script>
    <script>
    	//findIndex
        let nums4 = [2, 23, 77, 45, 3, 8, 10];
        let itemm = nums4.findIndex(function(item){
            return item === 3;
        })
        console.log(itemm);//返回的是 数组下标
    </script>

this指向与this相关

默认绑定

  • 默认绑定就是独立函数调用,可以理解为用window调用了这个函数只不过没有加.所以独立函数调用中this为window

隐式绑定

  • 独立函数调用,把对象内的方法地址传给外部变量,通过外部变量加()调用对象里面的方法,此时方法里的this表示的是window
  • 通过对象.方法名+()调用对象内部的方法,方法中出现的this就看.前面的是谁,this就代表了谁。

显示绑定

  • 函数也是对象 上面有几个默认的方法 通过call apply bind 可以改变this指向
    使用方法:想要改变this指向的函数名.call(想要指向的东西);
    使用方法:想要改变this指向的函数名.apply(想要指向的东西);
    使用方法:想要改变this指向的函数名.bind(想要指向的东西);
    注意:
    call 和 apply 不仅可以显示绑定this 而且还会让函数执行
    call 会将传入想要指向的东西 包装成一个新的对象返回
    bind 显示绑定this 不会让函数执行 返回一个绑定this 之后新函数

new绑定

  • 使用new创建一个对象
    第一步这个new会在类中生成一个空的对象(堆)
    第二步这个new中会生成一个this 这个this指向了这个堆
    第三步使用类生成的这个对象也会指向这个堆

规则优先级

  • 默认规则的优先级最低
  • 显示绑定优先级高于隐式绑定
  • new绑定优先级高于隐式绑定
  • new绑定优先级高于隐式绑定
  • new绑定优先级高于bind
  • new绑定和call、apply是不允许同时使用
  • new绑定可以和bind一起使用,new绑定优先级更高

忽略显示绑定

  • 如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则

箭头函数中this指向

  • 箭头函数中的this指向外面一层。

面向对象

创建对象的几种方式

    <script>
        
        创建对象的几种方式:
            字面量法:
                let obj = {};

            new方法:
                let obj = new Object(); new一个Object类 生成一个obj对象 //Object叫类也叫构造器
                let str = new String(); new一个String类 生成一个str对象 //String叫类也叫构造器

            工厂模式:
                function Person(name,age){
                    let obj={};
                    this.name = name;
                    this.age = age;
                    return obj;
                };
                let cc = Person('陈城',21);
                let gh = Person('高涵',22);
                打印出它的类时,都是Object 每个对象都是Object的实例
                这种方式 生成的对象 对应的全部都是 Object类 不能很真实的反应对象所对应的类

            构造函数法:
                创建一个手机类
                function Phone(name,price){
                    this.name = name;
                    this.price = price;
                }
                let mi = new Phone('小米',1999);
            new做了什么:
                在构造器的内部创建了一个新的对象
                让构造器中的this指向这个对象
                执行构造器中的代码
                如果构造器没有返回对象,那么返回这个被创建的新的对象
                这个对象内部的prototype属性会被赋值为该构造函数的prototype属性
            
            构造器+原型创建对象
                把共有的属性放到原型上
                    function Player(name){
                        this.name = name;
                    }
                    Player.prototype.game() = function(){
                        console.log(this.name + '开始比赛');
                    }
                    let qiaodan = new Player('乔丹');
                    qiandan.game();

            位于类上的属性叫做:静态属性
                    
            判断 一个对象是否属于 一个类 对象.instanceof()
            str.instaceof(Number);//false
        
    </script>

私有属性和公有属性

    <script>
        /* 
            一切都是对象
            对象是属性的无序集合
            原型链:
                查找对象上的属性,如果对象本身的私有属性中没有,那么会沿着__proto__到创建对象的构造器的原型对象中找
                每一个对象都有一个__proto__那么构造器的原型对象也有一个__proto__ 在创建对象的构造器的原型对象中
                还是找不到的话 也会沿着__proto__去Object 的原型对象中找 找不到的话 继续沿着__proto__找 找到null
                还找不到就报错,这个沿着__proto__查找属性的链 就叫原型链。

                每一个函数(构造器中都有一个prototype属性) 这个prototype指向这个构造器的原型对象
                每一个对象都有一个__proto__的属性 这个属性指向 创建它的构造器的原型对象
                原型对象中不仅有__proto__而且还有一个constructor指向创建原型对象的构造器
                构造器的prototype指向和这个构造器生成的对象中的__proto__指向的是同一个原型对象
                构造器的prototype指向创建的原型对象
                所有函数的默认原型都是Object的实例
                通过__proto__可以向上查找属性 
        */
    </script>
    <script>
        let arr2 = new Array('a','b','c');
        console.log(arr2);
        console.log(arr2.__proto__);//Array 原型对象
        //打印创建出arr2这个对象的原型对象 也就是打印Array的原型对象
        console.log(arr2.__proto__.__proto__);//Object 原型对象
        //每一个对象都有一个__proto__ 那么 Array的原型对象的__proto__也指向创建Array的原型对象的原型对象 这个对象为Object
        
        console.log(Array.prototype == arr2.__proto__);
        console.log(arr2.__proto__.constructor == Array);//true 
        //每一个原型对象上不仅有一个__proto__ 还有一个constructor 这个constructor 指向这个构造器

        console.log(arr2.__proto__.constructor);//Array 原型对象
    </script>

精细化设置对象中的属性

    <script>
        /* 
        精细化设置对象中的属性
            数据属性描述符 configurable enumerable writable value
            Object.defineProperty(目标对象,作用的属性,{
                value:设置的属性值,
                configurable:是否可以删除,//默认都是true ture表示可以删除
                enumerable:是否可以枚举,//默认都是true ture表示可以枚举
                writable:是否可以重新赋值//默认都是true ture表示可以修改
            })
            存取属性描述符 get set
            get:当获取属性的时候会执行的函数 默认为undefined
            set:当设置属性的时候会执行的函数 默认为undefined
            Object.defineProperty(目标对象,作用的属性,{
                configurable:false,
                enumerable:false,
                get:function(){
                    console.log('get...');
                    return this._address;
                }
            })
        */
    </script>
    <script>
        /* 
        get作用:
            获取器:当调用添加精细化添加的属性 那么就会自动调用defineProperty中的get 返回对象中的属性
        set作用:
            设置器:当给精细化属性重新赋值的时候,就会自动调用set函数 把修改的值 赋值给对象中的属性
        */
        let obj = {
            name:'sc',
            age:22,
            _address:"商丘"
        }
        Object.defineProperty(obj,'address',{
            configurable:false,
            enumerable:false,
            get:function(){         //获取器 当调用address 就执行里面的函数
                // console.log('get..');
                return this._address;//可以是对象的任意属性
            },
            set:function(value){    //设置器 当修改address 就执行里面的函数 把_address的值修改为 传入的address的值
                // console.log('set...');
                this._address = value;
            }
        })

        console.log(obj.address);//商丘
        obj.address = '博爱'; //修改对象中的属性值 把精细化属性值 "博爱" 赋值给对象中的属性值 this._address = value;
        console.log(obj._address);//博爱
    </script> 
    <script>
        /* 
        给多个属性精细化设置
        Object.definproperties(obj,{
            作用的属性:{

            },
            作用的属性:{

            }
        })
        */
       let obj = {
           _age:18,
           _address:'香港'
       }
       Object.defineProperties(obj,{
           name:{
               value:'修狗',
               configurable:true,
               enumerable:true,
               writable:true
           },
           address:{
               configurable:true,//可以删除
               enumerable:true,//可以枚举
               get:function(){
                   return this._address; //当调用address就会执行address里面的get方法返回_address
               },
               set:function(value){     //当修改address就会执行set方法 然后把对象中原本的属性对应的属性值改变
                    this._address = value;
               }
           }
       })
       console.log(obj.name);
       console.log(obj.address);
       obj.address = "台湾";
       console.log(obj._address);

    </script>
    <script>
        /* 
        get 和 set 在对象中的使用
        */
       let obj = {
           _name:'cc',
           get name(){//获取器
               return this._name;
           },
           set name(value){//设置器
                this._name = value;
           }
       }
       //当我们obj.name的时候就会返回 obj中的_name
       console.log(obj.name);//'cc'

       //当我们修改 obj.name的时候 就会自动调用对象里面的 set方法
       obj.name = 'sc';
       console.log(obj.name);//调用obj.name get方法执行 输出obj中的_name
    </script>

继承

原型链继承

    <script>
        function Parent() {
            this.hobby = ["eat", "run"]
        }
        Parent.prototype.getName = function () {
            console.log(this.name);
        }

        function Child() { }

        Child.prototype = new Parent();     //创建一个父类对象作为子类的原型对象
        Child.prototype.constructor = Child;//新建的原型对象的constructor指向Child类

        let c1 = new Child()                //新建一个子类的对象
        let c2 = new Child()                //新建一个子类的对象
        c1.hobby.pop(); // 删除子类原型对象中数组中的一个元素
        console.log(c1.hobby);  //["eat"]//因为子类对象全部都指向子类的原型对象 
        console.log(c2.hobby);          //所以只要有子类对象对子类原型对象中的属性做修改 
                                        //其他子类对象再调用子类原型对象中的属性是时就会受到影响 (就是属性值发生了改变)
    </script> 

构造函数继承

     <script>
         /* 
        构造函数继承实质:
            就是把父类中的this指向了子类
            所以子类对象获得了父类中的属性
        构造函数继承的不足:
            父类中的属性和方法都想被子对象继承的话,只能把属性和方法写在构造函数中
            如果是方法体的话,方法体一模一样,每创建一个子对象,都会有一个独立的堆空间
            但是方法的代码是一模一样的,这就造成了内存空间的浪费
        */
        //构造函数继承实质:
        //就是让父类原本指向父类对象的this 指向子类的对象
        //那么子类对象就可以通过父类添加属性
        //利用call方法不仅可以修改父类中的this指向,而且可以直接给子类对象设置属性
        //这样子类每new一次 子类对象就可以从父类那里获得到父类私有属性
        function Parent(){
            this.name = 'cc';
            this.hobby= [1,3,5];
        }
        function Child(age){
            this.age = age;
            Parent.call(this);//更改父类this指向 使父类this指向子类对象
        }
        let c1 = new Child(20);
        console.log(c1);//Child{age:20,name:'cc',hobby:[1,3,5]};
        //得到的是父类的姓名和 自己的年龄 
        //需要自己设置姓名和爱好 
    </script> 

组合继承

    <script>
        /*
        组合继承:
            第一:父类的this指向子类的this 子类对象可以获得父类的私有属性
            第二:子类的原型对象 改变成了父类的对象, 子类的对象可以通过原型对象 获得 父类原型对象上的方法和属性。
            总结:
            这种方法不仅可以使得子类的对象继承了父类的私有属性,而且父类把方法添加到父类原型对象上 
            通过子类更改原型对象为父类的对象 可以获取父类原型对象上的属性和方法。这样方法在原型对象上 就不会造成内存空间浪费
        */
    </script>
    <script>
        function Father(name,age){
            this.name = name;
            this.age = age;
        }
        Father.prototype.getName = function(){
            console.log(this.name);
        }
        function Son(sex,name,age){
            this.sex = sex;
            Father.apply(this,Array.from(arguments).slice(1));
        }
        Son.prototype = new Father();
        Son.prototype.constructor = Son;

        let s1 = new Son('男','杜宜霖','22');
        console.log(s1);
        s1.getName();
    </script>

寄生组合继承

    <script>
        function Parent(name, hobby) {
            console.log("lalala");
            this.name = name;
            this.hobby = hobby;
        }
        Parent.prototype.eat = function () {
            console.log("eat...");
        }
        function Child(id) {
            Parent.apply(this, Array.from(arguments).slice(1))
            this.id = id;
        }
        // Child.prototype = new Parent();
        // 这样行,好吗?
        // 不好,因为你让子的原型和父的原型指向了同一个对象
        // 如果父修改了原型对象,子也要受到影响
        Child.prototype = Parent.prototype;
        Child.prototype.constructor = Child;

        let c1 = new Child(1, "c1", ["haha"])
        let c2 = new Child(2, "c2", ["xixi"])

        c1.eat();
        c2.eat();
    </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值