前端基础知识必备

1,078 阅读23分钟

1、对seo搜索优化的了解

搜索引擎优化(Search Engine Optimization,SEO)是一种利用搜索引擎的搜索规则来提高目前网站在有关搜索引擎内的自然排名的方式。

简单地理解是通过总结搜索引擎的排名规律对网站进行合理优化,使网站在各搜索引擎的排名提高。

深刻的理解是通过 SEO 这样一套基于搜索引擎的营销思路为网站提供生态式的自我营销解决方案,让网站在行业内占据领先地位,从而获得品牌收益。

作用:

  1. 为网站盈利提供保障
  2. 帮助企业树立品牌形象
  3. 帮助企业通过SEO实现产品销售
  4. 帮助企业/个人/团体处理负面信息,解决信任危机

2、img的title和alt的区别

相同点:它们都会出现在浮层,显⽰⾃⼰设置图⽚相关的内容。

不同点
alt 属性:

  1. 当图⽚加载不出来的时候,就会在图⽚未显⽰的地⽅出现⼀段 alt 设置的属性内容。
  2. 浏览器的搜索引擎可以通过 alt 属性的⽂字描述来获取图⽚。 title 属性:
  3. 可以⽤在任何元素上,当⽤户把⿏标移动到元素上时,就会显⽰预先设置的 title 的内容,起到对图⽚说明的作⽤,实际上就是对图⽚的解释和备注。

总结
alt 是图⽚加载失败时显⽰在⽹页上的替代⽂字。
title 是⿏标放在图⽚上⾯时显⽰的⽂字,title 是对图⽚的描述和进⼀步的说明。

alt 是 img 必要的属性,⽽ title 不是。

3、行内元素和块级元素

行内元素

  1.和其他元素都在一行,即行内元素和其他行内元素都会在一条水平线上排列

  2.高度、宽度是不可控的,设置无效,由内容决定。

块级元素

  1.总是从新的一行开始,即各个块级元素独占一行,默认垂直向下排列;

  2.高度、宽度、margin及padding都是可控的,设置有效,有边距效果;

  3.宽度没有设置时,默认为100%;

  4.块级元素中可以包含块级元素和行内元素。

级别元素
行内元素a,b,strong,span,img,label,button,input,select,textarea
块级元素header,form,ul,ol,table,article,div,hr,aside,figure,canvas,video,audio,footer

4、媒体查询的使用方式

CSS的Media Query媒体查询(也称为媒介查询)用来根据窗口宽度、屏幕比例和设备方向等差异来改变页面的显示方式。使用媒体查询能够在不改变页面内容的情况下,为特定的输出设备制定显示效果。

1)媒体查询由媒体类型和条件表达式组成。常用的媒体查询属性如下。
2)设备宽高: device-width、device-height。
3)渲染窗口的宽和高:width、height。
4)设备的手持方向:orientation。
5)设备的分辨率:resolution。

方法1内联式

  1. 通过@来找到我们的媒体查询@media,开头注意@符号
  2. and :可以将多个媒体特性连接到一起,相当于“且”的意思
  3. 小括号里面写的是媒体查询的条件也可以叫特性
  4. 大括号里面写代码块

如图所示: 我给了一个条件设置最大宽度为800px时执行代码 将背景色变成天蓝色。

image.png

image.png

image.png

image.png

使用方法2外联式

image.png

5、css让元素看不到的方法

5.1、display:none;

display翻译成中文是显示、展览的意思;将display的属性改为none可以实现元素的隐藏,元素和盒子模型也不生成,被隐藏的元素不占位置,看不见摸不着,它会导致浏览器的重排和重绘。

5.2、visibility:hidden;

visibility翻译成中文是能见、可见性的意思;hidden翻译成中文的是隐藏、不易察觉的意思。将visibility的属性改成hidden可以实现元素的隐藏,和display:none的区别是它占位置,看不见但是摸得着,所以它只会导致浏览器重绘而不会重排。

5.3、opacity:0;

opacity翻译成中文是透明度、不透明的意思;将opacity的属性改成0那就是透明度等于0看不见元素,但是这种方法也是只能隐藏看不见元素,和visibility:hidden一样,元素依然存在页面中。

5.4、position;

利用定位将元素的top和left值设为足够大的负数,使它移出屏幕在屏幕上看不见。

5.5、overflow:hidden;

overflow翻译成中文是漫出、溢出的意思;将overflow的属性设置hidden可以实现元素隐藏,但是这个是超出盒子的部分隐藏,有局限性。

总:常用的方法就是display:none 和visibility:hidden。

6、重绘和回流

DOM性能 浏览器的性能大部分都是被这两个问题所消耗

重绘: DOM树没有元素增加或删除,只是样式的改变,针对浏览器对某一元素进行单独的渲染,这个过程就叫做重绘

回流(重排): DOM树中的元素被增加或者删除,导致浏览器需要重新的去渲染整个DOM树,回流比重绘更消耗性能,

注意:发生回流必定重绘,重绘不一定会导致回流。 详情

7、new操作符具体干了什么

如下代码,通过构造函数创建实例对象:

function Func(){
}
let func= new Func();

new 共经过了4个阶段:

1.创建一个空对象

let obj = new Object();

2.链接到原型

把 obj 的proto 指向构造函数Func的原型对象 prototype,此时便建立了 obj 对象的原型链:
obj->Func.prototype->Object.prototype->null
代码为:

obj.__proto__ = Func.prototype;

3.绑定this值
让Func中的this指向obj,并执行Func的函数体。

let result = Func.call(obj);

