0402JS

6. 怎样添加、移除、移动、复制、创建和查找节点?

1)创建新节点

  • createDocumentFragment() //创建一个DOM片段
  • createElement()//创建一个具体的元素
  • createTextNode()//创建一个文本节点
    2)添加、移除、替换、插入
  • appendChild() //添加
  • removeChild() //移除
  • replaceChild() //替换
  • insertBefore() //插入
    3)查找
  • getElementsByTagName() //通过标签名称
  • getElementsByName() //通过元素的Name属性的值
  • getElementById() //通过元素I的,唯一性

7.事件委托是什么

答案:利用事件冒泡的原理,让自己的所触发的事件,让他的父元素代替执行!
解析:

1、那什么样的事件可以用事件委托,什么样的事件不可以用呢?

  • 适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
  • 值得注意的是,mouseover 和 mouseout 虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
  • 不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说 focus,blur 之类的,本身就没用冒泡的特性,自然就不用事件委托了。
    2、为什么要用事件委托
  • 提高性能,可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒
<ul>
  <li>苹果</li>
  <li>香蕉</li>
  <li>凤梨</li>
</ul>

// good
document.querySelector('ul').onclick = (event) => {
  let target = event.target
  if (target.nodeName === 'LI') {
    console.log(target.innerHTML)
  }
}

// bad
document.querySelectorAll('li').forEach((e) => {
  e.onclick = function() {
    console.log(this.innerHTML)
  }
})

如上面代码所示,如果给每个li列表项都绑定一个函数,那对内存的消耗是非常大的,因此较好的解决办法就是将li元素的点击事件绑定到它的父元素ul身上,执行事件的时候再去匹配判断目标元素

  • 新添加的元素还有之前的事件
    假设上述的例子中列表项li就几个,我们给每个列表项都绑定了事件;

在很多时候,我们需要在**通过 AJAX 或者用户操作动态的增加或者删除列表项li元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件在*;

如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。
3、事件冒泡与事件委托的对比

  • 事件冒泡:box 内部无论是什么元素,点击后都会触发 box 的点击事件
  • 事件委托:可以对 box 内部的元素进行筛选
    4、事件委托怎么取索引?

8.require与import的区别

首先这两个都是为了JS模块化编程使用

  • 遵循规范不同
    require 是 CommonJS/AMD规范引入方式
    import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法
  • 调用时间不同
    require是运行时调用,所以require理论上可以运用在代码的任何地方(虽然这么说但是还是一般放开头)
    import是编译时调用,所以必须放在文件开头
  • 本质
    require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
    import是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require
  • 加载方式
    require 特点:社区方案,提供了服务器/浏览器的模块加载方案。非语言层面的标准。只能在运行时确定模块的依赖关系及输入/输出的变量,无法进行静态优化
    import 特点:语言规格层面支持模块功能。支持编译时静态分析,便于 JS 引入宏和类型检验。动态绑定。

9. javascript 对象的几种创建方式

  • 第一种:Object 构造函数创建

这行代码创建了 Object 引用类型的一个新实例,然后把实例保存在变量 Person 中。

var Person = new Object();
Person.name = "Nike";
Person.age = 29;
  • 第二种:使用对象字面量表示法
var Person = {}; //相当于 var Person = new Object();
var Person = {
    name: 'Nike';
    age: 29;
}

对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。也就是说,第一种和第二种方式创建对象的方法其实都是一样的,只是写法上的区别不同

在介绍第三种的创建方法之前,我们应该要明白为什么还要用别的方法来创建对象,也就是第一种,第二种方法的缺点所在:它们都是用了同一个接口创建很多对象,会产生大量的重复代码,就是如果你有 100 个对象,那你要输入 100 次很多相同的代码。那我们有什么方法来避免过多的重复代码呢,就是把创建对象的过程封装在函数体内,通过函数的调用直接生成对象。

  • 第三种:使用工厂模式创建对象
function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    };
    return o;
}
var person1 = createPerson("Nike", 29, "teacher");
var person2 = createPerson("Arvin", 20, "student")