4.返回新对象
判断Func的返回值类型: 如果无返回值 或者 返回一个非对象值,则将 obj 作为新对象返回;否则会将 result 作为新对象返回。

if (typeof(result) == "object"){
  func=result;
}
else{
    func=obj;
}

8、ajax的原理

Ajax的原理简单来说:通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面
流程图如下: image.png 实现Ajax异步交互需要服务器逻辑进行配合,需要完成以下步骤:

  1. 建 Ajax的核心对象 XMLHttpRequest对象
  2. 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
  3. 构建请求所需的数据内容,并通过XMLHttpRequest对象的 send() 方法发送给服务器端
  4. XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
  5. 接受并处理服务端向客户端响应的数据结果 将处理结果更新到 HTML页面中

8.1、封装一个ajax请求

1、封装

//封装一个ajax请求
function ajax(options) {
    //创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest()


    //初始化参数的内容
    options = options || {}
    options.type = (options.type || 'GET').toUpperCase()
    options.dataType = options.dataType || 'json'
    const params = options.data

    //发送请求
    if (options.type === 'GET') {
        xhr.open('GET', options.url + '?' + params, true)
        xhr.send(null)
    } else if (options.type === 'POST') {
        xhr.open('POST', options.url, true)
        xhr.send(params)

    //接收请求
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            let status = xhr.status
            if (status >= 200 && status < 300) {
                options.success && options.success(xhr.responseText, xhr.responseXML)
            } else {
                options.fail && options.fail(status)
            }
        }
    }
}

2、使用

ajax({
    type: 'post',
    dataType: 'json',
    data: {},
    url: 'https://xxxx',
    success: function(text,xml){//请求成功后的回调函数
        console.log(text)
    },
    fail: function(status){请求失败后的回调函数
        console.log(status)
    }
})

9、常见的设计模式有哪些

9.1、单例模式

单例模式是一种常用的软件设计模式。

在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。

应用场景:如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

9.2、工厂模式

工厂模式主要是为创建对象提供了接口。

应用场景如下:

a、 在编码时不能预见需要创建哪种类的实例。

b、 系统不应依赖于产品类实例如何被创建、组合和表达的细节。

9.3、策略模式

策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。

应用场景如下。

a、 一件事情,有很多方案可以实现。

b、我可以在任何时候,决定采用哪一种实现。

c.、未来可能增加更多的方案。

d、 策略模式让方案的变化不会影响到使用方案的客户。

举例业务场景如下。

系统的操作都要有日志记录,通常会把日志记录在数据库里面,方便后续的管理,但是在记录日志到数据库的时候,可能会发生错误,比如暂时连不上数据库了,那就先记录在文件里面。日志写到数据库与文件中是两种算法,但调用方不关心,只负责写就是。

9.4、观察者模式

观察者模式又被称作发布/订阅模式,定义了对象间一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

应用场景如下:

a、对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。

b、对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。

9.5、 迭代器模式

迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

应用场景如下:

当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍 历的时候,就应该考虑用迭代器模式。其实stl容器就是很好的迭代器模式的例子。

9.6、 模板方法模式

模板方法模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。

应用场景如下:

对于一些功能,在不同的对象身上展示不同的作用,但是功能的框架是一样的。

10、说说对promise的了解

10.1、Promise是什么?

Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大。

ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

10.2、Promise是为解决什么问题而产生的?

promise是为解决异步处理回调金字塔问题而产生的

10.3、Promise的两个特点

1、Promise对象的状态不受外界影响

1)pending初始状态

2)fulfilled成功状态

3)rejected失败状态

Promise有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态

2、Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由pending变成fulfilled或者由pending变成rejected

10.4、Promise的三个缺点

1)无法取消Promise,一旦新建它就会立即执行,无法中途取消

2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部

3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

10.5、Promise在哪存放成功回调序列和失败回调序列?

1)onResolvedCallbacks成功后要执行的回调序列是一个数组

2)onRejectedCallbacks失败后要执行的回调序列是一个数组

以上两个数组存放在Promise创建实例时给Promise这个类传的函数中,默认都是空数组。每次实例then的时候传入onFulfilled成功回调onRejected失败回调,如果此时的状态是pending则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中,如果此时的状态是fulfilled则onFulfilled立即执行,如果此时的状态是rejected则onRejected立即执行

上述序列中的回调函数执行的时候是有顺序的,即按照顺序依次执行

10.6、Promise的用法

1、Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

2、resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从pending变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

3、Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

11、js数据类型有哪些

1、基本数据类型包括undefined、null、number、boolean、string,es6新增有 Symbol、BigInt

2、引用数据类型也就是对象类型,比如Object、array、function、data等。

1) Number和parseInt的区别

1、Number()方法:Number()可以把任意值转成数值类型,如果目标字符串中,只要有一个字符不是数值型的字符,返回NaN,也就是说Number()只能用来转换纯数字的字符串。

3,parseInt()方法:从字符串开始位置一直读取,直到碰到第一个不是数字的字符时,截取。比如parseInt(“12px”)将截取出12 并且是截取的类型是Number类型

12、js的内存存储方式

1)数据结构

数据结构是计算机存储、组织数据的方式。这里只列出分类不做详细解释,常用的数据结构有:数组、栈、堆、列表、链表、树、图、散列表。

2)简单数据类型存储方式

简单数据类型保存在栈中,这些类型在内存中别占有固定大小,他们的值保存在栈空间,按值访问。变量在声明过程中,会在栈中开辟一段内存空间,变量值直接存在该内存中,变量读写的是它们实际保存的值。

var num = 1;
var str = 'abc';

image.png

简单数据数据复制时,会为新声明的变量开辟一段新空间, 然后把值复制到为新变量分配的空间中。

var num = 1;
var str = 'abc';
var num2 = num;
var str2 = str;

image.png

简单数据类在当前执行环境结束时销毁。

3)复杂数据类型存储方式

复杂数据类型是不固定大小的数据,但是存放他们的地址大小是固定的,所以把地址保存在栈中,值保存在堆中。复杂数据类型是一个指针,指针保存在栈中,但是指针指向的是堆。即存储复杂数据类型的变量保存在栈中,数据本身保存在堆中。读取其数据时,先访问栈中的地址,通过地址找到存在堆中的数据。

var object1 = new Object();
var arr1 = new Array();

image.png

复杂数据类型进行浅拷贝时,复制的是引用地址

var object1 = {
	name:'张三',
    age:18
}
var arr1 = [1,2,3,4]
var object2 = object1;
var arr2 = arr1;

image.png 由于浅拷贝指向的是同一个地址,所以当object2和arr2的值改变时,object1和arr1的值也随之改变。

与浅拷贝对应的是深拷贝,深拷贝是将引用类型的值全部拷贝一份,并且存放在不同的内存中,形成一个新的引用类型,修改它的值不会影响原来的值。

复杂数据类型不会随着当前执行环境结束而销毁,只有存放变量的地址不存在时,内存才会被垃圾回收机制回收。

但是这里有一个特殊的地方,就是null,null类型虽然是简单数据类型,但实际是一个特殊的对象,存放null的变量在栈中,指针指向堆中的一段空内存。

13、js里如何判断是不是一个数组

1)instanof

方法一:使用 instanceof 运算符, 该运算符左边是我们想要判断的变量, 右边则是我们想要判断的对象的类

let arr = [1, 2, 3]
console.log(arr instanceof Array)
//  true     返回true,说明变量arr是数组类型

1)构造函数

方法二:利用构造函数来判断他的原型是否为Array, 用法: 变量.constructor === 变量类型

let arr = [1, 2, 3]
console.log(arr.constructor === Array)
//  true     返回true,说明变量arr是数组类型

3)isArray()

方法三:利用的一个专门的方法 isArray(), 用法:Array.isArray(变量),返回true,则说明该变量是数组类型;反之,说明该变量不是数组类型

let arr = [1, 2, 3]
console.log(Array.isArray(arr))
//  true     返回true,说明变量arr是数组类型

4)Object.prototype.toString.call()

方法四:调用Object.prototype.toString.call(),返回true,则说明该变量是数组类型;反之,说明该变量不是数组类型

let arr = [1, 2, 3]
console.log(Object.prototype.toString.call(arr) === '[object Array]')
//  true     返回true,说明变量arr是数组类型

5)对象的原型

方法五:通过对象的原型方式来判断,直接来看例子

let arr = [1, 2, 3]
console.log(arr.__proto__ === Array.prototype)
 
//  true     返回true,说明变量arr是数组类型

6)Object.getPrototypeOf()

方法六:通过 Object.getPrototypeOf()来判断是否为数组类型,例如

let arr = [1, 2, 3]
console.log(Object.getPrototypeOf(arr) === Array.prototype)
//  true     返回true,说明变量arr是数组类型

7)isPrototypeOf()

方法七:通过 isPrototypeOf() 方法来判断是否为数组类型, 

例如
let arr = [1, 2, 3]
console.log(Array.prototype.isPrototypeOf(arr))
//  true     返回true,说明变量arr是数组类型

14、map和foreach的区别

1)相同点

  1. 都是循环遍历数组中的每一项
  2. forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)
  3. 匿名函数中的this都是指向window
  4. 只能遍历数组
array.map(function(item,index,arr){},this)
//
Array.forEach(function(item,index,arr){},this)

2)不同点

forEach()没有返回值。map()有返回值,可以return出来。forEach()方法不会返回执行结果,而是undefined,也就是说,forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回

  1. map速度比foreach快
  2. map会返回一个新数组,不对原数组产生影响,foreach不会产生新数组,foreach返回undefined
  3. map因为返回数组所以可以链式操作,foreach不能
  4. map里可以用return ,而foreach里用return不起作用,foreach不能用break,会直接报错

15、vue2的双向数据绑定原理

遍历data里面的属性,通过Object.defineProperty的set方法监听属性值的变化,最后通知视图更新

16、数组去重的方法

 数组去重12种方法

数组去重常用的几种方法:

定义基础数组变量

let arr = [8,5,12,8,5,45,2,56,78,45,5,45], 
    newArr = [] 

1)暴力双循环去重法

for(let i = 0;i < arr.length;i++){ 
  for(let j = i + 1;j < arr.length;j++){ 
    if(arr[i] == arr[j]){ 
      arr.splice(j,1) 
      j-- 
    } 
  } 
} 
console.log(arr) // (7) [8, 5, 12, 45, 2, 56, 78] 

2)includes()去重法

for(let i of arr){ 
  if(!newArr.includes(i)){ 
    newArr.push(i) 
  }else{ 
    console.log(i + "重复了") 
  } 
} 
console.log(newArr) // (7) [8, 5, 12, 45, 2, 56, 78] 

3)filter()循环过滤法

arr.filter((val,index) => { 
  if(newArr.indexOf(val) < 0){ 
    newArr.push(val) 
  } 
}) 
console.log(newArr) // (7) [8, 5, 12, 45, 2, 56, 78] 

4)sort()排序法