在使用工厂模式创建对象的时候,我们都可以注意到,在 createPerson 函数中,返回的是一个对象。那么我们就无法判断返回的对象究竟是一个什么样的类型。于是就出现了第四种创建对象的模式。

  • 第四种: 使用构造函数创建对象
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    };
}
var person1 = new Person("Nike", 29, "teacher");
var person2 = new Person("Arvin", 20, "student");

对比工厂模式,我们可以发现以下区别:

  1. 没有显示地创建对象

  2. 直接将属性和方法赋给了 this 对象

  3. 没有 return 语句

  4. 终于可以识别的对象的类型。对于检测对象类型,我们应该使用 instanceof 操作符,我们来进行自主检测:

alert(person1 instanceof Object); //ture

alert(person1 instanceof Person); //ture

alert(person2 instanceof Object); //ture

同时我们也应该明白,按照惯例,构造函数始终要应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。

那么构造函数确实挺好用的,但是它也有它的缺点:

就是每个方法都要在每个实例上重新创建一遍,方法指的就是我们在对象里面定义的函数。如果方法的数量很多,就会占用很多不必要的内存。于是出现了第五种创建对象的方法

  • 第五种:原型创建对象模式
function Person() {}
Person.prototype.name = "Nike";
Person.prototype.age = 20;
Person.prototype.jbo = "teacher";
Person.prototype.sayName = function() {
    alert(this.name);
};
var person1 = new Person();
person1.sayName();

使用原型创建对象的方式,可以让所有对象实例共享它所包含的属性和方法。

如果是使用原型创建对象模式,请看下面代码:

function Person() {}
Person.prototype.name = "Nike";
Person.prototype.age = 20;
Person.prototype.jbo = "teacher";
Person.prototype.sayName = function() {
    alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //'Greg' --来自实例
alert(person2.name); //'Nike' --来自原型

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。
这时候我们就可以使用构造函数模式与原型模式结合的方式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性

  • 第六种:组合使用构造函数模式和原型模式
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        alert(this.name);
    };
}
var person1 = new Person('Nike', 20, 'teacher');

10.JavaScript 继承的方式和优缺点

答:六种方式

  • 一、原型链继承
    这种方式关键在于:子类型的原型为父类型的一个实例对象。
       //父类型
       function Person(name, age) {
           this.name = name,
           this.age = age,
           this.play = [1, 2, 3]
           this.setName = function () { }
       }
       Person.prototype.setAge = function () { }
       //子类型
       function Student(price) {
           this.price = price
           this.setScore = function () { }
       }
       Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象
       var s1 = new Student(15000)
       var s2 = new Student(14000)
       console.log(s1,s2)

特点:
父类新增原型方法/原型属性,子类都能访问到
简单,易于实现
缺点:
引用类型的属性被所有实例共享,操作引用数据类型操作的是地址。
在创建 Child 的实例时,不能向 Parent 传参
要想为子类新增属性和方法,必须要在Student.prototype = new Person() 之后执行,因为会改变原型的指向,所以应该放到重新指定之后

  • 二、借用构造函数(经典继承)
    这种方式关键在于:在子类型构造函数中通用call()调用父类型构造函数
<script type="text/javascript">
  function Person(name, age) {
    this.name = name,
    this.age = age,
    this.setName = function () {}
  }
  Person.prototype.setAge = function () {}
  function Student(name, age, price) {
    Person.call(this, name, age)  // 相当于: this.Person(name, age)
    /*this.name = name
    this.age = age*/
    this.price = price
  }
  var s1 = new Student('Tom', 20, 15000)

优点:

避免了引用类型的属性被所有实例共享,解决原型链继承的其中一个问题
可以在 Child 中向 Parent 传参
可以实现多继承(call多个父类对象)
缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
只能继承父类的实例属性和方法,不能继承原型属性和方法

  • 三、组合继承(原型链+借用构造函数)
        function Person(name, age) {
            this.name = name,
            this.age = age,
            this.setAge = function () { }
        }
        Person.prototype.setAge = function () {
            console.log("111")
        }
        function Student(name, age, price) {
            Person.call(this,name,age)
            this.price = price
            this.setScore = function () { }
        }
        Student.prototype = new Person()
        Student.prototype.constructor = Student//组合继承也是需要修复构造函数指向的
        Student.prototype.sayHello = function () { }
        var s1 = new Student('Tom', 20, 15000)
        var s2 = new Student('Jack', 22, 14000)
        console.log(s1)
        console.log(s1.constructor) //Student
        console.log(p1.constructor) //Person