arr = arr.sort() // (12[12, 2, 45, 45, 45, 5, 5, 5, 56, 78, 8, 8] 
for(let i = 0;i < arr.length;i++){ 
  if(i <= arr.length - 1 && arr[i] != arr[i + 1]){ 
    newArr.push(arr[i]) 
  } 
} 
console.log(newArr) // (7[12, 2, 45, 5, 56, 78, 8] 

5)ES6 Array.from()方法

console.log(Array.from(new Set(arr))) // (7) [8, 5, 12, 45, 2, 56, 78] 
console.log([...new Set(arr)]) // (7) [8, 5, 12, 45, 2, 56, 78](简写方法)

6)递归去重

let index = 0 
arr = arr.sort() 
function loop(index){ 
  if(index > 1){ 
    if(arr[index] == arr[index - 1]){ 
      arr.splice(index,1) 
    } 
    index-- 
    loop(index) 
  } 
} 
loop(arr.length - 1) 
console.log(arr) // (7) [12, 2, 45, 5, 56, 78, 8] 

17、防抖和节流

防抖和节流本质是不一样的。防抖是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

  防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。

1)防抖

执行以下代码,控制台会在 2s 后打出日志,2s 之内的操作都被清空,以最后一次的操作为准。

let setTimer;
let shake = function() {
  clearTimeout(setTimer);
  setTimer = setTimeout(() => {
    console.log("这里是实际的业务代码");
  }, 0);
};

let interTimer = setInterval(() => {
  shake();
}, 0);

let timer = setTimeout(() => {
  clearInterval(interTimer);
  clearTimeout(timer);
  timer = null;
  interTimer = null;
}, 2000);

如果你在监听滚动事件,假设两秒以内用户在不断的平凡的触发onScroll事件,只有用户暂停滚动后,才会去执行响应的操作,代码如下

// 函数防抖
var timer = false;
document.getElementById("xxxx").onscroll = function(){
    clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
    timer = setTimeout(function(){
        console.log("函数防抖");
    }, 300);
};

2)节流

  • 定时器实现节流 执行以下代码你会看到控制台每隔 1s 后打印出结果,1s 内不会执行打印日志
let isAllow = true;
function shake() {
  let fun = function() {
    if (!isAllow) return;
    isAllow = false;
    let timer = setTimeout(() => {
      console.log("这里是实际的业务代码");
      clearTimeout(timer);
      timer = null;
      isAllow = true;
    }, 1000);
  };
  fun();
}
let interTimer = setInterval(() => {
  shake();
}, 0);

  • 闭包实现函数节流:
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
  let last = 0; // last为上一次触发回调的时间
  // 将throttle处理结果当作函数返回
  return function() {
    let context = this; // 保留调用时的this上下文
    let args = arguments; // 保留调用时传入的参数
    let now = +new Date(); // 记录本次触发回调的时间
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last >= interval) {
      // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
      last = now;
      fn.apply(context, args);
    }
  };
}
// 用throttle来包装scroll的回调
const better_scroll = throttle(() => console.log("触发了滚动事件"), 1000);
setInterval(() => better_scroll(), 0);

18、apply. call、 bind的区别

首先call、apply、bind的作用都是改变函数运行时this的指向。其次,在 ES6 的箭头函数下, call 和 apply、bind 将失效。

相同点

  • 都是改变this指向的;
  • 第一个参数都是this要指向的对象;
  • 都可以利用后续参数传参; 不同点
  • call和bind的参数是依次传参,一一对应的;
  • 但apply只有两个参数,第二个参数为数组
  • call和apply都是对函数进行直接调用,而bind方法返回的仍是一个函数;

19、this的指向

函数的调用有六种形式。 根据函数的调用方式的不同,this 会指向不同的对象:

1.以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。比如fun();相当于window.fun();

2.以方法的形式调用时,this 指向调用方法的那个对象

3.以构造函数的形式调用时,this 指向实例对象

4.以事件绑定函数的形式调用时,this 指向绑定事件的对象

5.使用 call 和 apply 调用时,this 指向指定的那个对象

20、判断js的数据类型

1、利用typeof操作符,语法“typeof 变量”;
2、利用instanceof运算符;
3、利用constructor属性,语法“变量.constructor==数据类型”;
4、利用toString()函数。 方法具体实现
5、jq中判断数据类型的方法

jQuery.isArray();是否为数组
jQuery.isEmptyObject();是否为空对象 (不含可枚举属性)。
jQuery.isFunction():是否为函数
jQuery.isNumberic():是否为数字
jQuery.isPlainObject():是否为使用“{}”或“new Object”生成对象,而不是浏览器原生提供的对象。
jQuery.isWindow(): 是否为window对象;
jQuery.isXMLDoc(): 判断一个DOM节点是否处于XML文档中。

21、eventloop的理解

具体理解 这个东西我研究过.是 js的一个底层运行原理,js是单线程的,但是也有一些耗时任务, 会影响执行效率.代码都在主线程中执行,当遇见你像ajax请求.setTimeout 定时器时候,会 单独开启异步线程.异步线程耗时之后会推入异步队列中等待执行.然后当主线程执行完毕 之后.会到异步队列中取出到主线程中执行.然后再去异步队列中取第二个.这个来回取的过 程就是您所说的事件循环(eventLoop)吧

image.png

22、常见的跨域方式

同源策略限制 不同源会造成跨域,以下任意一种情况不同,都是不同源:

同源:协议 域名 端口号全部相同 只要有一个不相同就是非同源策略

跨域的解决方案目前有三种主流解决方案:

跨域是浏览器做出的限制,和后端没关系
一、 是 jsonp
jsonp 实现原理:主要是利用动态创建 script 标签请求后端接口 地址,然后传递 callback 参数,后端接收 callback,后端经过 数据处理,返回 callback 函数调用的形式,callback 中的参数 就是 json

二、 通过代理的方式(前端代理和后端代理)
前端代理我在 vue 中使用那个 vue.config.js 里面配置一个 proxy,里面有个 target 属性指向跨域链接.修改完重启项目就可 以了.实际上就是启动了一个代理服务器.绕开同源策略,在请求 的时候,通过代理服务器获取到数据再转给浏览器

三、 是 CORS
CORS 全称叫跨域资源共享,主要是后台工程师设置后端代码来达到前端跨域请求的

23、浏览器存储方式

image.png cookie 原本并不是用来储存的,而是用来与服务端通信的,需要存取请自行封装 api

而 localStorage 则自带 getItem 和 setItem 方法,使用很方便。

1)localStorage注意

1.localStorage 只能存字符串,存取 JSON 数据需配合 JSON.stringify() 和 JSON.parse()

2.遇上禁用 setItem 的浏览器,需要使用 try...catch 捕获异常

2)cookie 和 session 的区别

session 是基于 cookie 实现的。cookie 保存在客户端浏览器中,而 session 保存在服务器上。cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

1、存在的位置

cookie 存在于客户端,临时文件夹中;session 存在于服务器的内存中,一个 session 域对象为一个用户浏览器服务
安全性
cookie 是以明文的方式存放在客户端的,安全性低,可以通过一个加密算法进行加密后存放;session 存放于服务器的内存中,所以安全性好

2、生命周期(以 20 分钟为例)

1.cookie 的生命周期是累计的,从创建时,就开始计时,20 分钟后 cookie 生命周期结束;

2.session 的生命周期是间隔的,从创建时,开始计时如在 20 分钟,没有访问 session,那么 session 生命周期被销毁。但是,如果在 20 分钟内(如在第 19 分钟时)访问过 session,那么,将重新计算 session 的生命周期。关机会造成 session 生命周期的结束,但是对 cookie 没有影响
访问范围

3.cookie 为多个用户浏览器共享;session 为一个用户浏览器独享

24、浏览器的缓存策略

浏览器的缓存过程如下:
1. 开始加载,域名解析,DNS缓存
2. 本地缓存(memory缓存)
3. Http缓存(强缓存和协商缓存)
4. 服务端缓存(cdn缓存)

image.png

强缓存是不发起请求,直接使用缓存内的内容的。浏览器将jscssimagefont-family等存到内存(存小文件)或者磁盘(存大文件)中,下次用户再访问的时候就从内存中取,以便提升性能。
协商缓存需要往后台发请求, 通过判断来决定是使用协商缓存。如果请求内容没发生变化,则请求返回304(服务器收到请求,但内容无变化),浏览器就用缓存内的内容。

image.png

25、vue2的生命周期

vue 生命周期即为一个组件从出生到死亡的一个完整周期,主要包括以下 4 个阶段:创建,挂载,更新,销毁

  • 创建前:beforeCreate, 创建后:created
  • 挂载前:beforeMount, 挂载后:mounted
  • 更新前:beforeUpdate, 更新后:updated
  • 销毁前:beforeDestroy, 销毁后:destroyed
    我平时用的比较多的钩了是 created 和 mounted,created 用于获取后台数据, mounted 用于 dom 挂载完后做一些 dom 操作,以及初始化插件等.beforeDestroy 用户清除定时器以及解绑事件等,
    另外还新增了使用内置组件 keep-alive 来缓存实例,而不是频繁创建和销毁(开销 大)
    actived 实例激活 deactived 实例失效

父子组件生命周期

一个完整的父子组件生命周期

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted->父beforeUpdate->子beforeUpdate->子updated->父updated->父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

1)父子组件初始化时 生命周期的执行顺序

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted->父mounted

2)父子组件数据更新时 生命周期的执行顺序

父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

3)父子组件销毁时 生命周期的执行顺序

父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

26、http的常见的状态码

  • 1xx(临时响应) 表示临时响应并需要请求者继续执行操作的状态代码

  • 2xx (成功) 表示成功处理了请求的状态码。

    200 – 服务器成功返回网页

  • 3xx (重定向) 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向

    301   (永久移动)  请求的网页已永久移动到新位置。 服务器返回此响应 时,会自动将请求者转到新位置。 
    302   (临时移动)  服务器目前从不同位置的网页响应请求,但请求者应 继续使用原有位置来进行以后的请求。
    304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响 应时,不会返回网页内容。

  • 4xx(请求错误) 这些状态代码表示请求可能出错,妨碍了服务器的处理。

image.png

  • 5xx(服务器错误) 这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本 身的错误,而不是请求出错。

image.png

27、w3c标准盒子模型和怪异(IE)盒子模型

标准盒模型: 一个块的总宽度 = width + padding(左右)+ border(左右)+ margin(左右)

怪异盒模型: 一个块的总宽度 = width + margin(左右)(怪异盒模型中的width已经包含了padding和border的值)

1)标准模型和怪异模型的转换

box-sizing:content-box; 将采用标准模式的盒子模型标准
box-sizing:border-box; 将采用怪异模式的盒子模型标准
box-sizing:inherit; 规定应从父元素继承 box-sizing 属性的值。

2)什么是 BFC