这种方式融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。不过也存在缺点就是无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数的内部,子类型最终会包含父类型对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。
优点:
融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
可以继承实例属性/方法,也可以继承原型属性/方法
不存在引用属性共享问题
可传参
函数可复用

  • 四、原型式继承
function object(o) {
function F() {}
F.prototype = o
return new F()
}

var person = {
    name: 'Jiang',
    friends: ['Shelby', 'Court']
}
var anotherPerson = object(person) 
console.log(anotherPerson.friends) // ['Shelby', 'Court']

在object函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例 本质上来说,object对传入其中的对象执行了一次浅复制
缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

  • 五、寄生式继承
    寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数
function createAnother(o) {
    var clone = Object.create(o) // 创建一个新对象
    clone.sayHi = function() { // 添加方法
        console.log('hi')
    }
    return clone // 返回这个对象
}

var person = {
    name: 'Jiang'
}

var anotherPeson = createAnother(person) anotherPeson.sayHi()

基于person返回了一个新对象anotherPeson,新对象不仅拥有了person的属性和方法,还有自己的sayHi方法
缺点:
跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

  • 六、寄生组合式继承
    在前面说的组合模式(原型链+构造函数)中,继承的时候需要调用两次父类构造函数 父类
function inheritPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

该函数实现了寄生组合继承的最简单形式 这个函数接受两个参数,一个子类,一个父类 第一步创建父类原型的副本,第二步将创建的副本添加constructor属性,第三部将子类的原型指向这个副本

function SuperType(name) {
    this.name = name this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function() {
    console.log(this.name)
}

function SubType(name, job) {
    // 继承属性
    SuperType.call(this, name)

    this.job = job
}

// 继承
inheritPrototype(SubType, SuperType)

var instance = new SubType('Jiang', 'student') instance.sayName()

补充:直接使用Object.create来实现,其实就是将上面封装的函数拆开,这样演示可以更容易理解

function SuperType(name) {
    this.name = name this.colors = ['red', 'blue', 'green']
}

SuperType.prototype.sayName = function() {
    console.log(this.name)
}

function SubType(name, job) {
    // 继承属性
    SuperType.call(this, name)

    this.job = job
}

// 继承
SubType.prototype = Object.create(SuperType.prototype)

// 修复constructor
SubType.prototype.constructor = SubType

var instance = new SubType('Jiang', 'student') instance.sayName()

ES6新增了一个方法,Object.setPrototypeOf,可以直接创建关联,而且不用手动添加constructor属性

// 继承
Object.setPrototypeOf(SubType.prototype, SuperType.prototype)
 
console.log(SubType.prototype.constructor === SubType) // true```

优点:
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent. prototype 上面创建不必要的、多余的属性。
与此同时,原型链还能保持不变;
因此,还能够正常使用 instanceof 和 isPrototypeOf。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式

补充:原型链继承
在这里插入图片描述
在操作基本数据类型的时候操作的是值,在操作引用数据类型的时候操作的是地址,如果说父类的私有属性中有引用类型的属性,那它被子类继承的时候会作为公有属性,这样子类1操作这个属性的时候,就会影响到子类2。

       //父类型
       function Person(name, age) {
           this.name = name,
           this.age = age,
           this.play = [1, 2, 3]//引用数据类型Array类型,操作的是地址
           this.setName = function () { }
       }
       Person.prototype.setAge = function () { }
       //子类型
       function Student(price) {
           this.price = price
           this.setScore = function () { }
       }
       Student.prototype = new Person() // 子类型的原型为父类型的一个实例对象
       var s1 = new Student(15000)
       var s2 = new Student(14000)
       console.log(s1,s2)
       //这样子类1操作这个属性的时候,就会影响到子类2。
       s1.play.push(4)
       console.log(s1.play, s2.play)
       console.log(s1.__proto__ === s2.__proto__)//true
       console.log(s1.__proto__.__proto__ === s2.__proto__.__proto__)//true

s1中play属性发生变化,与此同时,s2中play属性也会跟着变化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值