它就是一个 block format content 块级格式化上下文.是一个布局里面的概念.把一个盒子 设置成 bfc 之后,里面无论怎么布局.都不会影响外面的变动.另外如果是一个 bfc 盒子.浮动的元素也会参数计算.用它可以解决一些布局方面的问题吧.比方说.margin 重叠.高度塌陷等. overflow:hidden.float. display:inline-block

28、如何清除浮动

1)使用clear:both清除浮动

在代码中在放一个空的div标签,然后给这个标签设置clear:both来清除浮动对页面的影响。它的优点是简单,方便兼容性好,但是一般情况下不建议使用该方法,因为会造成结构混乱,不利于后期维护

image.png

2)利用伪元素clearfix来清除浮动

给父级元素添加了一个:after伪元素,通过清除伪元素的浮动,达到撑起父元素高度的目的

image.png

3)overflow方法的使用

当给父元素设置了overflow样式,不管是overflow:hidden或overflow:auto都可以清除浮动只要它的值不为visible就可以了,它的本质就是建构了一个BFC,这样使得达到撑起父元素高度的效果

image.png

4)双伪元素方法的使用

通过给父元素设置双伪元素来达到清除浮动的效果

image.png

29、es6的新增语法

ES6 新增特性常用的主要有:
let/const,箭头函数,模板字符串,解构赋值
模块的导入(import)和导出(export default/export),Promise,还有一些 数组字符串的新方法,其实有很多,我平时常用的就这些.

1)Const 声明一个常量.是对象的话.能不能修改里面的值?

可以的. 因为这个常量的不能修改指的是指向的地址不能修改,我只是修改里面的内容.堆空间地址是不变,所以可以修改

2)箭头函数里面的 this 指向是什么样子的?

箭头函数和普通函数的区别是.箭头函数指向定义时.定义的时候 this 就确定 了.指向它的外层. 普通函数是指向调用时.谁调用的我.this 就指向的谁

30、 父子组件传值的几种方法

详情

  • 1、Props 向子组件传递数据,$emit监听子组件事件
  • 2、插槽 prop 将插槽转换为可复用的模板,通过prop进行传值
  • 3、ref
  • 4、依赖注入 (provide)
  • 5、vuex

31、src和href之间的区别

href标识超文本引用,用在linka等元素上,href是引用和页面关联,是在当前元素和引用资源之间建立联系

src表示引用资源,表示替换当前元素,用在imgscriptiframe上,src是页面内容不可缺少的一部分。

1、src

src是source的缩写,指向外部资源的位置,指向的内容将会嵌⼊到⽂档中当前标签所在位置;在请求src资源时会将其指向的 资源下载并应⽤到⽂档内,例如js脚本,img图⽚和frame等元素。

<script src ="js.js"></script>

当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执⾏完毕,图⽚和框架等元素也如此, 类似于将所指向资源嵌⼊当前标签内。这也是为什么将js脚本放在底部⽽不是头部。
2、href

href 是HypertextReference的缩写,指向⽹络资源所在位置,建⽴和当前元素(锚点)或当前⽂档(链接)之间的链接,如果 我们在⽂档中添加

<link href="common.css" rel="stylesheet"/>

那么浏览器会识别该⽂档为css⽂件,就会并⾏下载资源并且不会停⽌对当前⽂档的处理。这也是为什么建议使⽤link⽅式来 加载css,⽽不是使⽤@import⽅式。

补充:link和@import的区别

两者都是外部引用CSS的方式,但是存在一定的区别:

区别1:link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。

区别2:link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。

区别3:link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。

区别4:ink支持使用Javascript控制DOM去改变样式;而@import不支持。

32、let、const、 var的区别

1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题: 内层变量可能覆盖外层变量 用来计数的循环变量泄露为全局变量

(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。

(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

const对象的属性可以修改?

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。 但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

33、computed和watch的区别

相同点:

两者都可以观察页面数据的变化

不同点:

1)computed

一个数据受多个数据影响
在computed中定义的每一个计算属性,都会被缓存起来,只有当计算属性里面依赖的一个或多个属性变化了,才会重新计算当前计算属性的值。
1、支持数据的缓存
2、函数内部的数据改变也会触发
3、不支持异步,当computed内部有异步操作时computed无效
4、如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,一般用computed
5、computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值。(return里面不用声明定义变量就可以进行监听观察数据变化的。

2)watch

watch是属性监听器,一般用来监听属性的变化(也可以用来监听计算属性函数),并做一些操作
1、不支持缓存
2、支持异步
3、只可以设置一个函数,可以带有两个参数
4、监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作。(return里面必须声明定义变量才能进行监听。)

举例说明:

1.在数据变化,会自动调用watch而不调用computed,只有在使用computed的值时才会调用conputed
2.多次使用computed时,如果computed所依赖的属性值没有变化,只有第一次使用会进行计算,之后的每次使用不会重新进行计算,而是会读取缓存
3.连续多次改变属性的值,watch只会执行最后一次
详情

34、vue3常用的api

生命周期

Vue2Vue3
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestoryonBeforeUnmount
destoryedunMounted

1)setup

setup 函数也是 Compsition API 的入口函数,我们的变量、方法都是在该函数里定义的

2)reactive

reactive方法是用来创建一个响应式的数据对象,该api很好的解决了vue2通过defineProperty实现数据响应式的缺陷(无法实现新增属性的响应式,无法实现删除属性的响应式,无法实现懒监听...)

3)ref

ref底层通过reactive包装了一个对象,然后将值传递给该对象的value属性,可以将ref(prop)理解为reactive({value: prop}),所以访问ref所包装的对象时需要加上.value

4)toRef

toRef是将某个对象中的某个值转化为响应式数据,接受两个参数,第一个参数为obj对象,第二个参数为对象的属性名

5)toRefs

toRefs的作用就是将传入对象所有的属性的值都转化为响应式数据对象,该函数只有一个参数,就是obj对象

6) shallowReactive

浅层次的reactive,reactive为深层次的,会为对象的每一层属性都使用proxy添加响应式,而shallowReactive只对对象的第一层属性添加响应式,可以进行性能优化

7) shallowRef

浅层次的ref,可以类比shallowReactive,这里不做多解释

8) toRaw

toRaw用于获取ref或reactive对象的原始数据

9)markRaw

markRaw可以将原始数据标记为非响应式数据,即便使用ref或者reactive包装后仍然无法实现响应式,接收一个参数,即原始数据,并返回被标记后的数据

10)provide&inject

与vue2中的provide&inject的作用是一样的,在vue3中需要手动引入

1、provide:向子组件以及子孙组件传递数据,接收两个参数,第一个参数是key,即数据名,第二个参数为value,即数据值

2、inject:接收父组件或祖先组件传递过来的数据,接收一个参数key,父组件或祖先组件传递的数据名

11)watch&watchEffect

用来监视某项数据变化从而执行指定的操作

1、watch(source, cb, [options]):

source:可以是表达式或函数,用于指定监听的依赖对象

cb: 依赖对象变化后执行的回调函数

options: 可选参数,可以配置的属性有immediate(立即触发回调函数),deep(深度监听)

watch方法会返回一个stop方法,如果需要停止监听,可以直接执行stop方法

2、watchEffect

watchEffect跟watch不同,它不需要手动传入依赖,每次初始化的时候就会执行一次回调函数来自动获取到依赖,无法获取到原始值,只能获取改变后的值

12)computed

13)useStore

vue3中使用vuex的方式跟vue2不一样,需要使用vuex中的useStore方法

14)useRouter

vue3中使用路由的方式跟vue2不一样,需要使用router中的useRouter方法

15)getCurrentInstance

vue3中获取实例的方式跟vue2不一样,需要使用getCurrentInstance方法

16)元素ref属性

元素ref属性可以获取到标签的元素或组件

35、vueRouter的原理

vueRouter是前端路由,当路径切换的时候在浏览器端判断当前路径,并加载当前路径对应的组件,它有两种模式:一种是Hash模式,一种是History模式

第一种:利用 H5 的 history API实现 主要通过 history.pushState 和 history.replaceState 来实 现,不同之处在于,pushState 会增加一条新的历史记录,而 replaceState 则会替换当前的历史记录[发布项目时,需要配置下 apache]

第二种:利用url 的 hash 实现 我们经常在 url 中看到 #,这个 # 有两种情况,一个是我们所谓 的锚点,路由里的 # 不叫锚点,我们称之为 hash,我们说的就是 hash , 主要利用监听哈希值的变化来触发事件 —— hashchange 事件来 做页面局部更新

总结 :hash 方案兼容性好,而 H5 的 history 主要针对高级浏览器。

36、==和===的区别

1、对于string,number等基础类型,== 和=== 是有区别的

不同类型间比较,== 之比较“转化成同一类型后的值”看“值”是否相等,=== 如果类型不同,其结果就是不等

同类型比较,直接进行“值”比较,两者结果一样

2、对于Array,Object等高级类型,== 和 ===是没有区别的

3、基础类型与高级类型, == 和 ===是有区别的

对于 ==,将高级转化为基础类型,进行“值”比较,因为类型不同, === 结果为false

简而言之就是 " == " 只要求值相等; " === " 要求值和类型都相等

37、阻止事件冒泡的方式

1)event.stopPropagation()方法

这是阻止事件冒泡方法,不止事件向document上蔓延,但是默认事件仍然会执行,当你调用这个方法的时候,如果点击一个连接,这个连接仍然会被打开

2)event.preventDefault()方法

这是阻止默认事件的方法,调用此方法时,链接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素

3)return false

这个方法比较暴力,他会同时阻止事件冒泡也会阻止默认事件,不仅阻止了事件往上冒泡,而且阻止了事件本身

4)if(event.target == this){}

//阻止冒泡
a.onclick =function(event){
 	if(event.target == this){
 	console.log('ddd')
	 }
 });

38、http请求方法有哪些

HTTP,即超文本传输协议,是一种实现客户端和服务器之间通信的响应协议,它是用作客户端和服务器之间的请求。

客户端(浏览器)会向服务器提交HTTP请求;然后服务器向客户端返回响应;其中响应包含有关请求的状态信息,还可能包含请求的内容。

HTTP请求的常用方法有详情

  • 1、GET方法;
  • 2、POST方法;
  • 3、HEAD方法;
  • 4、PUT方法;
  • 5、DELETE方法;
  • 6、CONNECT方法;
  • 7、OPTIONS方法;
  • 8、TRACE方法。

39、rem和em的区别

  1. rem是相对于根元素进行计算,而em是相对于当前元素或父元素的字体大小。
  2. rem不仅可以设置字体的大小,还支持元素宽、高等属性。
  3. em是相对于当前元素或父元素进行换算,层级越深,换算越复杂。而rem是相对于根元素计算,避免层级关系。

40、实现一个div上下左右居中的三种方法

情景一:一个浏览器页面中,只有一个div模块,让其上下左右居中

      解决方案:  { position:fixed;

              left:0;

             right:0;

             top:0;

             bottom:0;

             margin:auto; }

情景二:一个父元素div,一个已知宽度、高度的子元素div(200*300)

      解决方案: 1、position布局

             {

              position:absolute/fixed;

              top:50%;

              left:50%;

              margin-left:-100px;

              margin-top:-150px;}

情景三:一个父元素div,一个未知宽度、高度的子元素div

解决方案:
1、position布局,position设为absolute,其他和情景一相同

2、display:table

父级元素:{ display:table;}

子级元素: { display:table-cell;vertical-align:middle }

3、flex布局

父级元素:{ display:flex;flex-direction:row;justify-content:center;align-items:center;} 子级元素:{flex:1}

4、translate

position: absolute;

top: 50%;

left: 50%;

-webkit-transform: translate(-50%, -50%);

-moz-transform: translate(-50%, -50%);

-ms-transform: translate(-50%, -50%);

-o-transform: translate(-50%, -50%);

transform: translate(-50%, -50%);

41、v一for和v一if的区别

首先在官方文档中明确指出v-for和v-if不建议一起使用。

原因: 因为v-for的优先级比v-if的优先级高,所以每次渲染时都会先循环再进行条件判断,而又因为v-if会根据渲染条件为ture或false来决定渲染与否的,所以如果将v-if 和 v-for 用在一起会特别消耗性能

v-if和v-for的作用:

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染。

v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组或者对象,而 item 则是被迭代的数组元素的别名。

在 v-for 的时候,建议设置key值,并且保证每个key值是独一无二的,这便于diff算法进行优化。

在使用中,v-for优先级比v-if高

42、router传参的方式

1)params传参

$route.params 类型: Object。 ⼀个 key/value 对象,包含了动态⽚段和全匹配⽚段,如果没有路由参数,就是⼀个空对象

this.$router.push({ name: 'news', params: { type: 1 }}

通过路由的名字的传参的话,必须使⽤路由对象的属性 。

2)query传参

如果没有查询参数,则是个空对象。

const type = 1
this.$router.push({ path: 'news', query: { type: type  }})

前⾯说到的传参都是⽐较简单的键值对。如果要传⼀个内容⽐较多的对象应该怎么传? 可以先⽤JSON.stringfy() 把参数格式化,传到页⾯之后再⽤JSON.parse()去解析

43、v一if和v一show的区别

v-if 和 v-show 都可以显示和隐藏一个元素,但有本质区别

v-if 是惰性的,只是值为 false 就不会加载对应元素,为 true 才动态加 载对应元素

v-show:是无论为 true 和为 false 都会加载对应 html 代码,但为 false 时用 display:none 隐藏不在页面显示,但为 true 时页面上用 display:block 显示其效果

适用场景:切换频繁的场合用 v-show,切换不频繁的场合用 v-if

44、url输入到地址栏后发生了什么

  • DNS解析(域名解析
  • 浏览器主机根据ip地址与服务器建立TCP连接
  • 发送HTTP请求
  • 服务器处理请求
  • 断开TCP连接
  • 浏览器解析文件
  • 浏览器布局渲染 详情

45、Number和parselnt的区别

1、当转换的内容包含非数字的时候,Number() 会返回NaN(Not a Number);parseInt() 要看情况,如果以数字开头,就会返回开头的合法数字部分,如果以非数字开头,则返回NaN。

2、parseInt()仅返回整数值的区别,而Number()返回包括浮点的所有数字。

46、axios的封装有哪一些

axios官网

  • 环境切换
  • 设置请求超时
  • 请求头设置
  • 请求拦截
  • 响应拦截

1)配置封装 config-util.js

export default {
  baseUrl: {
    // 开发环境
    dev: 'xxx',
    // 生产环境
    prod: 'xxx'
  }
}

2)请求封装 http-util.js

import axios from 'axios' // 引入axios
import config from '@/api/utils/config-util.js' // 引入配置文件

const instance = axios.create({
  baseURL: config.baseUrl.dev,
  timeout: 60000
})

// 请求拦截器
instance.interceptors.request.use(function (config) {
  console.log('发请求之前', config)
  return config
}, function (error) {
  console.log('请求错误', error)
  return Promise.reject(error)
})

// 响应拦截器
instance.interceptors.response.use(function (response) {
  console.log('得到的响应数据', response)
  return response
}, function (error) {
  console.log('响应错误', error)
  return Promise.reject(error)
})

// get请求
export function get (url, data = {}) {
  return new Promise((resolve, reject) => {
    instance
      .get(url, {
        params: data
      })
      .then((response) => {
        resolve(response)
      })
      .catch((err) => {
        reject(err)
      })
  })
}

// post请求
export function post (url, data = {}) {
  return new Promise((resolve, reject) => {
    instance.post(url, data).then(
      (response) => {
        resolve(response.data)
      },
      (err) => {
        reject(err)
      }
    )
  })
}

47、js常见的创建对象方式

1)Object构造函数创建

var Person = new Object();
Person.name = 'Nike';
Person.age = 29;

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

2)使用对象字面量表示法

var Person = {};//相当于var Person = new Object();
var Person = {
 name:'Nike';
 age:29;  
}

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

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

3)使用工厂模式创建对象

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函数中,返回的是一个对象。那么我们就无法判断返回的对象究竟是一个什么样的类型。于是就出现了第四种创建对象的模式。

4)使用构造函数创建对象

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
alert(person2 instanceof Object);//ture

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

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

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

5)原型创建对象模式

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' --来自原型

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。

这时候我们就可以使用构造函数模式与原型模式结合的方式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性

6)组合使用构造函数模式和原型模式

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');